| ... | ... | @@ -272,11 +272,12 @@ describe("git integration", () => { |
| 272 | 272 | |
| 273 | 273 | await checkoutGitBranch({ cwd: source.path, branch: featureBranch }); |
| 274 | 274 | const core = new GitCoreService(); |
| 275 | | const details = await core.statusDetails(source.path); |
| 276 | | |
| 277 | | expect(details.branch).toBe(featureBranch); |
| 278 | | expect(details.aheadCount).toBe(0); |
| 279 | | expect(details.behindCount).toBe(1); |
| 275 | await vi.waitFor(async () => { |
| 276 | const details = await core.statusDetails(source.path); |
| 277 | expect(details.branch).toBe(featureBranch); |
| 278 | expect(details.aheadCount).toBe(0); |
| 279 | expect(details.behindCount).toBe(1); |
| 280 | }); |
| 280 | 281 | }); |
| 281 | 282 | |
| 282 | 283 | it("keeps checkout successful when upstream refresh fails", async () => { |
| ... | ... | @@ -320,7 +321,9 @@ describe("git integration", () => { |
| 320 | 321 | await expect(core.checkoutBranch({ cwd: source.path, branch: featureBranch })).resolves.toBe( |
| 321 | 322 | undefined, |
| 322 | 323 | ); |
| 323 | | expect(core.refreshFetchAttempts).toBe(1); |
| 324 | await vi.waitFor(() => { |
| 325 | expect(core.refreshFetchAttempts).toBe(1); |
| 326 | }); |
| 324 | 327 | expect(await git(source.path, "branch --show-current")).toBe(featureBranch); |
| 325 | 328 | }); |
| 326 | 329 | |
| ... | ... | @@ -362,6 +365,9 @@ describe("git integration", () => { |
| 362 | 365 | |
| 363 | 366 | const core = new FetchRecordingGitCoreService(); |
| 364 | 367 | await core.checkoutBranch({ cwd: source.path, branch: featureBranch }); |
| 368 | await vi.waitFor(() => { |
| 369 | expect(core.fetchArgs).not.toBeNull(); |
| 370 | }); |
| 365 | 371 | |
| 366 | 372 | expect(await git(source.path, "branch --show-current")).toBe(featureBranch); |
| 367 | 373 | expect(core.fetchArgs).toEqual([ |
| ... | ... | @@ -373,6 +379,67 @@ describe("git integration", () => { |
| 373 | 379 | ]); |
| 374 | 380 | }); |
| 375 | 381 | |
| 382 | it("returns checkout result before background upstream refresh completes", async () => { |
| 383 | await using remote = await makeTmpDir(); |
| 384 | await using source = await makeTmpDir(); |
| 385 | await git(remote.path, "init --bare"); |
| 386 | |
| 387 | await initRepoWithCommit(source.path); |
| 388 | const defaultBranch = (await listGitBranches({ cwd: source.path })).branches.find( |
| 389 | (branch) => branch.current, |
| 390 | )!.name; |
| 391 | await git(source.path, `remote add origin ${JSON.stringify(remote.path)}`); |
| 392 | await git(source.path, `push -u origin ${defaultBranch}`); |
| 393 | |
| 394 | const featureBranch = "feature/background-refresh"; |
| 395 | await git(source.path, `checkout -b ${featureBranch}`); |
| 396 | await writeFile(path.join(source.path, "feature.txt"), "feature base\n"); |
| 397 | await git(source.path, "add feature.txt"); |
| 398 | await git(source.path, "commit -m 'feature base'"); |
| 399 | await git(source.path, `push -u origin ${featureBranch}`); |
| 400 | await git(source.path, `checkout ${defaultBranch}`); |
| 401 | |
| 402 | class SlowRefreshGitCoreService extends GitCoreService { |
| 403 | fetchStarted = false; |
| 404 | private readonly waitForReleasePromise: Promise<void>; |
| 405 | private releaseWait: () => void = () => undefined; |
| 406 | |
| 407 | constructor() { |
| 408 | super(); |
| 409 | this.waitForReleasePromise = new Promise<void>((resolve) => { |
| 410 | this.releaseWait = resolve; |
| 411 | }); |
| 412 | } |
| 413 | |
| 414 | releaseFetch(): void { |
| 415 | this.releaseWait(); |
| 416 | } |
| 417 | |
| 418 | override async git( |
| 419 | cwd: string, |
| 420 | args: readonly string[], |
| 421 | allowNonZeroExit = false, |
| 422 | ): Promise<void> { |
| 423 | if (args[0] === "fetch") { |
| 424 | this.fetchStarted = true; |
| 425 | await this.waitForReleasePromise; |
| 426 | return; |
| 427 | } |
| 428 | await super.git(cwd, args, allowNonZeroExit); |
| 429 | } |
| 430 | } |
| 431 | |
| 432 | const core = new SlowRefreshGitCoreService(); |
| 433 | await expect(core.checkoutBranch({ cwd: source.path, branch: featureBranch })).resolves.toBe( |
| 434 | undefined, |
| 435 | ); |
| 436 | await vi.waitFor(() => { |
| 437 | expect(core.fetchStarted).toBe(true); |
| 438 | }); |
| 439 | expect(await git(source.path, "branch --show-current")).toBe(featureBranch); |
| 440 | core.releaseFetch(); |
| 441 | }); |
| 442 | |
| 376 | 443 | it("throws when branch does not exist", async () => { |
| 377 | 444 | await using tmp = await makeTmpDir(); |
| 378 | 445 | await initRepoWithCommit(tmp.path); |