From ec0bec84ed196f238eb26ab80819379620abcf21 Mon Sep 17 00:00:00 2001 From: Yishan Date: Sun, 8 Mar 2026 22:41:48 -0400 Subject: [PATCH 1/3] fix(cli): gate free-trial status polling by model --- .../cli/src/ui/FreeTrialStatus.test.tsx | 66 +++++++++++++++++++ extensions/cli/src/ui/FreeTrialStatus.tsx | 57 +++++++++------- 2 files changed, 100 insertions(+), 23 deletions(-) create mode 100644 extensions/cli/src/ui/FreeTrialStatus.test.tsx diff --git a/extensions/cli/src/ui/FreeTrialStatus.test.tsx b/extensions/cli/src/ui/FreeTrialStatus.test.tsx new file mode 100644 index 00000000000..9a1d1681df6 --- /dev/null +++ b/extensions/cli/src/ui/FreeTrialStatus.test.tsx @@ -0,0 +1,66 @@ +import { render } from "ink-testing-library"; +import React from "react"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { FreeTrialStatus } from "./FreeTrialStatus.js"; + +describe("FreeTrialStatus", () => { + const originalNodeEnv = process.env.NODE_ENV; + + afterEach(() => { + process.env.NODE_ENV = originalNodeEnv; + vi.useRealTimers(); + vi.clearAllMocks(); + }); + + it("does not fetch or poll for non-free-trial models", async () => { + vi.useFakeTimers(); + const getFreeTrialStatus = vi.fn().mockResolvedValue({ + optedInToFreeTrial: true, + chatCount: 1, + chatLimit: 10, + }); + + render( + , + ); + + await vi.runAllTimersAsync(); + + expect(getFreeTrialStatus).not.toHaveBeenCalled(); + }); + + it("fetches immediately and polls every five seconds for free-trial models", async () => { + vi.useFakeTimers(); + process.env.NODE_ENV = "development"; + const getFreeTrialStatus = vi.fn().mockResolvedValue({ + optedInToFreeTrial: true, + chatCount: 1, + chatLimit: 10, + }); + + render( + , + ); + + await Promise.resolve(); + await Promise.resolve(); + expect(getFreeTrialStatus).toHaveBeenCalledTimes(1); + + await vi.advanceTimersByTimeAsync(5000); + expect(getFreeTrialStatus).toHaveBeenCalledTimes(2); + + await vi.advanceTimersByTimeAsync(10000); + expect(getFreeTrialStatus).toHaveBeenCalledTimes(4); + }); +}); diff --git a/extensions/cli/src/ui/FreeTrialStatus.tsx b/extensions/cli/src/ui/FreeTrialStatus.tsx index 5a2c7c78d0b..422fe7c1c49 100644 --- a/extensions/cli/src/ui/FreeTrialStatus.tsx +++ b/extensions/cli/src/ui/FreeTrialStatus.tsx @@ -28,39 +28,50 @@ const FreeTrialStatus: React.FC = ({ null, ); const [loading, setLoading] = useState(true); + const shouldFetchStatus = !!apiClient && isModelUsingFreeTrial(model); - const fetchStatus = async () => { - try { - if (!apiClient) { - setStatus(null); - setLoading(false); - return; - } - - const response = await apiClient.getFreeTrialStatus(); - setStatus(response); - setLoading(false); - } catch { - // Silently handle errors - component returns null if no status + useEffect(() => { + if (!shouldFetchStatus) { setStatus(null); setLoading(false); + return; } - }; - useEffect(() => { - // Initial fetch - fetchStatus(); + let isMounted = true; + + const fetchStatus = async () => { + try { + const response = await apiClient.getFreeTrialStatus(); + if (!isMounted) { + return; + } + setStatus(response); + setLoading(false); + } catch { + if (!isMounted) { + return; + } + setStatus(null); + setLoading(false); + } + }; + + setLoading(true); + void fetchStatus(); - // Don't poll in test environment if (process.env.NODE_ENV === "test") { - return; + return () => { + isMounted = false; + }; } - // Poll every 5 seconds const interval = setInterval(fetchStatus, 5000); - return () => clearInterval(interval); - }, []); + return () => { + isMounted = false; + clearInterval(interval); + }; + }, [apiClient, shouldFetchStatus]); // Check if user has maxed out their free trial and notify parent useEffect(() => { @@ -89,7 +100,7 @@ const FreeTrialStatus: React.FC = ({ loading || !status || !status.optedInToFreeTrial || - !isModelUsingFreeTrial(model) + !shouldFetchStatus ) { return null; } From 05a46dc2d2cf8c78f8a44008506175052fffe469 Mon Sep 17 00:00:00 2001 From: Yishan Date: Sun, 8 Mar 2026 22:59:38 -0400 Subject: [PATCH 2/3] style(cli): format free-trial status changes --- extensions/cli/src/ui/FreeTrialStatus.test.tsx | 12 +++++++----- extensions/cli/src/ui/FreeTrialStatus.tsx | 7 +------ 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/extensions/cli/src/ui/FreeTrialStatus.test.tsx b/extensions/cli/src/ui/FreeTrialStatus.test.tsx index 9a1d1681df6..7b76ae82a02 100644 --- a/extensions/cli/src/ui/FreeTrialStatus.test.tsx +++ b/extensions/cli/src/ui/FreeTrialStatus.test.tsx @@ -45,11 +45,13 @@ describe("FreeTrialStatus", () => { render( , ); diff --git a/extensions/cli/src/ui/FreeTrialStatus.tsx b/extensions/cli/src/ui/FreeTrialStatus.tsx index 422fe7c1c49..a275b0820bf 100644 --- a/extensions/cli/src/ui/FreeTrialStatus.tsx +++ b/extensions/cli/src/ui/FreeTrialStatus.tsx @@ -96,12 +96,7 @@ const FreeTrialStatus: React.FC = ({ }, [status, loading, onTransitionStateChange, model]); // Don't render anything while loading or if no status - if ( - loading || - !status || - !status.optedInToFreeTrial || - !shouldFetchStatus - ) { + if (loading || !status || !status.optedInToFreeTrial || !shouldFetchStatus) { return null; } From e5c8a67a10a65e4e13ac5f942d360c755b3d7418 Mon Sep 17 00:00:00 2001 From: Yishan Date: Sun, 8 Mar 2026 23:12:59 -0400 Subject: [PATCH 3/3] fix(vscode): skip remote marketplace installs when ssh tests are disabled --- .../e2e/install-marketplace-extensions.mjs | 34 +++++++++++++++++++ extensions/vscode/package.json | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 extensions/vscode/e2e/install-marketplace-extensions.mjs diff --git a/extensions/vscode/e2e/install-marketplace-extensions.mjs b/extensions/vscode/e2e/install-marketplace-extensions.mjs new file mode 100644 index 00000000000..dbb3b05aac2 --- /dev/null +++ b/extensions/vscode/e2e/install-marketplace-extensions.mjs @@ -0,0 +1,34 @@ +import { execFileSync } from "node:child_process"; + +const extensionsDir = "./e2e/.test-extensions"; +const storageDir = "./e2e/storage"; +const remoteExtensions = [ + "ms-vscode-remote.remote-ssh", + "ms-vscode-remote.remote-containers", + "ms-vscode-remote.remote-wsl", +]; + +if (process.env.IGNORE_SSH_TESTS === "true") { + console.log( + "Skipping Remote-* marketplace extension installs because IGNORE_SSH_TESTS=true.", + ); + process.exit(0); +} + +const extestCommand = process.platform === "win32" ? "extest.cmd" : "extest"; + +for (const extensionId of remoteExtensions) { + console.log(`Installing ${extensionId} from the VS Code marketplace...`); + execFileSync( + extestCommand, + [ + "install-from-marketplace", + extensionId, + "--extensions_dir", + extensionsDir, + "--storage", + storageDir, + ], + { stdio: "inherit" }, + ); +} diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index cfe48996f18..e13bc1f3ad1 100644 --- a/extensions/vscode/package.json +++ b/extensions/vscode/package.json @@ -685,7 +685,7 @@ "e2e:sign-vscode": "codesign --entitlements entitlements.plist --deep --force -s - './e2e/storage/Visual Studio Code.app'", "e2e:copy-vsix": "chmod +x ./e2e/get-latest-vsix.sh && bash ./e2e/get-latest-vsix.sh", "e2e:install-vsix": "extest install-vsix -f ./e2e/vsix/continue.vsix --extensions_dir ./e2e/.test-extensions --storage ./e2e/storage", - "e2e:install-extensions": "extest install-from-marketplace ms-vscode-remote.remote-ssh --extensions_dir ./e2e/.test-extensions --storage ./e2e/storage && extest install-from-marketplace ms-vscode-remote.remote-containers --extensions_dir ./e2e/.test-extensions --storage ./e2e/storage && extest install-from-marketplace ms-vscode-remote.remote-wsl --extensions_dir ./e2e/.test-extensions --storage ./e2e/storage", + "e2e:install-extensions": "node ./e2e/install-marketplace-extensions.mjs", "e2e:test": "NODE_ENV=e2e extest run-tests ${TEST_FILE:-'./e2e/_output/tests/*.test.js'} --code_settings settings.json --extensions_dir ./e2e/.test-extensions --storage ./e2e/storage", "e2e:clean": "rm -rf ./e2e/_output ./e2e/storage", "e2e:all": "npm run e2e:build && npm run e2e:compile && npm run e2e:create-storage && npm run e2e:get-chromedriver && npm run e2e:get-vscode && npm run e2e:sign-vscode && npm run e2e:copy-vsix && npm run e2e:install-vsix && npm run e2e:install-extensions && CONTINUE_GLOBAL_DIR=e2e/test-continue npm run e2e:test && npm run e2e:clean",