test(composer): cover the forced source reset behaviourally, not by source text

The boundaries suite pinned the project switch by matching the literal
`handleRepoChange(nextRepoId, ...)` call, which the setup-choice refactor
renamed. The invariant it protected — returning from a folder target to a repo
with the same id must force the repo-scoped reset past handleRepoChange's
same-id early return — now runs against the real handleRepoChange, so a rename
cannot break it and a dropped flag cannot pass it.
This commit is contained in:
Merge Sim
2026-08-31 23:37:25 -07:00
parent 7d221d9d96
commit 74c3981de7
2 changed files with 101 additions and 6 deletions
@@ -5,6 +5,7 @@ import type { ExecutionHostId } from '../../../../shared/execution-host'
import type { Project, ProjectHostSetup } from '../../../../shared/project-types'
import type { Repo } from '../../../../shared/repo-types'
import { useProjectTargetActions } from './project-target-actions'
import { useTargetChangeActions } from './target-change-actions'
const PROJECT_ID = 'github:stablyai/orca'
@@ -114,3 +115,98 @@ describe('composer project switch with several ready setups (STA-6080)', () => {
expect(spies.setRepoId).not.toHaveBeenCalled()
})
})
/**
* The folder branch of the composer leaves `repoId` on the group's source repo, so switching back to
* a project whose setup uses that same repo id hits `handleRepoChange`'s same-id early return and
* would keep the folder's source state. `forceResetStartFrom: isProjectGroupTarget` is what forces
* the reset through. Exercised against the real `handleRepoChange`, not its source text.
*/
function renderProjectSwitchFromFolderTarget(isProjectGroupTarget: boolean) {
const spies = {
setBaseBranch: vi.fn(),
setLinkedIssue: vi.fn(),
setRepoId: vi.fn(),
setSparseEnabled: vi.fn(),
setStartFromResetHint: vi.fn()
}
const noop = vi.fn()
const shared = {
linkedWorkItem: null,
setBranchNameOverride: noop,
setBranchNameOverridePreservesNameEdits: noop,
setForkPushWarning: noop,
setLinkedGitLabIssue: noop,
setLinkedGitLabMR: noop,
setLinkedPR: noop,
setLinkedTaskSourceContext: noop,
setLinkedWorkItem: noop,
setProjectError: noop,
setPushTarget: noop,
setReuseEligibleBranch: noop,
setReuseSelectedBranch: noop,
setSelectedProjectHostSetupOverrideId: noop,
setSelectedProjectIdOverride: noop,
setSparseDirectories: noop,
setSparseSelectedPresetId: noop,
...spies
}
const { result } = renderHook(() => {
const { handleRepoChange } = useTargetChangeActions({
baseBranch: undefined,
branchAutoNameRef: { current: null },
decisions: { retargetGitHubPrStartPointSelection: (selection: unknown) => selection },
folderSourceRepos: [],
hostOptions: [],
projectHostSetupOptions: [],
// Why: the folder branch already parked the composer on this repo id, so the project switch
// resolves to the same value and only the forced reset can clear the folder's state.
repoId: 'orca-main',
selectedRepoProjectId: PROJECT_ID,
setCompareBaseRef: noop,
smartGitHubPrStartPointSelectionRef: { current: null },
...shared
} as unknown as Parameters<typeof useTargetChangeActions>[0])
return useProjectTargetActions({
actionableHostIds: new Set<ExecutionHostId>(['local']),
eligibleRepos: [makeRepo('orca-main', '/checkouts/main')],
handleRepoChange,
initialProjectGroupAppliedRef: { current: false },
isProjectGroupTarget,
projectGroups: [],
projectHostSetups: [makeSetup('setup-main', 'local', 'orca-main', '/checkouts/main')],
projects: [project],
repos: [],
selectedWorkspaceTarget: { status: 'unavailable', reason: 'project-has-no-ready-setup' },
workspaceHostScope: 'all',
setSelectedProjectGroupId: noop,
...shared
} as unknown as Parameters<typeof useProjectTargetActions>[0])
})
return { handleProjectChange: result.current.handleProjectChange, spies }
}
describe('returning from a folder target to a repo with the same id', () => {
it('forces the repo-scoped source reset', () => {
const { handleProjectChange, spies } = renderProjectSwitchFromFolderTarget(true)
handleProjectChange(PROJECT_ID)
expect(spies.setRepoId).toHaveBeenCalledWith('orca-main')
expect(spies.setSparseEnabled).toHaveBeenCalledWith(false)
expect(spies.setLinkedIssue).toHaveBeenCalledWith('')
expect(spies.setBaseBranch).toHaveBeenCalledWith(undefined)
expect(spies.setStartFromResetHint).toHaveBeenCalledWith(null)
})
it('leaves the source state alone when the composer was already on that repo', () => {
const { handleProjectChange, spies } = renderProjectSwitchFromFolderTarget(false)
handleProjectChange(PROJECT_ID)
expect(spies.setRepoId).toHaveBeenCalledWith('orca-main')
expect(spies.setSparseEnabled).not.toHaveBeenCalled()
expect(spies.setLinkedIssue).not.toHaveBeenCalled()
expect(spies.setBaseBranch).not.toHaveBeenCalled()
})
})
@@ -453,15 +453,14 @@ describe('useComposerState host-context boundaries', () => {
expect(section).toContain("branchAutoNameRef.current = ''")
})
it('forces repo-scoped source reset when returning from folder target to a repo with the same id', () => {
// The caller half — that a project switch passes `forceResetStartFrom: isProjectGroupTarget`, and
// that the reset actually runs — is covered behaviourally against the real `handleRepoChange` in
// composer-state/project-target-actions.test.ts ("returning from a folder target to a repo with
// the same id"), so it survives renaming the local that holds the resolved repo id.
it('keeps the same-id early return escapable', () => {
const handleRepoChange = COMPOSER_SOURCE.targetChange
expect(handleRepoChange).toContain('forceResetStartFrom?: boolean')
expect(handleRepoChange).toContain('value === repoId && !options.forceResetStartFrom')
const handleProjectChange = COMPOSER_SOURCE.projectTarget
expect(handleProjectChange).toContain(
'handleRepoChange(nextRepoId, { forceResetStartFrom: isProjectGroupTarget })'
)
})
it('keeps a Linear branch override when its workspace-scoped issue survives a repo change', () => {