@craft-native/ios 0.0.449 → 0.0.464
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -1
- package/dist/cli.js +290 -81
- package/dist/index.d.ts +1 -685
- package/dist/index.js +292 -81
- package/dist/internal/ios/src/index.d.ts +684 -0
- package/dist/internal/typescript/src/native-notifications.d.ts +17 -0
- package/dist/internal/typescript/src/native-routing.d.ts +26 -0
- package/package.json +1 -1
- package/templates/CraftApp.swift +118 -16
- package/templates/CraftHybrid.swift +75 -5
- package/templates/CraftNativeActions.swift +84 -13
- package/templates/CraftNativeFiles.swift +83 -1
- package/templates/CraftNativeScreen.swift +130 -4
- package/templates/CraftNotificationService.swift +199 -0
- package/templates/CraftVoiceRecording.swift +29 -1
- package/templates/testing/DriverTests.swift.template +61 -6
- /package/dist/{app-shortcuts.d.ts → internal/ios/src/app-shortcuts.d.ts} +0 -0
- /package/dist/{runtime-profile.d.ts → internal/ios/src/runtime-profile.d.ts} +0 -0
- /package/dist/{source-audit.d.ts → internal/ios/src/source-audit.d.ts} +0 -0
- /package/dist/{testing.d.ts → internal/ios/src/testing.d.ts} +0 -0
|
@@ -61,6 +61,8 @@ final class DriverTests: XCTestCase {
|
|
|
61
61
|
let tabs: [String] = {{TABS}}
|
|
62
62
|
let tabIdentifiers: [String: String] = {{TAB_IDENTIFIERS}}
|
|
63
63
|
private var bundledJourney = false
|
|
64
|
+
private var lastTappedInput: XCUIElement?
|
|
65
|
+
private var lastBooleanTap: [String: Any]?
|
|
64
66
|
|
|
65
67
|
func get(_ path: String, timeout: Double = 15) -> Data? {
|
|
66
68
|
var result: Data?
|
|
@@ -245,6 +247,9 @@ final class DriverTests: XCTestCase {
|
|
|
245
247
|
/// nor any descendant has keyboard focus". A later tap lands once the app
|
|
246
248
|
/// is idle again.
|
|
247
249
|
func focus(_ target: XCUIElement, seconds: Double = 15) -> Bool {
|
|
250
|
+
// Retapping an already focused field can collapse its selection. A
|
|
251
|
+
// caller that just selected text must type into that same selection.
|
|
252
|
+
if hasKeyboardFocus(target) { return true }
|
|
248
253
|
let end = Date().addingTimeInterval(seconds)
|
|
249
254
|
repeat {
|
|
250
255
|
target.tap()
|
|
@@ -290,24 +295,39 @@ final class DriverTests: XCTestCase {
|
|
|
290
295
|
return (parts[0], parts.count > 1 ? Double(parts[1]) ?? 10 : 10)
|
|
291
296
|
}
|
|
292
297
|
|
|
293
|
-
/// A switch reports "0" or "1". Any other value is not a switch state to
|
|
294
|
-
/// wait on, so the tap stands as it would for any other control.
|
|
295
298
|
/// The switch answers `/exec` with 200 whatever the hook did, its first
|
|
296
299
|
/// word the hook's exit code: only `exit 0` is a hook that worked.
|
|
297
300
|
func hookSucceeded(_ answer: String) -> Bool {
|
|
298
|
-
|
|
301
|
+
// get() already requires HTTP success. Custom control servers may
|
|
302
|
+
// return a message or no body. Preserve the legacy shell protocol's
|
|
303
|
+
// explicit failures, without accepting "exit 01" as a zero status.
|
|
304
|
+
let body = answer.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
305
|
+
if body == "no such hook" { return false }
|
|
306
|
+
let fields = body.split(whereSeparator: { $0.isWhitespace })
|
|
307
|
+
guard fields.first == "exit" else { return true }
|
|
308
|
+
return fields.count >= 2 && fields[1] == "0"
|
|
299
309
|
}
|
|
300
310
|
|
|
311
|
+
/// UIKit and Craft's solid toggle both publish "0" or "1", regardless
|
|
312
|
+
/// of the accessibility element type that XCTest assigns to them.
|
|
301
313
|
func switchFlipped(from before: String?, to after: String?) -> Bool {
|
|
302
314
|
let states: Set<String> = ["0", "1"]
|
|
303
|
-
|
|
315
|
+
// A lost accessibility value is missing evidence, not a successful tap.
|
|
316
|
+
guard let before, let after, states.contains(before), states.contains(after) else { return false }
|
|
304
317
|
return before != after
|
|
305
318
|
}
|
|
306
319
|
|
|
307
320
|
/// Deliver one gesture; the command's observation determines success.
|
|
308
321
|
func press(_ element: XCUIElement) {
|
|
322
|
+
// Resolve this before activation: a menu item can disappear as soon
|
|
323
|
+
// as its gesture completes (for example Select All).
|
|
324
|
+
let input = [.textField, .secureTextField, .textView, .searchField].contains(element.elementType)
|
|
325
|
+
? element : nil
|
|
309
326
|
if element.isHittable { element.tap() }
|
|
310
327
|
else { element.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() }
|
|
328
|
+
// A tap on a read-only text view need not open the keyboard. Only a
|
|
329
|
+
// following type command observes whether this target took focus.
|
|
330
|
+
lastTappedInput = input
|
|
311
331
|
}
|
|
312
332
|
|
|
313
333
|
func answerOpenPrompt() {
|
|
@@ -343,6 +363,11 @@ final class DriverTests: XCTestCase {
|
|
|
343
363
|
let parts = line.split(separator: " ", maxSplits: 1).map(String.init)
|
|
344
364
|
let command = parts[0]
|
|
345
365
|
let arg = parts.count > 1 ? parts[1] : ""
|
|
366
|
+
// Context changes can move focus without going through press().
|
|
367
|
+
if ["launch", "activate", "terminate", "home", "background", "openurl", "universallink",
|
|
368
|
+
"back", "return", "tab", "tapxy", "edge", "notifications", "systemtap", "longpressid", "exec"].contains(command) {
|
|
369
|
+
lastTappedInput = nil
|
|
370
|
+
}
|
|
346
371
|
if bundledJourney && ["net", "delay", "exec"].contains(command) {
|
|
347
372
|
check(false, "\(command) requires a control-server journey")
|
|
348
373
|
return
|
|
@@ -460,12 +485,15 @@ final class DriverTests: XCTestCase {
|
|
|
460
485
|
if let target = poll(10, { exact(arg) }) { press(target); note("tapexact \(arg): ok") }
|
|
461
486
|
else { XCTFail("Element not found"); note("FAIL tapexact \(arg): NOT FOUND") }
|
|
462
487
|
case "tapid":
|
|
488
|
+
lastBooleanTap = nil
|
|
463
489
|
let target = identified(arg)
|
|
464
490
|
if !target.waitForExistence(timeout: 10) { XCTFail("Element not found"); note("FAIL tapid \(arg): NOT FOUND") }
|
|
465
|
-
else if target.elementType == .switch {
|
|
491
|
+
else if target.elementType == .switch || ["0", "1"].contains(target.value as? String ?? "") {
|
|
466
492
|
// Observe one physical gesture. Retapping could conceal a
|
|
467
493
|
// renderer or hit-testing failure (HQ agreement job 7570).
|
|
468
494
|
let before = target.value as? String
|
|
495
|
+
let enabledBefore = target.isEnabled
|
|
496
|
+
let frameBefore = target.frame
|
|
469
497
|
press(target)
|
|
470
498
|
let settle = Date().addingTimeInterval(1.5)
|
|
471
499
|
var flipped = false
|
|
@@ -473,6 +501,15 @@ final class DriverTests: XCTestCase {
|
|
|
473
501
|
flipped = switchFlipped(from: before, to: target.value as? String)
|
|
474
502
|
if !flipped { Thread.sleep(forTimeInterval: 0.25) }
|
|
475
503
|
} while !flipped && Date() < settle
|
|
504
|
+
let observation: [String: Any] = [
|
|
505
|
+
"id": arg, "gestures": 1, "flipped": flipped,
|
|
506
|
+
"enabledBefore": enabledBefore, "enabledAfter": target.isEnabled,
|
|
507
|
+
"valueBefore": before ?? "unknown", "valueAfter": target.value as? String ?? "unknown",
|
|
508
|
+
"frameBefore": [frameBefore.origin.x, frameBefore.origin.y, frameBefore.width, frameBefore.height],
|
|
509
|
+
]
|
|
510
|
+
lastBooleanTap = observation
|
|
511
|
+
if let data = try? JSONSerialization.data(withJSONObject: observation, options: [.sortedKeys]),
|
|
512
|
+
let json = String(data: data, encoding: .utf8) { note("BOOLEAN_TAP \(json)") }
|
|
476
513
|
check(flipped, "tapid \(arg) flips the switch")
|
|
477
514
|
}
|
|
478
515
|
else { press(target); note("tapid \(arg): ok") }
|
|
@@ -482,6 +519,12 @@ final class DriverTests: XCTestCase {
|
|
|
482
519
|
check(false, "longpressid \(arg) is hittable")
|
|
483
520
|
return
|
|
484
521
|
}
|
|
522
|
+
if [.textField, .secureTextField, .textView].contains(target.elementType) {
|
|
523
|
+
guard focus(target) else {
|
|
524
|
+
check(false, "longpressid \(arg) takes keyboard focus")
|
|
525
|
+
return
|
|
526
|
+
}
|
|
527
|
+
}
|
|
485
528
|
target.press(forDuration: 1.2)
|
|
486
529
|
note("longpressid \(arg): ok")
|
|
487
530
|
case "holdtab":
|
|
@@ -580,9 +623,20 @@ final class DriverTests: XCTestCase {
|
|
|
580
623
|
start.press(forDuration: 0.05, thenDragTo: app.coordinate(withNormalizedOffset: CGVector(dx: 0.8, dy: 0.5)))
|
|
581
624
|
note("back: edge swipe")
|
|
582
625
|
}
|
|
583
|
-
case "type":
|
|
626
|
+
case "type":
|
|
627
|
+
let target = lastTappedInput ?? app
|
|
628
|
+
lastTappedInput = nil
|
|
629
|
+
// A returned gesture is not proof of keyboard focus. Observe the
|
|
630
|
+
// one gesture before typing, including WebKit/cold-keyboard focus
|
|
631
|
+
// delivery. Never retap and conceal a dropped input event.
|
|
632
|
+
guard poll(15, { self.hasKeyboardFocus(target) ? target : nil }) != nil else {
|
|
633
|
+
check(false, "type into \(target.label) takes keyboard focus")
|
|
634
|
+
return
|
|
635
|
+
}
|
|
636
|
+
app.typeText(arg)
|
|
584
637
|
case "return": app.typeText(XCUIKeyboardKey.return.rawValue); note("return")
|
|
585
638
|
case "typeid":
|
|
639
|
+
lastTappedInput = nil
|
|
586
640
|
let idParts = arg.split(separator: " ", maxSplits: 1).map(String.init)
|
|
587
641
|
let target = identified(idParts[0])
|
|
588
642
|
if target.waitForExistence(timeout: 10) {
|
|
@@ -611,6 +665,7 @@ final class DriverTests: XCTestCase {
|
|
|
611
665
|
let target = XCUIApplication(bundleIdentifier: bundle)
|
|
612
666
|
check(target.wait(for: .runningForeground, timeout: seconds), "expectapp \(bundle)")
|
|
613
667
|
case "field":
|
|
668
|
+
lastTappedInput = nil
|
|
614
669
|
let fieldParts = arg.split(separator: " ", maxSplits: 1).map(String.init)
|
|
615
670
|
let index = Int(fieldParts[0]) ?? 0
|
|
616
671
|
let fields = app.textFields.allElementsBoundByIndex + app.secureTextFields.allElementsBoundByIndex
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|