From 2f061d7408b36887482316715f976655d100f5f4 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Mon, 2 Nov 2026 09:04:09 -0800 Subject: [PATCH 1/2] Make status bar timer tests deterministic --- src/test/cloud/ui/statusBar.test.ts | 41 ++++++++++++++++------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/test/cloud/ui/statusBar.test.ts b/src/test/cloud/ui/statusBar.test.ts index c84b251..348e7dc 100644 --- a/src/test/cloud/ui/statusBar.test.ts +++ b/src/test/cloud/ui/statusBar.test.ts @@ -167,7 +167,7 @@ suite("cloud/ui/statusBar", () => { assert.ok(listenerStub.calledOnce) }) - test("debounces updates on active editor change", async () => { + test("debounces updates on active editor change", () => { const statusBarItem = mockStatusBarItem() const manager = new StatusBarManager( statusBarItem, @@ -175,9 +175,10 @@ suite("cloud/ui/statusBar", () => { () => null, ) - const getSessionStub = sinon - .stub(vscode.authentication, "getSession") - .resolves(undefined) + const clock = sinon.useFakeTimers({ + toFake: ["setTimeout", "clearTimeout"], + }) + const updateStub = sinon.stub(manager, "update").resolves() let editorChangeCallback: | ((e: vscode.TextEditor | undefined) => void) @@ -196,11 +197,12 @@ suite("cloud/ui/statusBar", () => { editorChangeCallback!(undefined) editorChangeCallback!(undefined) - // Wait for debounce timeout (100ms + buffer) - await new Promise((resolve) => setTimeout(resolve, 150)) - - // Only one update should have happened due to debouncing - assert.strictEqual(getSessionStub.callCount, 1) + // Only this manager should update, once the debounce delay has elapsed. + clock.tick(99) + assert.strictEqual(updateStub.callCount, 0) + clock.tick(1) + assert.strictEqual(updateStub.callCount, 1) + manager.dispose() }) }) @@ -225,7 +227,7 @@ suite("cloud/ui/statusBar", () => { assert.ok((statusBarItem.dispose as sinon.SinonStub).calledOnce) }) - test("clears pending timeout on dispose", async () => { + test("clears pending timeout on dispose", () => { const statusBarItem = mockStatusBarItem() const manager = new StatusBarManager( statusBarItem, @@ -233,9 +235,10 @@ suite("cloud/ui/statusBar", () => { () => null, ) - const getSessionStub = sinon - .stub(vscode.authentication, "getSession") - .resolves(undefined) + const clock = sinon.useFakeTimers({ + toFake: ["setTimeout", "clearTimeout"], + }) + const updateStub = sinon.stub(manager, "update").resolves() let editorChangeCallback: | ((e: vscode.TextEditor | undefined) => void) @@ -252,14 +255,14 @@ suite("cloud/ui/statusBar", () => { // Trigger an editor change to start the debounce timer editorChangeCallback!(undefined) - // Dispose before the timeout fires - manager.dispose() + assert.strictEqual(clock.countTimers(), 1) - // Wait to ensure timeout would have fired - await new Promise((resolve) => setTimeout(resolve, 150)) + // Dispose before the timeout fires. + manager.dispose() + assert.strictEqual(clock.countTimers(), 0) - // getSession should not have been called because timeout was cleared - assert.strictEqual(getSessionStub.callCount, 0) + clock.tick(150) + assert.strictEqual(updateStub.callCount, 0) }) }) }) From f0a9375491f919cd0932e1ccd1c71a75230a33f0 Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Mon, 2 Nov 2026 09:15:00 -0800 Subject: [PATCH 2/2] Test debounce resets and disposal through update behavior --- src/test/cloud/ui/statusBar.test.ts | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/test/cloud/ui/statusBar.test.ts b/src/test/cloud/ui/statusBar.test.ts index 348e7dc..780cadb 100644 --- a/src/test/cloud/ui/statusBar.test.ts +++ b/src/test/cloud/ui/statusBar.test.ts @@ -192,16 +192,24 @@ suite("cloud/ui/statusBar", () => { manager.show() - // Trigger multiple editor changes rapidly editorChangeCallback!(undefined) + clock.tick(60) editorChangeCallback!(undefined) + + // The first change's deadline passes, but the second change reset it. + clock.tick(60) + assert.strictEqual(updateStub.callCount, 0) editorChangeCallback!(undefined) - // Only this manager should update, once the debounce delay has elapsed. + // Wait until 100ms after the most recent change, not the first one. clock.tick(99) assert.strictEqual(updateStub.callCount, 0) clock.tick(1) assert.strictEqual(updateStub.callCount, 1) + + // No additional updates should follow once editor changes stop. + clock.tick(1000) + assert.strictEqual(updateStub.callCount, 1) manager.dispose() }) }) @@ -227,7 +235,7 @@ suite("cloud/ui/statusBar", () => { assert.ok((statusBarItem.dispose as sinon.SinonStub).calledOnce) }) - test("clears pending timeout on dispose", () => { + test("does not update after disposal with an editor change pending", () => { const statusBarItem = mockStatusBarItem() const manager = new StatusBarManager( statusBarItem, @@ -252,16 +260,13 @@ suite("cloud/ui/statusBar", () => { manager.show() - // Trigger an editor change to start the debounce timer editorChangeCallback!(undefined) + clock.tick(50) + assert.strictEqual(updateStub.callCount, 0) - assert.strictEqual(clock.countTimers(), 1) - - // Dispose before the timeout fires. + // Disposal must prevent the pending update, even after its deadline. manager.dispose() - assert.strictEqual(clock.countTimers(), 0) - - clock.tick(150) + clock.tick(1000) assert.strictEqual(updateStub.callCount, 0) }) })