From fe84664bc28bd0bc8dff246663dc358fe6aaaa4c Mon Sep 17 00:00:00 2001 From: onevcat Date: Wed, 26 Aug 2026 09:29:53 +0900 Subject: [PATCH] fix(repositories): drive reducer delays with an injected clock The GitHub-integration recovery loop, the delayed PR refresh, and the toast auto-dismiss slept on a real `ContinuousClock()`, so a TestStore could not control them. Under parallel CI the 15s recovery interval could elapse inside a test's window and emit an extra `refreshGithubIntegrationAvailability` -> `githubIntegrationAvailabilityUpdated` pair, failing `worktreeInfoEventRepositoryPullRequestRefreshQueuesWhileAvailabilityUnknown` with "Must handle 2 received actions before sending an action". It passed locally and in isolation, so it read as noise, but it is a real non-deterministic-clock defect. Inject `@Dependency(\.continuousClock)` and sleep on it at all three sites; the affected tests provide a `TestClock`, so the delays never fire unless advanced. Claude-Session: https://claude.ai/code/session_01AQ4X2bx8DnU74wwThhV9D8 --- .../Reducer/RepositoriesFeature+CoreReducer.swift | 2 +- .../RepositoriesFeature+GithubIntegration.swift | 4 ++-- .../Repositories/Reducer/RepositoriesFeature.swift | 1 + supacodeTests/RepositoriesFeatureTests.swift | 13 +++++++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/supacode/Features/Repositories/Reducer/RepositoriesFeature+CoreReducer.swift b/supacode/Features/Repositories/Reducer/RepositoriesFeature+CoreReducer.swift index a985b62a..8ca1e76b 100644 --- a/supacode/Features/Repositories/Reducer/RepositoriesFeature+CoreReducer.swift +++ b/supacode/Features/Repositories/Reducer/RepositoriesFeature+CoreReducer.swift @@ -902,7 +902,7 @@ extension RepositoriesFeature { return .cancel(id: CancelID.toastAutoDismiss) case .success, .warning: return .run { send in - try? await ContinuousClock().sleep(for: .seconds(3)) + try? await clock.sleep(for: .seconds(3)) await send(.dismissToast) } .cancellable(id: CancelID.toastAutoDismiss, cancelInFlight: true) diff --git a/supacode/Features/Repositories/Reducer/RepositoriesFeature+GithubIntegration.swift b/supacode/Features/Repositories/Reducer/RepositoriesFeature+GithubIntegration.swift index b34cea02..df979bae 100644 --- a/supacode/Features/Repositories/Reducer/RepositoriesFeature+GithubIntegration.swift +++ b/supacode/Features/Repositories/Reducer/RepositoriesFeature+GithubIntegration.swift @@ -71,7 +71,7 @@ extension RepositoriesFeature { let repositoryRootURL = worktree.repositoryRootURL let worktreeIDs = repository.worktrees.map(\.id) return .run { send in - try? await ContinuousClock().sleep(for: .seconds(2)) + try? await clock.sleep(for: .seconds(2)) await send( .worktreeInfoEvent( .repositoryPullRequestRefresh( @@ -181,7 +181,7 @@ extension RepositoriesFeature { clearAllPullRequestRefreshTracking(state: &state) return .run { send in while !Task.isCancelled { - try? await ContinuousClock().sleep(for: githubIntegrationRecoveryInterval) + try? await clock.sleep(for: githubIntegrationRecoveryInterval) guard !Task.isCancelled else { return } diff --git a/supacode/Features/Repositories/Reducer/RepositoriesFeature.swift b/supacode/Features/Repositories/Reducer/RepositoriesFeature.swift index 2f05bb51..36730d51 100644 --- a/supacode/Features/Repositories/Reducer/RepositoriesFeature.swift +++ b/supacode/Features/Repositories/Reducer/RepositoriesFeature.swift @@ -556,6 +556,7 @@ struct RepositoriesFeature { @Dependency(RepositoryPersistenceClient.self) var repositoryPersistence @Dependency(ShellClient.self) var shellClient @Dependency(\.date.now) var now + @Dependency(\.continuousClock) var clock @Dependency(BranchNameSuggestionClient.self) var branchNameSuggestionClient @Dependency(\.uuid) var uuid @Dependency(\.repositoryIconDetector) var repositoryIconDetector diff --git a/supacodeTests/RepositoriesFeatureTests.swift b/supacodeTests/RepositoriesFeatureTests.swift index 70f78bfa..d68d6cce 100644 --- a/supacodeTests/RepositoriesFeatureTests.swift +++ b/supacodeTests/RepositoriesFeatureTests.swift @@ -1752,6 +1752,8 @@ struct RepositoriesFeatureTests { initialState.workspaceCreationPrompt?.isCreating = true let store = TestStore(initialState: initialState) { RepositoriesFeature() + } withDependencies: { + $0.continuousClock = TestClock() } store.exhaustivity = .off @@ -1820,6 +1822,7 @@ struct RepositoriesFeatureTests { let store = TestStore(initialState: initialState) { RepositoriesFeature() } withDependencies: { + $0.continuousClock = TestClock() $0.date.now = Date(timeIntervalSince1970: 1_700_000_000) $0.shellClient.runLoginImpl = { _, _, _, _ in ShellOutput(stdout: "", stderr: "", exitCode: 0) } $0.repositoryPersistence.loadRepositoryEntries = { [] } @@ -5967,6 +5970,7 @@ struct RepositoriesFeatureTests { let store = TestStore(initialState: state) { RepositoriesFeature() } withDependencies: { + $0.continuousClock = TestClock() $0.githubIntegration.isAvailable = { true } $0.githubCLI.resolveRemoteInfo = { _ in upstreamRemoteInfo } $0.githubCLI.mergePullRequest = { _, _, number, _, _ in @@ -6021,6 +6025,7 @@ struct RepositoriesFeatureTests { let store = TestStore(initialState: state) { RepositoriesFeature() } withDependencies: { + $0.continuousClock = TestClock() $0.githubIntegration.isAvailable = { true } $0.githubCLI.resolveRemoteInfo = { _ in upstreamRemoteInfo } $0.githubCLI.mergePullRequest = { _, _, _, strategy, _ in @@ -6065,6 +6070,7 @@ struct RepositoriesFeatureTests { let store = TestStore(initialState: state) { RepositoriesFeature() } withDependencies: { + $0.continuousClock = TestClock() $0.githubIntegration.isAvailable = { true } $0.gitClient.remoteInfo = { root in #expect(root == URL(fileURLWithPath: repoRoot)) @@ -6100,6 +6106,7 @@ struct RepositoriesFeatureTests { let store = TestStore(initialState: fixture.state) { RepositoriesFeature() } withDependencies: { + $0.continuousClock = TestClock() $0.githubIntegration.isAvailable = { true } $0.gitClient.remoteInfo = { _ in Issue.record("git remoteInfo should not run when PR URL resolves") @@ -6133,6 +6140,7 @@ struct RepositoriesFeatureTests { let store = TestStore(initialState: fixture.state) { RepositoriesFeature() } withDependencies: { + $0.continuousClock = TestClock() $0.githubIntegration.isAvailable = { true } $0.gitClient.remoteInfo = { _ in Issue.record("git remoteInfo should not run when PR URL resolves") @@ -6166,6 +6174,7 @@ struct RepositoriesFeatureTests { let store = TestStore(initialState: fixture.state) { RepositoriesFeature() } withDependencies: { + $0.continuousClock = TestClock() $0.githubIntegration.isAvailable = { true } $0.gitClient.remoteInfo = { _ in Issue.record("git remoteInfo should not run when PR URL resolves") @@ -6272,6 +6281,7 @@ struct RepositoriesFeatureTests { let store = TestStore(initialState: state) { RepositoriesFeature() } withDependencies: { + $0.continuousClock = TestClock() $0.githubIntegration.isAvailable = { true } $0.githubCLI.resolveRemoteInfo = { _ in upstreamRemoteInfo } $0.githubCLI.closePullRequest = { _, _, number, _ in @@ -6479,6 +6489,7 @@ struct RepositoriesFeatureTests { let store = TestStore(initialState: makeState(repositories: [repository])) { RepositoriesFeature() } withDependencies: { + $0.continuousClock = TestClock() $0.githubIntegration.isAvailable = { false } $0.gitClient.remoteInfo = { _ in Issue.record("remoteInfo should not be requested when GitHub integration is unavailable") @@ -6614,6 +6625,8 @@ struct RepositoriesFeatureTests { ) let store = TestStore(initialState: initialState) { RepositoriesFeature() + } withDependencies: { + $0.continuousClock = TestClock() } await store.send(.githubIntegration(.githubIntegrationAvailabilityUpdated(false))) { -- 2.51.2