| @@ -272,11 +272,12 @@ describe("git integration", () => { · 271 unmodified lines |
| 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", () => { · 37 unmodified lines |
| 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", () => { · 35 unmodified lines |
| 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", () => { · 5 unmodified lines |
| 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); |