⋯ 193 lines Show the 193 lines above this hunk @@ -194,6 +194,44 @@ final class NativeNavigationUnitTests: XCTestCase { 194 194 XCTAssertEqual ( link . gestureRecognizers ? . filter { $0 is UITapGestureRecognizer } . count , 1 ) 195 195 } 196 196 197 + func testRepeatedSelectorsRetainDistinctProtocolViewsAcrossReorders ( ) throws { 198 + let controller = CraftNativeScreenController ( config : CraftConfig ( ) ) 199 + window . rootViewController = controller 200 + controller . loadViewIfNeeded ( ) 201 + window . makeKeyAndVisible ( ) 202 + controller . view . layoutIfNeeded ( ) 203 + func document ( _ ids : [ String ] ) - > [ String : Any ] { 204 + [ "id" : "identity-root" , "type" : "View" , "children" : ids . map { id in 205 + [ "id" : "row-\(id)" , "type" : "View" , "props" : [ "testID" : "repeated-row" ] , 206 + "children" : [ [ "id" : "input-\(id)" , "type" : "TextInput" , "props" : [ "testID" : "repeated-input" , "accessibilityLabel" : "\(id) draft" ] ] ] ] 207 + } ] 208 + } 209 + func matching ( _ view : UIView , _ id : String ) - > [ UIView ] { 210 + ( view . accessibilityIdentifier = = id ? [ view ] : [ ] ) + view . subviews . flatMap { matching ( $0 , id ) } 211 + } 212 + controller . render ( document ( [ "alpha" , "beta" ] ) ) 213 + controller . view . layoutIfNeeded ( ) 214 + let original = matching ( controller . view , "repeated-row" ) 215 + XCTAssertEqual ( original . count , 2 ) 216 + guard original . count = = 2 else { return } 217 + let alpha = original [ 0 ] 218 + let beta = original [ 1 ] 219 + let betaInput = try XCTUnwrap ( matching ( beta , "repeated-input" ) . first as ? UITextField ) 220 + XCTAssertTrue ( betaInput . becomeFirstResponder ( ) ) 221 + betaInput . text = "Beta private draft" 222 + betaInput . sendActions ( for : . editingChanged ) 223 + controller . render ( document ( [ "beta" , "alpha" ] ) ) 224 + controller . view . layoutIfNeeded ( ) 225 + let reordered = matching ( controller . view , "repeated-row" ) 226 + XCTAssertEqual ( reordered . count , 2 ) 227 + guard reordered . count = = 2 else { return } 228 + XCTAssertTrue ( reordered [ 0 ] = = = beta , "the repeated selector overrode Beta's protocol identity" ) 229 + XCTAssertTrue ( reordered [ 1 ] = = = alpha , "the repeated selector overrode Alpha's protocol identity" ) 230 + XCTAssertTrue ( matching ( reordered [ 0 ] , "repeated-input" ) . first = = = betaInput ) 231 + XCTAssertEqual ( betaInput . text , "Beta private draft" ) 232 + XCTAssertTrue ( betaInput . isFirstResponder , "reordering replaced the focused native input" ) 233 + } 234 + 197 235 func testIdlePreparationKeepsSelectionAndCurrentStackIntact ( ) async throws { 198 236 let home = try XCTUnwrap ( stack ( "/" ) ) 199 237 let root = home . topViewController