diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cbee6a1..0831335 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,26 +7,15 @@ on: branches: [main] jobs: - lint: + ci: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 - - run: bun install + with: + cache: bun + - run: bun ci - run: bun run lint - - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 - - run: bun install + - run: bun run typecheck - run: bun run compile - - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 - - run: bun install - run: xvfb-run -a bun run test:coverage diff --git a/.husky/pre-commit b/.husky/pre-commit index ea5a55b..01cc0f1 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +1,2 @@ +bun run typecheck bunx lint-staged diff --git a/package.json b/package.json index c313eb4..0bee86f 100644 --- a/package.json +++ b/package.json @@ -277,6 +277,7 @@ "test": "bun run compile && vscode-test", "test:coverage": "bash scripts/test-coverage.sh", "test:web": "bun run compile && bunx @vscode/test-web --extensionDevelopmentPath=. --browserType=none", + "typecheck": "tsc --noEmit", "prepare": "husky" }, "devDependencies": { diff --git a/src/test/cloud/controller.test.ts b/src/test/cloud/controller.test.ts index f49af2a..6a6ee13 100644 --- a/src/test/cloud/controller.test.ts +++ b/src/test/cloud/controller.test.ts @@ -415,8 +415,8 @@ suite("cloud/cloudController", () => { await deps.controller.initialize() - // Explicitly wait for status bar update since it's fire-and-forget - await deps.controller["statusBarManager"].update() + // Trigger status bar update via refreshAll + await deps.controller.refreshAll() assert.strictEqual(deps.statusBar.text, "$(warning) FastAPI Cloud") assert.ok(warnStub.calledOnce) @@ -488,7 +488,8 @@ suite("cloud/cloudController", () => { newWorkspace, ), ) - assert.strictEqual(deps.controller["workspaceStates"].size, 1) + // Verify state was set by checking that getApp was called (indicates refresh happened) + assert.ok((deps.apiService.getApp as sinon.SinonStub).calledOnce) dispose(deps) }) @@ -515,11 +516,33 @@ suite("cloud/cloudController", () => { sinon.stub(deps.apiService, "getApp").resolves(testApp) sinon.stub(deps.apiService, "getTeam").resolves(testTeam) + // Set up active editor to point to workspace + const activeEditor = { + document: { uri: vscode.Uri.file("/tmp/workspace/file.py") }, + } + Object.defineProperty(vscode.window, "activeTextEditor", { + value: activeEditor, + configurable: true, + }) + sinon + .stub(vscode.workspace, "getWorkspaceFolder") + .returns(workspaceFolder) + await deps.controller.initialize() - assert.strictEqual(deps.controller["workspaceStates"].size, 1) + + assert.strictEqual(deps.statusBar.text, "$(cloud) test-app") deps.controller.removeWorkspaceFolder(workspace) - assert.strictEqual(deps.controller["workspaceStates"].size, 0) + + // Verify state was deleted by showing menu - should show setup menu (not_configured) + const quickPickStub = sinon + .stub(vscode.window, "showQuickPick") + .resolves(undefined) + await deps.controller.showMenu() + + const items = quickPickStub.firstCall.args[0] as any[] + assert.ok(items.some((i: any) => i.id === "link")) + assert.ok(items.some((i: any) => i.id === "create")) dispose(deps) }) @@ -554,7 +577,11 @@ suite("cloud/cloudController", () => { await deps.controller.refreshAll() - assert.strictEqual(deps.controller["workspaceStates"].size, 2) + // Verify both workspaces were refreshed by checking getApp was called twice + assert.strictEqual( + (deps.apiService.getApp as sinon.SinonStub).callCount, + 2, + ) dispose(deps) }) @@ -609,6 +636,17 @@ suite("cloud/cloudController", () => { configurable: true, }) + const activeEditor = { + document: { uri: vscode.Uri.file("/tmp/workspace/file.py") }, + } + Object.defineProperty(vscode.window, "activeTextEditor", { + value: activeEditor, + configurable: true, + }) + sinon + .stub(vscode.workspace, "getWorkspaceFolder") + .returns(workspaceFolder) + sinon .stub(vscode.authentication, "getSession") .rejects(new Error("Auth error")) @@ -616,8 +654,11 @@ suite("cloud/cloudController", () => { await deps.controller.initialize() - const state = deps.controller["getState"](workspace) - assert.strictEqual(state.status, "not_configured") + // Verify state is not_configured by checking status bar shows sign-in + assert.strictEqual( + deps.statusBar.text, + "$(cloud) Sign into FastAPI Cloud", + ) dispose(deps) }) @@ -632,6 +673,17 @@ suite("cloud/cloudController", () => { configurable: true, }) + const activeEditor = { + document: { uri: vscode.Uri.file("/tmp/workspace/file.py") }, + } + Object.defineProperty(vscode.window, "activeTextEditor", { + value: activeEditor, + configurable: true, + }) + sinon + .stub(vscode.workspace, "getWorkspaceFolder") + .returns(workspaceFolder) + sinon .stub(vscode.authentication, "getSession") .resolves(mockSession as any) @@ -642,8 +694,8 @@ suite("cloud/cloudController", () => { await deps.controller.initialize() - const state = deps.controller["getState"](workspace) - assert.strictEqual(state.status, "error") + // Verify state is error by checking status bar shows setup (error state shows setup) + assert.strictEqual(deps.statusBar.text, "$(cloud) Set up FastAPI Cloud") dispose(deps) }) @@ -679,13 +731,7 @@ suite("cloud/cloudController", () => { await deps.controller.initialize() assert.strictEqual(warnStub.callCount, 1) - // Manually mark warning as shown - deps.controller["setState"](workspace, { - status: "not_found", - warningShown: true, - }) - - // Second refresh - should NOT show warning again + // Second refresh - warning was dismissed (resolved undefined), so should NOT show again await deps.controller.refresh(workspace) assert.strictEqual(warnStub.callCount, 1) // Still 1, not 2 @@ -753,6 +799,17 @@ suite("cloud/cloudController", () => { configurable: true, }) + const activeEditor = { + document: { uri: vscode.Uri.file("/tmp/workspace/file.py") }, + } + Object.defineProperty(vscode.window, "activeTextEditor", { + value: activeEditor, + configurable: true, + }) + sinon + .stub(vscode.workspace, "getWorkspaceFolder") + .returns(workspaceFolder) + sinon .stub(vscode.authentication, "getSession") .resolves(mockSession as any) @@ -772,8 +829,8 @@ suite("cloud/cloudController", () => { await deps.controller.createAndLinkProject(workspace) - const state = deps.controller["getState"](workspace) - assert.strictEqual(state.status, "linked") + // Verify state is linked by checking status bar shows app slug + assert.strictEqual(deps.statusBar.text, "$(cloud) test-app") dispose(deps) }) @@ -859,7 +916,7 @@ suite("cloud/cloudController", () => { .withArgs(editor1.document.uri) .returns(workspaceFolder1) - await deps.controller["statusBarManager"].update() + await deps.controller.refreshAll() assert.strictEqual(deps.statusBar.text, "$(cloud) test-app") // Switch to workspace2 file @@ -874,7 +931,7 @@ suite("cloud/cloudController", () => { .withArgs(editor2.document.uri) .returns(workspaceFolder2) - await deps.controller["statusBarManager"].update() + await deps.controller.refreshAll() assert.strictEqual(deps.statusBar.text, "$(cloud) test-app-2") dispose(deps) @@ -925,7 +982,7 @@ suite("cloud/cloudController", () => { .withArgs(editor1.document.uri) .returns(workspaceFolder1) - await deps.controller["statusBarManager"].update() + await deps.controller.refreshAll() assert.strictEqual(deps.statusBar.text, "$(cloud) test-app") // workspace2 file shows setup @@ -940,7 +997,7 @@ suite("cloud/cloudController", () => { .withArgs(editor2.document.uri) .returns(workspaceFolder2) - await deps.controller["statusBarManager"].update() + await deps.controller.refreshAll() assert.strictEqual(deps.statusBar.text, "$(cloud) Set up FastAPI Cloud") dispose(deps) @@ -1166,6 +1223,18 @@ suite("cloud/cloudController", () => { configurable: true, }) + // Set up active editor + const editor1 = { + document: { uri: vscode.Uri.file("/tmp/workspace1/file.py") }, + } + Object.defineProperty(vscode.window, "activeTextEditor", { + value: editor1, + configurable: true, + }) + sinon + .stub(vscode.workspace, "getWorkspaceFolder") + .returns(workspaceFolder1) + const getSessionStub = sinon.stub(vscode.authentication, "getSession") getSessionStub.resolves(mockSession as any) @@ -1178,7 +1247,8 @@ suite("cloud/cloudController", () => { await deps.controller.initialize() - assert.strictEqual(deps.controller["workspaceStates"].size, 2) + // Verify workspaces are linked by checking status bar shows app + assert.strictEqual(deps.statusBar.text, "$(cloud) test-app") sinon .stub(vscode.window, "showWarningMessage") @@ -1190,8 +1260,11 @@ suite("cloud/cloudController", () => { await deps.controller.signOut() - // All workspace states should be cleared - assert.strictEqual(deps.controller["workspaceStates"].size, 0) + // All workspace states should be cleared - status bar shows sign-in + assert.strictEqual( + deps.statusBar.text, + "$(cloud) Sign into FastAPI Cloud", + ) dispose(deps) })