Skip to content

Commit 60e0e1f

Browse files
muukiiclaude
andcommitted
Simplify guards to match RemovingTransitionContext pattern and fix test
- Remove hasNotifiedCompletion flag, use isInvalidated guard instead - Use XCTestExpectation for async batch transition test (crossDissolve) - Place stack in UIWindow so animation completes properly Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9b13455 commit 60e0e1f

2 files changed

Lines changed: 14 additions & 19 deletions

File tree

Sources/FluidStack/Transition/BatchRemovingTransitionContext.swift

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ public final class BatchRemovingTransitionContext: TransitionContext {
3636

3737
private let onCompleted: (BatchRemovingTransitionContext) -> Void
3838
private var childContexts: [ChildContext] = []
39-
private var hasNotifiedCompletion: Bool = false
4039
private var callbacks: [(CompletionEvent) -> Void] = []
4140

4241
init(
@@ -92,9 +91,8 @@ public final class BatchRemovingTransitionContext: TransitionContext {
9291
/// Triggers ``addCompletionEventHandler(_:)`` with ``TransitionContext/CompletionEvent/interrupted``
9392
override func invalidate() {
9493
assert(Thread.isMainThread)
94+
guard isInvalidated == false else { return }
9595
isInvalidated = true
96-
guard hasNotifiedCompletion == false else { return }
97-
hasNotifiedCompletion = true
9896
callbacks.forEach { $0(.interrupted) }
9997
}
10098

@@ -110,9 +108,6 @@ public final class BatchRemovingTransitionContext: TransitionContext {
110108
Triggers ``addCompletionEventHandler(_:)`` with ``TransitionContext/CompletionEvent/succeeded``
111109
*/
112110
func transitionSucceeded() {
113-
assert(Thread.isMainThread)
114-
guard hasNotifiedCompletion == false else { return }
115-
hasNotifiedCompletion = true
116111
callbacks.forEach { $0(.succeeded) }
117112
}
118113
}

Tests/FluidStackTests/FluidStackControllerTests.swift

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -566,13 +566,14 @@ final class FluidStackControllerTests: XCTestCase {
566566
}
567567

568568
/// Tests that batch removing completion is called exactly once.
569-
/// Previously, when multiple StackingPlatterViews shared the same
570-
/// BatchRemovingTransitionContext, each swapTransitionContext call
571-
/// would fire invalidate() on the shared context, causing completion
572-
/// to be called N times.
569+
/// When fluidPop triggers batch removing (VC is not on top),
570+
/// the completion must fire exactly once after the batch transition completes.
573571
func testBatchRemovingCompletionCalledOnce() {
574572

573+
let window = UIWindow()
575574
let stack = FluidStackController()
575+
window.rootViewController = stack
576+
window.makeKeyAndVisible()
576577

577578
let vc1 = UIViewController()
578579
let vc2 = UIViewController()
@@ -586,17 +587,16 @@ final class FluidStackControllerTests: XCTestCase {
586587

587588
XCTAssertEqual(stack.stackingViewControllers.count, 4)
588589

590+
let exp = expectation(description: "completion called")
591+
exp.assertForOverFulfill = true
589592
var completionCallCount = 0
590593

591-
// Remove vc2, which is not on top -> triggers batch removing of [vc2, vc3, vc4]
592-
stack.removeViewController(
593-
vc2,
594-
transition: nil,
595-
transitionForBatch: .disabled,
596-
completion: { event in
597-
completionCallCount += 1
598-
}
599-
)
594+
vc2.fluidPop { _ in
595+
completionCallCount += 1
596+
exp.fulfill()
597+
}
598+
599+
wait(for: [exp], timeout: 2)
600600

601601
XCTAssertEqual(stack.stackingViewControllers.count, 1)
602602
XCTAssertEqual(completionCallCount, 1, "Completion should be called exactly once")

0 commit comments

Comments
 (0)