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,