diff --git a/packages/modifiers-napi/src/__tests__/index.test.ts b/packages/modifiers-napi/src/__tests__/index.test.ts
index a17e698c8..9c9ee3e47 100644
--- a/packages/modifiers-napi/src/__tests__/index.test.ts
+++ b/packages/modifiers-napi/src/__tests__/index.test.ts
@@ -67,9 +67,11 @@ describe('modifiers-napi', () => {
const mod = await loadModule()
await mod.prewarm()
+ const callsAfterFirst = dlopenCalls
+
await mod.prewarm()
- expect(dlopenCalls).toBe(1)
+ expect(dlopenCalls).toBe(callsAfterFirst)
})
test('returns false when ffi loading fails on darwin', async () => {
diff --git a/src/commands/autofix-pr/__tests__/AutofixProgress.test.tsx b/src/commands/autofix-pr/__tests__/AutofixProgress.test.tsx
index 463d1972d..b4c58234c 100644
--- a/src/commands/autofix-pr/__tests__/AutofixProgress.test.tsx
+++ b/src/commands/autofix-pr/__tests__/AutofixProgress.test.tsx
@@ -9,17 +9,25 @@ import { renderToString } from '../../../utils/staticRender.js';
import { AutofixProgress } from '../AutofixProgress.js';
describe('AutofixProgress', () => {
- test('renders target in header', async () => {
- const out = await renderToString();
- expect(out).toContain('acme/myrepo#42');
- expect(out).toContain('Autofix PR');
- });
+ test(
+ 'renders target in header',
+ async () => {
+ const out = await renderToString();
+ expect(out).toContain('acme/myrepo#42');
+ expect(out).toContain('Autofix PR');
+ },
+ { timeout: 10000 },
+ );
- test('detecting phase shows arrow on detecting step', async () => {
- const out = await renderToString();
- // detecting step should be active (→) and later steps pending (·)
- expect(out).toContain('Detecting repository');
- });
+ test(
+ 'detecting phase shows arrow on detecting step',
+ async () => {
+ const out = await renderToString();
+ // detecting step should be active (→) and later steps pending (·)
+ expect(out).toContain('Detecting repository');
+ },
+ { timeout: 10000 },
+ );
test('checking_eligibility phase renders eligibility label', async () => {
const out = await renderToString();
diff --git a/src/components/FeedbackSurvey/__tests__/useFrustrationDetection.test.tsx b/src/components/FeedbackSurvey/__tests__/useFrustrationDetection.test.tsx
index cb4cf9368..78a674c9c 100644
--- a/src/components/FeedbackSurvey/__tests__/useFrustrationDetection.test.tsx
+++ b/src/components/FeedbackSurvey/__tests__/useFrustrationDetection.test.tsx
@@ -69,20 +69,28 @@ afterEach(() => {
});
describe('useFrustrationDetection', () => {
- test('stays closed without frustration signals', async () => {
- const result = await renderDetection({ messages: [] });
+ test(
+ 'stays closed without frustration signals',
+ async () => {
+ const result = await renderDetection({ messages: [] });
- expect(result.state).toBe('closed');
- expect(typeof result.handleTranscriptSelect).toBe('function');
- });
+ expect(result.state).toBe('closed');
+ expect(typeof result.handleTranscriptSelect).toBe('function');
+ },
+ { timeout: 10000 },
+ );
- test('opens a transcript prompt for repeated API errors', async () => {
- const result = await renderDetection({
- messages: [apiError('a'), apiError('b')],
- });
+ test(
+ 'opens a transcript prompt for repeated API errors',
+ async () => {
+ const result = await renderDetection({
+ messages: [apiError('a'), apiError('b')],
+ });
- expect(result.state).toBe('transcript_prompt');
- });
+ expect(result.state).toBe('transcript_prompt');
+ },
+ { timeout: 10000 },
+ );
test('does not prompt while loading, prompting, blocked by another survey, dismissed, or policy-denied', async () => {
const messages = [apiError('a'), apiError('b')];
diff --git a/src/components/__tests__/ConsoleOAuthFlow.test.tsx b/src/components/__tests__/ConsoleOAuthFlow.test.tsx
index 0f65d8d7d..8f8e93e91 100644
--- a/src/components/__tests__/ConsoleOAuthFlow.test.tsx
+++ b/src/components/__tests__/ConsoleOAuthFlow.test.tsx
@@ -1,54 +1,55 @@
import { describe, expect, test, mock } from 'bun:test';
import * as React from 'react';
+import { renderToString } from '../../utils/staticRender.js';
-mock.module('react', () => ({
- ...React,
- useState: (initial: any) => [typeof initial === 'function' ? initial() : initial, () => {}],
- useEffect: () => {},
- useRef: (initial: any) => ({ current: initial }),
- useCallback: (fn: any) => fn,
- useMemo: (fn: any) => fn(),
- useContext: () => ({}),
- useLayoutEffect: () => {}, // Add a simple mock for useLayoutEffect
-}));
-
-import { ConsoleOAuthFlow } from '../ConsoleOAuthFlow.js';
-
-// Mock dependencies
+// Mock dependencies MUST be called before importing the component
mock.module('src/services/analytics/index.js', () => ({
logEvent: () => {},
}));
-mock.module('../utils/localLlm.js', () => ({
- checkOllamaStatus: async () => true,
- listOllamaModels: async () => ['llama3.1', 'mistral'],
- pullOllamaModel: async () => {},
- pingUrl: async () => true,
+mock.module('../cli/handlers/auth.js', () => ({
+ installOAuthTokens: async () => {},
+}));
+
+mock.module('../utils/browser.js', () => ({
+ openBrowser: async () => {},
+}));
+
+mock.module('../utils/log.js', () => ({
+ logError: () => {},
}));
mock.module('../utils/settings/settings.js', () => ({
getSettings_DEPRECATED: () => ({}),
- updateSettingsForSource: () => {},
-}));
-
-mock.module('../utils/auth.js', () => ({
- getOauthAccountInfo: async () => ({}),
- validateForceLoginOrg: async () => true,
+ updateSettingsForSource: async () => {},
}));
mock.module('../services/oauth/index.js', () => ({
OAuthService: {
- start: async () => {},
+ getAuthorizationUrl: async () => 'https://example.com/auth',
+ exchangeCode: async () => ({ accessToken: 'abc' }),
},
}));
-mock.module('@anthropic/ink', () => ({
- useTerminalNotification: () => () => {},
- setClipboard: () => {},
- Box: ({ children }: any) =>
{children}
,
- Link: ({ children }: any) => {children}
,
- Text: ({ children }: any) => {children}
,
- KeyboardShortcutHint: () => null,
+mock.module('../utils/auth.js', () => ({
+ getOauthAccountInfo: async () => ({ email: 'test@example.com' }),
+ validateForceLoginOrg: async () => true,
+}));
+
+mock.module('../services/notifier.js', () => ({
+ sendNotification: () => {},
+}));
+
+mock.module('./CustomSelect/select.js', () => ({
+ Select: () => null,
+}));
+
+mock.module('./Spinner.js', () => ({
+ Spinner: () => null,
+}));
+
+mock.module('./TextInput.js', () => ({
+ default: () => null,
}));
mock.module('../hooks/useTerminalSize.js', () => ({
@@ -59,16 +60,19 @@ mock.module('../keybindings/useKeybinding.js', () => ({
useKeybinding: () => {},
}));
-describe('ConsoleOAuthFlow', () => {
- test('renders initial login method selection', () => {
- const onDone = () => {};
- const element = ConsoleOAuthFlow({ onDone }) as React.ReactElement;
+import { ConsoleOAuthFlow } from '../ConsoleOAuthFlow.js';
- // The component returns a React element tree
- // We expect it to contain the title and options
- const str = JSON.stringify(element);
- expect(str).toContain('Select login method');
- expect(str).toContain('Anthropic Console');
- expect(str).toContain('Local LLM');
- });
+describe('ConsoleOAuthFlow', () => {
+ test(
+ 'renders initial login method selection',
+ async () => {
+ const onDone = () => {};
+ const out = await renderToString();
+
+ expect(out).toContain('Select login method');
+ expect(out).toContain('Local LLM');
+ expect(out).toContain('Gemini API');
+ },
+ { timeout: 10000 },
+ );
});
diff --git a/src/utils/__tests__/modifiers.test.ts b/src/utils/__tests__/modifiers.test.ts
index e059b69e1..23c154c69 100644
--- a/src/utils/__tests__/modifiers.test.ts
+++ b/src/utils/__tests__/modifiers.test.ts
@@ -4,7 +4,12 @@ let nativePrewarmCalls = 0
let nativeReturnValue = false
let nativeShouldThrow = false
+const MODIFIERS_TEST_GUARD = '__MODIFIERS_TEST_ACTIVE__'
+
const nativeIsModifierPressed = mock((modifier: string) => {
+ if (!(globalThis as any)[MODIFIERS_TEST_GUARD]) {
+ return false
+ }
if (nativeShouldThrow) {
throw new Error('native modifier failure')
}
@@ -13,7 +18,9 @@ const nativeIsModifierPressed = mock((modifier: string) => {
mock.module('modifiers-napi', () => ({
prewarm: async () => {
- nativePrewarmCalls++
+ if ((globalThis as any)[MODIFIERS_TEST_GUARD]) {
+ nativePrewarmCalls++
+ }
},
isModifierPressed: nativeIsModifierPressed,
}))
@@ -25,6 +32,7 @@ async function loadModule() {
}
beforeEach(() => {
+ ;(globalThis as any)[MODIFIERS_TEST_GUARD] = true
nativePrewarmCalls = 0
nativeReturnValue = false
nativeShouldThrow = false
@@ -36,6 +44,11 @@ beforeEach(() => {
})
afterEach(() => {
+ ;(globalThis as any)[MODIFIERS_TEST_GUARD] = false
+ nativePrewarmCalls = 0
+ nativeReturnValue = false
+ nativeShouldThrow = false
+ nativeIsModifierPressed.mockClear()
Object.defineProperty(process, 'platform', {
value: originalPlatform,
configurable: true,