Skip to content

Commit 6748434

Browse files
authored
fix: flakey CLI tests (#8124)
fix: flakey test with waitForNextRender
1 parent 03598ea commit 6748434

File tree

4 files changed

+37
-28
lines changed

4 files changed

+37
-28
lines changed

extensions/cli/src/ui/__tests__/TUIChat.fileSearch.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { renderInMode, testSingleMode } from "./TUIChat.dualModeHelper.js";
2+
import { waitForNextRender } from "./TUIChat.testHelper.js";
23

34
describe("TUIChat - @ File Search Tests", () => {
45
testSingleMode("shows @ character when user types @", "local", async () => {
@@ -56,9 +57,8 @@ describe("TUIChat - @ File Search Tests", () => {
5657

5758
// Type multiple @ characters
5859
stdin.write("@@test");
59-
6060
// Wait for UI update
61-
await new Promise((resolve) => setTimeout(resolve, 500));
61+
await waitForNextRender();
6262

6363
const frame = lastFrame();
6464

extensions/cli/src/ui/__tests__/TUIChat.input.test.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import { testBothModes, renderInMode } from "./TUIChat.dualModeHelper.js";
1+
import { renderInMode, testBothModes } from "./TUIChat.dualModeHelper.js";
2+
import { waitForNextRender } from "./TUIChat.testHelper.js";
23

34
describe("TUIChat - User Input Tests", () => {
4-
testBothModes("shows typed text in input field", (mode) => {
5+
testBothModes("shows typed text in input field", async (mode) => {
56
const { lastFrame, stdin } = renderInMode(mode);
67

78
stdin.write("Testing 123");
9+
await waitForNextRender();
810

911
const frame = lastFrame();
1012
// The input might be in a different format, let's be more flexible
@@ -23,13 +25,14 @@ describe("TUIChat - User Input Tests", () => {
2325
testBothModes("handles Enter key to submit", async (mode) => {
2426
const { lastFrame, stdin } = renderInMode(mode);
2527

28+
await waitForNextRender();
2629
const beforeEnter = lastFrame();
2730
expect(beforeEnter).toContain("Ask anything");
2831

2932
stdin.write("\r");
3033

3134
// Wait for the UI to update after pressing enter
32-
await new Promise((resolve) => setTimeout(resolve, 50));
35+
await new Promise((resolve) => setTimeout(resolve, 500));
3336

3437
const afterEnter = lastFrame();
3538

@@ -46,20 +49,22 @@ describe("TUIChat - User Input Tests", () => {
4649

4750
testBothModes(
4851
"handles special characters in input without crashing",
49-
(mode) => {
52+
async (mode) => {
5053
const { lastFrame, stdin } = renderInMode(mode);
5154

5255
// Try typing various special characters
5356
stdin.write("!@#$%^&*()");
57+
await waitForNextRender();
5458

5559
const frame = lastFrame();
5660

5761
// Should handle special characters without crashing
5862
expect(frame).toBeDefined();
5963
expect(frame).not.toBe("");
6064

61-
// UI should still be functional
62-
expect(frame).toContain("Ask anything");
65+
// UI should still be functional and show the typed special characters
66+
// Note: "Ask anything" placeholder is replaced when text is typed
67+
expect(frame).toContain("!@#$%^&*()");
6368

6469
// Mode-specific UI elements
6570
if (mode === "remote") {

extensions/cli/src/ui/__tests__/TUIChat.slashCommands.test.tsx

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import { renderInMode, testBothModes } from "./TUIChat.dualModeHelper.js";
2+
import { waitForNextRender } from "./TUIChat.testHelper.js";
23

34
describe("TUIChat - Slash Commands Tests", () => {
45
testBothModes("shows slash when user types /", async (mode) => {
56
const { lastFrame, stdin } = renderInMode(mode);
67

78
// Type / to trigger slash command
89
stdin.write("/");
9-
10-
// Wait a bit for the UI to update
11-
await new Promise((resolve) => setTimeout(resolve, 200));
10+
await waitForNextRender();
1211

1312
const frame = lastFrame();
1413

@@ -29,9 +28,7 @@ describe("TUIChat - Slash Commands Tests", () => {
2928

3029
// Type /exi to trigger slash command filtering
3130
stdin.write("/exi");
32-
33-
// Wait a bit for the UI to update (allow extra time in both modes)
34-
await new Promise((resolve) => setTimeout(resolve, 600));
31+
await waitForNextRender();
3532

3633
const frame = lastFrame();
3734

@@ -52,12 +49,10 @@ describe("TUIChat - Slash Commands Tests", () => {
5249

5350
// Type /exi and then tab
5451
stdin.write("/exi");
55-
56-
await new Promise((resolve) => setTimeout(resolve, 200));
52+
await waitForNextRender();
5753

5854
stdin.write("\t");
59-
60-
await new Promise((resolve) => setTimeout(resolve, 200));
55+
await waitForNextRender();
6156

6257
const frameAfterTab = lastFrame();
6358

@@ -80,8 +75,7 @@ describe("TUIChat - Slash Commands Tests", () => {
8075

8176
// Type just /
8277
stdin.write("/");
83-
84-
await new Promise((resolve) => setTimeout(resolve, 600));
78+
await waitForNextRender();
8579

8680
const frame = lastFrame();
8781

@@ -113,8 +107,7 @@ describe("TUIChat - Slash Commands Tests", () => {
113107

114108
// Type a complete command name first
115109
stdin.write("/title");
116-
117-
await new Promise((resolve) => setTimeout(resolve, 200));
110+
await waitForNextRender();
118111

119112
const frameAfterCommand = lastFrame();
120113
if (mode === "remote") {
@@ -127,8 +120,7 @@ describe("TUIChat - Slash Commands Tests", () => {
127120

128121
// Now add a space and arguments
129122
stdin.write(" My Session Title");
130-
131-
await new Promise((resolve) => setTimeout(resolve, 200));
123+
await waitForNextRender();
132124

133125
const frameAfterArgs = lastFrame();
134126

@@ -151,17 +143,15 @@ describe("TUIChat - Slash Commands Tests", () => {
151143

152144
// Type a complete command with arguments
153145
stdin.write("/title Test Session");
154-
155-
await new Promise((resolve) => setTimeout(resolve, 200));
146+
await waitForNextRender();
156147

157148
const frameBeforeEnter = lastFrame();
158149
expect(frameBeforeEnter).toContain("/title");
159150
expect(frameBeforeEnter).toContain("Test Session");
160151

161152
// Press Enter - this should execute the command, not try to autocomplete
162153
stdin.write("\r");
163-
164-
await new Promise((resolve) => setTimeout(resolve, 300));
154+
await waitForNextRender();
165155

166156
const frameAfterEnter = lastFrame();
167157

extensions/cli/src/ui/__tests__/TUIChat.testHelper.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,20 @@ export async function sendMessage(
422422
await new Promise((resolve) => setTimeout(resolve, waitTime));
423423
}
424424

425+
/**
426+
* Helper to wait for next render cycle to complete
427+
* Use this after stdin.write() to ensure at least one render has occurred
428+
* This prevents flaky tests caused by calling lastFrame() before React renders
429+
*/
430+
export async function waitForNextRender(): Promise<void> {
431+
// Wait for multiple ticks to ensure render completes
432+
await new Promise((resolve) => setImmediate(resolve));
433+
await new Promise((resolve) => setImmediate(resolve));
434+
// Add a delay to allow Ink terminal UI to render
435+
// CI environments are slower, so we need a longer delay
436+
await new Promise((resolve) => setTimeout(resolve, 500));
437+
}
438+
425439
/**
426440
* Helper to check if UI shows remote mode indicators
427441
*/

0 commit comments

Comments
 (0)