Skip to content

Commit 0dceff8

Browse files
Add Codable and wire-format regression tests for the portable model
Migrate the existing model Codable tests to the portable API (AccessibilityShape/Rect/Point instead of AccessibilityElement.Shape/CGRect/ CGPoint) and add regression tests that decode legacy JSON fixtures and assert the geometry array encodings, locking in wire-format compatibility. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7039d35 commit 0dceff8

1 file changed

Lines changed: 64 additions & 18 deletions

File tree

‎Example/UnitTests/AccessibilityHierarchyParserTests.swift‎

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -504,8 +504,8 @@ final class AccessibilityHierarchyParserTests: XCTestCase {
504504
identifier: "test-button-id",
505505
hint: "Double tap to activate",
506506
userInputLabels: ["tap button", "press button"],
507-
shape: .frame(CGRect(x: 10, y: 20, width: 100, height: 44)),
508-
activationPoint: CGPoint(x: 60, y: 42),
507+
shape: .frame(AccessibilityRect(x: 10, y: 20, width: 100, height: 44)),
508+
activationPoint: AccessibilityPoint(x: 60, y: 42),
509509
usesDefaultActivationPoint: true,
510510
customActions: ["Delete"],
511511
customContent: [],
@@ -538,7 +538,7 @@ final class AccessibilityHierarchyParserTests: XCTestCase {
538538
func testAccessibilityContainerCodable() throws {
539539
let container = AccessibilityContainer(
540540
type: .list,
541-
frame: CGRect(x: 0, y: 0, width: 320, height: 200)
541+
frame: AccessibilityRect(x: 0, y: 0, width: 320, height: 200)
542542
)
543543

544544
let encoder = JSONEncoder()
@@ -560,8 +560,8 @@ final class AccessibilityHierarchyParserTests: XCTestCase {
560560
identifier: nil,
561561
hint: nil,
562562
userInputLabels: nil,
563-
shape: .frame(CGRect(x: 0, y: 0, width: 100, height: 44)),
564-
activationPoint: CGPoint(x: 50, y: 22),
563+
shape: .frame(AccessibilityRect(x: 0, y: 0, width: 100, height: 44)),
564+
activationPoint: AccessibilityPoint(x: 50, y: 22),
565565
usesDefaultActivationPoint: true,
566566
customActions: [],
567567
customContent: [],
@@ -578,8 +578,8 @@ final class AccessibilityHierarchyParserTests: XCTestCase {
578578
identifier: nil,
579579
hint: nil,
580580
userInputLabels: nil,
581-
shape: .frame(CGRect(x: 0, y: 50, width: 100, height: 44)),
582-
activationPoint: CGPoint(x: 50, y: 72),
581+
shape: .frame(AccessibilityRect(x: 0, y: 50, width: 100, height: 44)),
582+
activationPoint: AccessibilityPoint(x: 50, y: 72),
583583
usesDefaultActivationPoint: true,
584584
customActions: [],
585585
customContent: [],
@@ -590,7 +590,7 @@ final class AccessibilityHierarchyParserTests: XCTestCase {
590590

591591
let container = AccessibilityContainer(
592592
type: .list,
593-
frame: CGRect(x: 0, y: 0, width: 100, height: 100)
593+
frame: AccessibilityRect(x: 0, y: 0, width: 100, height: 100)
594594
)
595595

596596
let hierarchy: [AccessibilityHierarchy] = [
@@ -632,21 +632,66 @@ final class AccessibilityHierarchyParserTests: XCTestCase {
632632

633633
func testShapeCodableWithPath() throws {
634634
let path = UIBezierPath(roundedRect: CGRect(x: 10, y: 20, width: 100, height: 50), cornerRadius: 8)
635-
let shape = AccessibilityElement.Shape.path(path)
635+
let elements = AccessibilityPathElement.elements(from: path.cgPath)
636+
let shape = AccessibilityShape.path(elements)
636637

637638
let encoder = JSONEncoder()
638639
let data = try encoder.encode(shape)
639640

640641
let decoder = JSONDecoder()
641-
let decoded = try decoder.decode(AccessibilityElement.Shape.self, from: data)
642+
let decoded = try decoder.decode(AccessibilityShape.self, from: data)
642643

643644
if case let .path(decodedPath) = decoded {
644-
XCTAssertEqual(decodedPath.bounds, path.bounds)
645+
XCTAssertEqual(decodedPath, elements)
645646
} else {
646647
XCTFail("Expected path shape")
647648
}
648649
}
649650

651+
// MARK: - Wire-Format Compatibility
652+
653+
// The portable model types replaced CoreGraphics geometry but must keep the exact
654+
// JSON wire format that the previous `CGPoint`/`CGRect`/`AccessibilityElement.Shape`
655+
// Codable conformances produced, so persisted payloads keep decoding.
656+
657+
func testGeometryEncodesAsLegacyCGGeometryArrays() throws {
658+
let encoder = JSONEncoder()
659+
660+
let point = try encoder.encode(AccessibilityPoint(x: 1, y: 2))
661+
XCTAssertEqual(String(data: point, encoding: .utf8), "[1,2]")
662+
663+
let size = try encoder.encode(AccessibilitySize(width: 100, height: 44))
664+
XCTAssertEqual(String(data: size, encoding: .utf8), "[100,44]")
665+
666+
let rect = try encoder.encode(AccessibilityRect(x: 10, y: 20, width: 100, height: 44))
667+
XCTAssertEqual(String(data: rect, encoding: .utf8), "[[10,20],[100,44]]")
668+
}
669+
670+
func testShapeDecodesLegacyFrameWireFormat() throws {
671+
let legacyJSON = Data(#"{"type":"frame","frame":[[10,20],[100,44]]}"#.utf8)
672+
let decoded = try JSONDecoder().decode(AccessibilityShape.self, from: legacyJSON)
673+
XCTAssertEqual(decoded, .frame(AccessibilityRect(x: 10, y: 20, width: 100, height: 44)))
674+
}
675+
676+
func testShapeDecodesLegacyPathWireFormat() throws {
677+
let legacyJSON = Data(#"""
678+
{"type":"path","pathElements":[{"move":{"to":[0,0]}},{"line":{"to":[100,0]}},{"closeSubpath":{}}]}
679+
"""#.utf8)
680+
let decoded = try JSONDecoder().decode(AccessibilityShape.self, from: legacyJSON)
681+
XCTAssertEqual(decoded, .path([
682+
.move(to: AccessibilityPoint(x: 0, y: 0)),
683+
.line(to: AccessibilityPoint(x: 100, y: 0)),
684+
.closeSubpath,
685+
]))
686+
}
687+
688+
func testShapeFrameEncodesWithTypeDiscriminator() throws {
689+
let data = try JSONEncoder().encode(AccessibilityShape.frame(AccessibilityRect(x: 10, y: 20, width: 100, height: 44)))
690+
let object = try JSONSerialization.jsonObject(with: data) as! [String: Any]
691+
XCTAssertEqual(object["type"] as? String, "frame")
692+
XCTAssertEqual(object["frame"] as? [[Double]], [[10, 20], [100, 44]])
693+
}
694+
650695
func testContainerTypeCodable() throws {
651696
let types: [AccessibilityContainer.ContainerType] = [
652697
.list,
@@ -708,17 +753,18 @@ final class AccessibilityHierarchyParserTests: XCTestCase {
708753
path.addLine(to: CGPoint(x: 100, y: 50))
709754
path.close()
710755

711-
let shape = AccessibilityElement.Shape.path(path)
756+
let elements = AccessibilityPathElement.elements(from: path.cgPath)
757+
let shape = AccessibilityShape.path(elements)
712758

713759
let encoder = JSONEncoder()
714760
let data = try encoder.encode(shape)
715761

716762
// Verify round-trip works
717763
let decoder = JSONDecoder()
718-
let decoded = try decoder.decode(AccessibilityElement.Shape.self, from: data)
764+
let decoded = try decoder.decode(AccessibilityShape.self, from: data)
719765

720766
if case let .path(decodedPath) = decoded {
721-
XCTAssertEqual(decodedPath.bounds, path.bounds)
767+
XCTAssertEqual(decodedPath, elements)
722768
} else {
723769
XCTFail("Expected path shape")
724770
}
@@ -786,7 +832,7 @@ final class AccessibilityHierarchyParserTests: XCTestCase {
786832
func testDataTableContainerCodable() throws {
787833
let container = AccessibilityContainer(
788834
type: .dataTable(rowCount: 5, columnCount: 4),
789-
frame: CGRect(x: 0, y: 0, width: 320, height: 200)
835+
frame: AccessibilityRect(x: 0, y: 0, width: 320, height: 200)
790836
)
791837

792838
let encoder = JSONEncoder()
@@ -806,7 +852,7 @@ final class AccessibilityHierarchyParserTests: XCTestCase {
806852
func testSemanticGroupContainerCodable() throws {
807853
let container = AccessibilityContainer(
808854
type: .semanticGroup(label: "Group Label", value: "Group Value", identifier: "group-id"),
809-
frame: CGRect(x: 0, y: 0, width: 200, height: 100)
855+
frame: AccessibilityRect(x: 0, y: 0, width: 200, height: 100)
810856
)
811857

812858
let encoder = JSONEncoder()
@@ -827,7 +873,7 @@ final class AccessibilityHierarchyParserTests: XCTestCase {
827873
func testTabBarContainerCodable() throws {
828874
let container = AccessibilityContainer(
829875
type: .tabBar,
830-
frame: CGRect(x: 0, y: 0, width: 320, height: 49)
876+
frame: AccessibilityRect(x: 0, y: 0, width: 320, height: 49)
831877
)
832878

833879
let encoder = JSONEncoder()
@@ -842,7 +888,7 @@ final class AccessibilityHierarchyParserTests: XCTestCase {
842888
func testLandmarkContainerCodable() throws {
843889
let container = AccessibilityContainer(
844890
type: .landmark,
845-
frame: CGRect(x: 0, y: 0, width: 320, height: 200)
891+
frame: AccessibilityRect(x: 0, y: 0, width: 320, height: 200)
846892
)
847893

848894
let encoder = JSONEncoder()

0 commit comments

Comments
 (0)