@@ -68,6 +68,45 @@ class CALayer_DisableActionsTests: XCTestCase {
6868 }
6969 }
7070
71+ func test_disableActions_insideDisablingTransaction_skipsActionsInstall( ) throws {
72+ let frame = CGRect ( x: 0 , y: 0 , width: 50 , height: 50 )
73+ let layer = CALayer ( )
74+ layer. frame = frame
75+
76+ let window = TestWindow ( )
77+ window. layer. addSublayer ( layer)
78+
79+ // wait for the layer to have a presentation layer
80+ expect ( layer. presentation ( ) ) . toEventuallyNot ( beNil ( ) )
81+
82+ // install a single-entry sentinel actions dictionary so we can tell whether the call swaps `actions` (slow path,
83+ // which would install a 2-key disabling dictionary) or leaves them untouched (fast path).
84+ let sentinel : [ String : CAAction ] = [ " sentinel " : NSNull ( ) ]
85+ layer. actions = sentinel
86+
87+ var workRan = false
88+ var actionsCountDuringWork : Int ?
89+
90+ // mimic the render pass: run inside a transaction that already disables actions for all keys.
91+ CATransaction . begin ( )
92+ CATransaction . setDisableActions ( true )
93+ layer. disableActions ( for: " position " , " bounds " ) {
94+ workRan = true
95+ actionsCountDuringWork = layer. actions? . count
96+ layer. frame = CGRect ( x: 0 , y: 0 , width: 100 , height: 100 )
97+ }
98+ CATransaction . commit ( )
99+
100+ // the work runs
101+ expect ( workRan) == true
102+ // fast path: `actions` stays as the sentinel (count 1), not swapped to the 2-key disabling dictionary.
103+ expect ( actionsCountDuringWork) == 1
104+ // `actions` is left intact after the call.
105+ expect ( layer. actions? . count) == 1
106+ // no implicit animation is added because the transaction already suppressed it.
107+ expect ( layer. animationKeys ( ) ) == nil
108+ }
109+
71110 func test_disableAllActions_mainThread( ) throws {
72111 let frame = CGRect ( x: 0 , y: 0 , width: 50 , height: 50 )
73112 let layer = CALayer ( )
0 commit comments