diff --git a/ink/main.test.tsx b/ink/main.test.tsx index bf87678..0259411 100644 --- a/ink/main.test.tsx +++ b/ink/main.test.tsx @@ -31,6 +31,17 @@ const down = '\u001B[B'; // const left = '\u001B[D'; const enter = '\r'; +// Ink re-renders asynchronously after stdin writes; polling for the expected +// frame avoids the flakiness of a fixed-duration sleep under CI load. +const waitForFrame = (lastFrame: () => string | undefined, text: string) => + vi.waitFor(() => expect(lastFrame()).toContain(text)); + +// ApiKeyStep renders input as a password field, masking typed characters as +// `*`. Wait for the masked value before submitting so the keystrokes have +// actually reached component state. +const waitForMaskedInput = (lastFrame: () => string | undefined, text: string) => + vi.waitFor(() => expect(lastFrame()).toContain('*'.repeat(text.length))); + describe('MainConfig', () => { const mockExit = vi.fn(); @@ -74,35 +85,30 @@ describe('MainConfig', () => { const onComplete = vi.fn(); const { lastFrame, stdin } = render(); - const simulateKey = async (char: string) => { - stdin.write(char); - // Wait for re-render - await new Promise((resolve) => setTimeout(resolve, 100)); - }; - // 1. ProviderStep - Choose OpenAI (default) - expect(lastFrame()).toContain('What model provider would you like to use today?'); - await simulateKey(enter); + await waitForFrame(lastFrame, 'What model provider would you like to use today?'); + stdin.write(enter); // 2. ApiKeyStep - expect(lastFrame()).toContain('Can you provide us with your OpenAI API key?'); - await simulateKey('sk-test-key'); - await simulateKey(enter); + await waitForFrame(lastFrame, 'Can you provide us with your OpenAI API key?'); + stdin.write('sk-test-key'); + await waitForMaskedInput(lastFrame, 'sk-test-key'); + stdin.write(enter); // 3. ModelSelectionStep - Model - expect(lastFrame()).toContain('What model would you like to use?'); - await simulateKey(enter); // Accept default + await waitForFrame(lastFrame, 'What model would you like to use?'); + stdin.write(enter); // Accept default // 4. ModelSelectionStep - Compactor - expect(lastFrame()).toContain('What model should we use for memory compaction?'); - await simulateKey(enter); // Accept default + await waitForFrame(lastFrame, 'What model should we use for memory compaction?'); + stdin.write(enter); // Accept default // 5. EnvironmentSettingsStep - expect(lastFrame()).toContain('Additional Settings'); - await simulateKey(enter); // Accept defaults + await waitForFrame(lastFrame, 'Additional Settings'); + stdin.write(enter); // Accept defaults // Verification - expect(onComplete).toHaveBeenCalled(); + await vi.waitFor(() => expect(onComplete).toHaveBeenCalled()); expect(vi.mocked(writeFileSync)).toHaveBeenCalled(); // Check process.env @@ -115,36 +121,31 @@ describe('MainConfig', () => { const onComplete = vi.fn(); const { lastFrame, stdin } = render(); - const simulateKey = async (char: string) => { - stdin.write(char); - // Wait for re-render - await new Promise((resolve) => setTimeout(resolve, 100)); - }; - // 1. ProviderStep - Choose Anthropic - expect(lastFrame()).toContain('What model provider would you like to use today?'); - await simulateKey(down); // to Anthropic - await simulateKey(enter); + await waitForFrame(lastFrame, 'What model provider would you like to use today?'); + stdin.write(down); // to Anthropic + stdin.write(enter); // 2. ApiKeyStep - expect(lastFrame()).toContain('Can you provide us with your Anthropic API key?'); - await simulateKey('sk-ant-test-key'); - await simulateKey(enter); + await waitForFrame(lastFrame, 'Can you provide us with your Anthropic API key?'); + stdin.write('sk-ant-test-key'); + await waitForMaskedInput(lastFrame, 'sk-ant-test-key'); + stdin.write(enter); // 3. ModelSelectionStep - Model - expect(lastFrame()).toContain('What model would you like to use?'); - await simulateKey(enter); // Accept default + await waitForFrame(lastFrame, 'What model would you like to use?'); + stdin.write(enter); // Accept default // 4. ModelSelectionStep - Compactor - expect(lastFrame()).toContain('What model should we use for memory compaction?'); - await simulateKey(enter); // Accept default + await waitForFrame(lastFrame, 'What model should we use for memory compaction?'); + stdin.write(enter); // Accept default // 5. EnvironmentSettingsStep - expect(lastFrame()).toContain('Additional Settings'); - await simulateKey(enter); // Accept defaults + await waitForFrame(lastFrame, 'Additional Settings'); + stdin.write(enter); // Accept defaults // Verification - expect(onComplete).toHaveBeenCalled(); + await vi.waitFor(() => expect(onComplete).toHaveBeenCalled()); expect(vi.mocked(writeFileSync)).toHaveBeenCalled(); // Check process.env @@ -157,37 +158,32 @@ describe('MainConfig', () => { const onComplete = vi.fn(); const { lastFrame, stdin } = render(); - const simulateKey = async (char: string) => { - stdin.write(char); - // Wait for re-render - await new Promise((resolve) => setTimeout(resolve, 100)); - }; - // 1. ProviderStep - Choose Google - expect(lastFrame()).toContain('What model provider would you like to use today?'); - await simulateKey(down); // to Anthropic - await simulateKey(down); // to Google - await simulateKey(enter); + await waitForFrame(lastFrame, 'What model provider would you like to use today?'); + stdin.write(down); // to Anthropic + stdin.write(down); // to Google + stdin.write(enter); // 2. ApiKeyStep - expect(lastFrame()).toContain('Can you provide us with your Google API key?'); - await simulateKey('google-test-key'); - await simulateKey(enter); + await waitForFrame(lastFrame, 'Can you provide us with your Google API key?'); + stdin.write('google-test-key'); + await waitForMaskedInput(lastFrame, 'google-test-key'); + stdin.write(enter); // 3. ModelSelectionStep - Model - expect(lastFrame()).toContain('What model would you like to use?'); - await simulateKey(enter); // Accept default + await waitForFrame(lastFrame, 'What model would you like to use?'); + stdin.write(enter); // Accept default // 4. ModelSelectionStep - Compactor - expect(lastFrame()).toContain('What model should we use for memory compaction?'); - await simulateKey(enter); // Accept default + await waitForFrame(lastFrame, 'What model should we use for memory compaction?'); + stdin.write(enter); // Accept default // 5. EnvironmentSettingsStep - expect(lastFrame()).toContain('Additional Settings'); - await simulateKey(enter); // Accept defaults + await waitForFrame(lastFrame, 'Additional Settings'); + stdin.write(enter); // Accept defaults // Verification - expect(onComplete).toHaveBeenCalled(); + await vi.waitFor(() => expect(onComplete).toHaveBeenCalled()); expect(vi.mocked(writeFileSync)).toHaveBeenCalled(); // Check process.env @@ -200,38 +196,32 @@ describe('MainConfig', () => { const onComplete = vi.fn(); const { lastFrame, stdin } = render(); - const simulateKey = async (char: string) => { - stdin.write(char); - // Wait for re-render - await new Promise((resolve) => setTimeout(resolve, 100)); - }; - // 1. ProviderStep - Choose Ollama - expect(lastFrame()).toContain('What model provider would you like to use today?'); + await waitForFrame(lastFrame, 'What model provider would you like to use today?'); - await simulateKey(down); // to Anthropic - await simulateKey(down); // to Google - await simulateKey(down); // to Ollama - await simulateKey(enter); + stdin.write(down); // to Anthropic + stdin.write(down); // to Google + stdin.write(down); // to Ollama + stdin.write(enter); // 2. ApiUrlStep (since provider is Ollama) - expect(lastFrame()).toContain('Where are you hosting Ollama?'); - await simulateKey(enter); // Accept default http://localhost:11434/api + await waitForFrame(lastFrame, 'Where are you hosting Ollama?'); + stdin.write(enter); // Accept default http://localhost:11434/api // 3. ModelSelectionStep - Model - expect(lastFrame()).toContain('What model would you like to use?'); - await simulateKey(enter); // Accept default + await waitForFrame(lastFrame, 'What model would you like to use?'); + stdin.write(enter); // Accept default // 4. ModelSelectionStep - Compactor - expect(lastFrame()).toContain('What model should we use for memory compaction?'); - await simulateKey(enter); // Accept default + await waitForFrame(lastFrame, 'What model should we use for memory compaction?'); + stdin.write(enter); // Accept default // 5. EnvironmentSettingsStep - expect(lastFrame()).toContain('Additional Settings'); - await simulateKey(enter); // Accept defaults + await waitForFrame(lastFrame, 'Additional Settings'); + stdin.write(enter); // Accept defaults // Verification - expect(onComplete).toHaveBeenCalled(); + await vi.waitFor(() => expect(onComplete).toHaveBeenCalled()); expect(vi.mocked(writeFileSync)).toHaveBeenCalled(); // Check process.env