@craft-native/ios 0.0.435 → 0.0.436
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/package.json
CHANGED
|
@@ -18,10 +18,22 @@ struct CraftNativeMutationFailure: Error, Equatable {
|
|
|
18
18
|
struct CraftNativeMutationResult {
|
|
19
19
|
let batchId: String
|
|
20
20
|
let revision: Int
|
|
21
|
-
let document: [String: Any]?
|
|
22
21
|
let updatedNodeIds: [String]
|
|
23
22
|
let affectedNodeIds: [String]
|
|
24
23
|
let requiresFullRender: Bool
|
|
24
|
+
private let materialize: () -> [String: Any]?
|
|
25
|
+
/// The committed tree, built when asked for: a host that redraws only the
|
|
26
|
+
/// nodes a batch touched never needs the whole tree.
|
|
27
|
+
var document: [String: Any]? { materialize() }
|
|
28
|
+
|
|
29
|
+
init(batchId: String, revision: Int, document: @escaping () -> [String: Any]?, updatedNodeIds: [String], affectedNodeIds: [String], requiresFullRender: Bool) {
|
|
30
|
+
self.batchId = batchId
|
|
31
|
+
self.revision = revision
|
|
32
|
+
self.materialize = document
|
|
33
|
+
self.updatedNodeIds = updatedNodeIds
|
|
34
|
+
self.affectedNodeIds = affectedNodeIds
|
|
35
|
+
self.requiresFullRender = requiresFullRender
|
|
36
|
+
}
|
|
25
37
|
}
|
|
26
38
|
|
|
27
39
|
/// Validates and applies versioned native-tree mutations without touching UIKit.
|
|
@@ -91,6 +103,8 @@ final class CraftNativeMutationDocument {
|
|
|
91
103
|
var updatedNodeIds: [String] = []
|
|
92
104
|
var affectedNodeIds: [String] = []
|
|
93
105
|
var requiresFullRender = false
|
|
106
|
+
// Only an operation that adds, moves or removes nodes can leave one unreachable.
|
|
107
|
+
var reshaped = false
|
|
94
108
|
for (index, value) in operations.enumerated() {
|
|
95
109
|
guard let operation = value as? [String: Any], let name = operation["op"] as? String else {
|
|
96
110
|
throw CraftNativeMutationFailure(
|
|
@@ -99,6 +113,7 @@ final class CraftNativeMutationDocument {
|
|
|
99
113
|
operationIndex: index
|
|
100
114
|
)
|
|
101
115
|
}
|
|
116
|
+
if name != "updateNode" { reshaped = true }
|
|
102
117
|
do {
|
|
103
118
|
switch name {
|
|
104
119
|
case "createNode":
|
|
@@ -147,7 +162,7 @@ final class CraftNativeMutationDocument {
|
|
|
147
162
|
guard let root = candidateNodes[candidateRoot], root.parent == nil else {
|
|
148
163
|
throw CraftNativeMutationFailure("INVALID_TREE", "root node must not have a parent")
|
|
149
164
|
}
|
|
150
|
-
guard descendants(of: candidateRoot, nodes: candidateNodes).count + 1 == candidateNodes.count else {
|
|
165
|
+
guard !reshaped || descendants(of: candidateRoot, nodes: candidateNodes).count + 1 == candidateNodes.count else {
|
|
151
166
|
throw CraftNativeMutationFailure("INVALID_TREE", "all nodes must be reachable from the root")
|
|
152
167
|
}
|
|
153
168
|
}
|
|
@@ -158,7 +173,7 @@ final class CraftNativeMutationDocument {
|
|
|
158
173
|
return CraftNativeMutationResult(
|
|
159
174
|
batchId: batchId,
|
|
160
175
|
revision: nextRevision,
|
|
161
|
-
document: candidateRoot.flatMap { materialize($0, nodes: candidateNodes) },
|
|
176
|
+
document: { [candidateRoot, candidateNodes, weak self] in candidateRoot.flatMap { self?.materialize($0, nodes: candidateNodes) } },
|
|
162
177
|
updatedNodeIds: updatedNodeIds,
|
|
163
178
|
affectedNodeIds: affectedNodeIds,
|
|
164
179
|
requiresFullRender: requiresFullRender
|
|
@@ -285,8 +285,15 @@ final class DriverTests: XCTestCase {
|
|
|
285
285
|
return (parts[0], parts.count > 1 ? Double(parts[1]) ?? 10 : 10)
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
-
/// A
|
|
289
|
-
///
|
|
288
|
+
/// A switch reports "0" or "1". Any other value is not a switch state to
|
|
289
|
+
/// wait on, so the tap stands as it would for any other control.
|
|
290
|
+
func switchFlipped(from before: String?, to after: String?) -> Bool {
|
|
291
|
+
let states: Set<String> = ["0", "1"]
|
|
292
|
+
guard let before, let after, states.contains(before), states.contains(after) else { return true }
|
|
293
|
+
return before != after
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/// Deliver one gesture; the command's observation determines success.
|
|
290
297
|
func press(_ element: XCUIElement) {
|
|
291
298
|
if element.isHittable { element.tap() }
|
|
292
299
|
else { element.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() }
|
|
@@ -438,8 +445,21 @@ final class DriverTests: XCTestCase {
|
|
|
438
445
|
else { XCTFail("Element not found"); note("FAIL tapexact \(arg): NOT FOUND") }
|
|
439
446
|
case "tapid":
|
|
440
447
|
let target = identified(arg)
|
|
441
|
-
if target.waitForExistence(timeout: 10) {
|
|
442
|
-
else
|
|
448
|
+
if !target.waitForExistence(timeout: 10) { XCTFail("Element not found"); note("FAIL tapid \(arg): NOT FOUND") }
|
|
449
|
+
else if target.elementType == .switch {
|
|
450
|
+
// Observe one physical gesture. Retapping could conceal a
|
|
451
|
+
// renderer or hit-testing failure (HQ agreement job 7570).
|
|
452
|
+
let before = target.value as? String
|
|
453
|
+
press(target)
|
|
454
|
+
let settle = Date().addingTimeInterval(1.5)
|
|
455
|
+
var flipped = false
|
|
456
|
+
repeat {
|
|
457
|
+
flipped = switchFlipped(from: before, to: target.value as? String)
|
|
458
|
+
if !flipped { Thread.sleep(forTimeInterval: 0.25) }
|
|
459
|
+
} while !flipped && Date() < settle
|
|
460
|
+
check(flipped, "tapid \(arg) flips the switch")
|
|
461
|
+
}
|
|
462
|
+
else { press(target); note("tapid \(arg): ok") }
|
|
443
463
|
case "longpressid":
|
|
444
464
|
let target = identified(arg)
|
|
445
465
|
guard target.waitForExistence(timeout: 10), target.isHittable else {
|