From 6889833737acc4091cb6872f35cc9f8c848a3e04 Mon Sep 17 00:00:00 2001 From: Dawson Toth Date: Thu, 2 Jul 2026 10:36:24 -0400 Subject: [PATCH] fix(test): replace fixed-delay waits with polling in MainConfig walkthrough tests The walkthrough tests waited a fixed 100ms after each stdin write, assuming Ink's re-render always completed in that window. Under CI load (observed on Node 24 ubuntu-latest) this could flake before a re-render caught up. Replace the sleep with vi.waitFor polling for the expected frame content, and add an explicit wait for the masked password field before submitting the API key so keystrokes are guaranteed to have reached component state. --- ink/main.test.tsx | 144 +++++++++++++++++++++------------------------- 1 file changed, 67 insertions(+), 77 deletions(-) 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