diff --git a/packages/cli/.prettierignore b/packages/cli/.prettierignore index dd83f1ce..1c21ceff 100644 --- a/packages/cli/.prettierignore +++ b/packages/cli/.prettierignore @@ -1,3 +1,4 @@ dist/ node_modules/ .claude/ +CHANGELOG.md diff --git a/packages/core/.prettierignore b/packages/core/.prettierignore index 1eae0cf6..06a1096d 100644 --- a/packages/core/.prettierignore +++ b/packages/core/.prettierignore @@ -1,2 +1,3 @@ dist/ node_modules/ +CHANGELOG.md diff --git a/packages/core/__tests__/integrations/mcp-disconnect.test.ts b/packages/core/__tests__/integrations/mcp-disconnect.test.ts index 60c2d3b5..613fa1c4 100644 --- a/packages/core/__tests__/integrations/mcp-disconnect.test.ts +++ b/packages/core/__tests__/integrations/mcp-disconnect.test.ts @@ -2,6 +2,7 @@ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { createProjectDir, + isolateHome, writeClaudeSettings, writeCodexConfig, writeCursorMcpConfig, @@ -90,7 +91,7 @@ describe('cursor disconnectMcpServer', () => { it('removes the server from global config and CLI permissions', async () => { using project = createProjectDir('empty'); using home = createProjectDir('empty'); - vi.stubEnv('HOME', home.path); + using _home = isolateHome(home.path); writeCursorMcpConfig(home.path, { mcpServers: { 'confidence-flags': { type: 'http', url: 'https://example.com' }, @@ -131,7 +132,7 @@ describe('codex disconnectMcpServer', () => { it('removes the server section from project and global configs', async () => { using project = createProjectDir('empty'); - vi.stubEnv('HOME', project.path); + using _home = isolateHome(project.path); const toml = [ '[mcp_servers.confidence-flags]', 'url = "https://example.com"', @@ -152,7 +153,7 @@ describe('codex disconnectMcpServer', () => { it('does not fail when config files do not exist', async () => { using project = createProjectDir('empty'); - vi.stubEnv('HOME', project.path); + using _home = isolateHome(project.path); const sut = await loadDisconnect(); await expect( diff --git a/packages/quickstart/.prettierignore b/packages/quickstart/.prettierignore index 1eae0cf6..06a1096d 100644 --- a/packages/quickstart/.prettierignore +++ b/packages/quickstart/.prettierignore @@ -1,2 +1,3 @@ dist/ node_modules/ +CHANGELOG.md diff --git a/packages/quickstart/__tests__/ui/screens/ConnectToolsScreen.auth.test.tsx b/packages/quickstart/__tests__/ui/screens/ConnectToolsScreen.auth.test.tsx index 22590f08..6bb69120 100644 --- a/packages/quickstart/__tests__/ui/screens/ConnectToolsScreen.auth.test.tsx +++ b/packages/quickstart/__tests__/ui/screens/ConnectToolsScreen.auth.test.tsx @@ -3,6 +3,7 @@ import { act, renderScreen, createProjectDir, + isolateHome, prepareAuthTokens, waitFor, buildExpiredJwt, @@ -37,9 +38,10 @@ describe('ConnectToolsScreen', () => { http.post('https://mcp.confidence.dev/mcp/docs', () => HttpResponse.error()), ); + using project = createProjectDir(); + using _home = isolateHome(project.path); using _auth = prepareAuthTokens('none'); using _pref = createMcpPreference('connected'); - using project = createProjectDir(); // Act using sut = renderScreen(, { @@ -175,8 +177,9 @@ describe('ConnectToolsScreen', () => { ), ); - using _auth = prepareAuthTokens('none'); using project = createProjectDir(); + using _home = isolateHome(project.path); + using _auth = prepareAuthTokens('none'); using sut = renderScreen(, { screen: ScreenId.ConnectTools, @@ -203,7 +206,7 @@ type SeedMcpOpts = { }; function seedMcpRegistration(projectDir: string, opts?: SeedMcpOpts) { - vi.stubEnv('HOME', projectDir); + const home = isolateHome(projectDir); const headers = opts?.token ? { Authorization: `Bearer ${opts.token}` } : undefined; const servers = { @@ -223,7 +226,7 @@ function seedMcpRegistration(projectDir: string, opts?: SeedMcpOpts) { return { [Symbol.dispose]() { - vi.unstubAllEnvs(); + home[Symbol.dispose](); }, }; } diff --git a/packages/quickstart/__tests__/ui/screens/ConnectToolsScreen.test.tsx b/packages/quickstart/__tests__/ui/screens/ConnectToolsScreen.test.tsx index 226e330e..2cd756a2 100644 --- a/packages/quickstart/__tests__/ui/screens/ConnectToolsScreen.test.tsx +++ b/packages/quickstart/__tests__/ui/screens/ConnectToolsScreen.test.tsx @@ -4,6 +4,7 @@ import { renderScreen, renderApp, createProjectDir, + isolateHome, prepareAuthTokens, ENTER, ARROW_DOWN, @@ -20,8 +21,9 @@ vi.mock('../../../../core/src/exec/exec.js', () => ({ describe('ConnectToolsScreen', () => { it('renders title', async () => { - using _auth = prepareAuthTokens('none'); using project = createProjectDir(); + using _home = isolateHome(project.path); + using _auth = prepareAuthTokens('none'); using sut = renderScreen(, { screen: ScreenId.ConnectTools, dir: project.path, @@ -32,8 +34,9 @@ describe('ConnectToolsScreen', () => { }); it('shows tool list after detection', async () => { - using _auth = prepareAuthTokens('none'); using project = createProjectDir(); + using _home = isolateHome(project.path); + using _auth = prepareAuthTokens('none'); using sut = renderScreen(, { screen: ScreenId.ConnectTools, dir: project.path, @@ -45,8 +48,9 @@ describe('ConnectToolsScreen', () => { }); it('shows connect options', async () => { - using _auth = prepareAuthTokens('none'); using project = createProjectDir(); + using _home = isolateHome(project.path); + using _auth = prepareAuthTokens('none'); using sut = renderScreen(, { screen: ScreenId.ConnectTools, dir: project.path, @@ -58,8 +62,9 @@ describe('ConnectToolsScreen', () => { describe('when connection succeeds', () => { it('connects and shows success message', async () => { - using _auth = prepareAuthTokens('none'); using project = createProjectDir(); + using _home = isolateHome(project.path); + using _auth = prepareAuthTokens('none'); using sut = renderScreen(, { screen: ScreenId.ConnectTools, dir: project.path, @@ -84,8 +89,9 @@ describe('ConnectToolsScreen', () => { http.post('https://mcp.confidence.dev/mcp/docs', () => HttpResponse.error()), ); - using _auth = prepareAuthTokens('none'); using project = createProjectDir(); + using _home = isolateHome(project.path); + using _auth = prepareAuthTokens('none'); using sut = renderScreen(, { screen: ScreenId.ConnectTools, dir: project.path, @@ -105,8 +111,9 @@ describe('ConnectToolsScreen', () => { it('shows partial failure status', async () => { server.use(http.post('https://mcp.confidence.dev/mcp/docs', () => HttpResponse.error())); - using _auth = prepareAuthTokens('none'); using project = createProjectDir(); + using _home = isolateHome(project.path); + using _auth = prepareAuthTokens('none'); using sut = renderScreen(, { screen: ScreenId.ConnectTools, dir: project.path, @@ -127,8 +134,9 @@ describe('ConnectToolsScreen', () => { describe('when user skips', () => { it('shows skip confirmation and auto-advances', async () => { - using _auth = prepareAuthTokens('none'); using project = createProjectDir(); + using _home = isolateHome(project.path); + using _auth = prepareAuthTokens('none'); using sut = renderApp({ screen: ScreenId.ConnectTools, dir: project.path }); await waitFor(() => { diff --git a/packages/quickstart/__tests__/ui/testing-framework/index.ts b/packages/quickstart/__tests__/ui/testing-framework/index.ts index 57f16104..6bb952e5 100644 --- a/packages/quickstart/__tests__/ui/testing-framework/index.ts +++ b/packages/quickstart/__tests__/ui/testing-framework/index.ts @@ -8,7 +8,11 @@ export { buildAuthState, prepareAuthTokens, } from '@spotify-confidence/testing/auth'; -export { createProjectDir, type ProjectType } from '@spotify-confidence/testing/scaffold'; +export { + createProjectDir, + isolateHome, + type ProjectType, +} from '@spotify-confidence/testing/scaffold'; export { KEY_MAP, resolveKey, diff --git a/packages/testing/src/scaffold/home-scaffold.ts b/packages/testing/src/scaffold/home-scaffold.ts new file mode 100644 index 00000000..688923e5 --- /dev/null +++ b/packages/testing/src/scaffold/home-scaffold.ts @@ -0,0 +1,22 @@ +import { vi } from 'vitest'; + +/** + * Stubs the `HOME` environment variable to `dir`, isolating the test + * from real user config files (`~/.cursor/`, `~/.claude.json`, etc.). + * Supports `Symbol.dispose` for automatic cleanup via `vi.unstubAllEnvs`. + * + * @example + * ```ts + * using project = createProjectDir(); + * using _home = isolateHome(project.path); + * ``` + */ +export function isolateHome(dir: string) { + vi.stubEnv('HOME', dir); + + return { + [Symbol.dispose]() { + vi.unstubAllEnvs(); + }, + }; +} diff --git a/packages/testing/src/scaffold/index.ts b/packages/testing/src/scaffold/index.ts index 227cd3aa..69cf3514 100644 --- a/packages/testing/src/scaffold/index.ts +++ b/packages/testing/src/scaffold/index.ts @@ -1,4 +1,5 @@ export { createConfigDir } from './config-scaffold.js'; +export { isolateHome } from './home-scaffold.js'; export { createProjectDir } from './project-scaffold.js'; export { writeClaudeSettings,