-
Notifications
You must be signed in to change notification settings - Fork 3k
fix(cli): keep keypress handlers current #5421
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -13,6 +13,29 @@ import stripAnsi from 'strip-ansi'; | |||||||||||||
|
|
||||||||||||||
| const wait = (ms = 50) => new Promise((resolve) => setTimeout(resolve, ms)); | ||||||||||||||
| const clean = (value: string | undefined) => stripAnsi(value ?? ''); | ||||||||||||||
| const waitForFrame = async ( | ||||||||||||||
| predicate: () => void, | ||||||||||||||
| options: { timeout?: number; interval?: number } = {}, | ||||||||||||||
| ) => { | ||||||||||||||
| const { timeout = 1000, interval = 10 } = options; | ||||||||||||||
| const start = Date.now(); | ||||||||||||||
| let lastError: unknown; | ||||||||||||||
|
|
||||||||||||||
| while (Date.now() - start < timeout) { | ||||||||||||||
| try { | ||||||||||||||
| predicate(); | ||||||||||||||
| return; | ||||||||||||||
| } catch (error) { | ||||||||||||||
| lastError = error; | ||||||||||||||
| } | ||||||||||||||
| await wait(interval); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (lastError) { | ||||||||||||||
| throw lastError; | ||||||||||||||
| } | ||||||||||||||
| throw new Error('waitForFrame timed out'); | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| const createSingleQuestion = ( | ||||||||||||||
| overrides: Partial< | ||||||||||||||
|
|
@@ -301,22 +324,36 @@ describe('<AskUserQuestionDialog />', () => { | |||||||||||||
| await wait(); | ||||||||||||||
|
|
||||||||||||||
| stdin.write('4'); // Select "Other" custom input | ||||||||||||||
| await wait(150); | ||||||||||||||
| expect(clean(lastFrame())).toContain('❯ 4.'); | ||||||||||||||
| await waitForFrame(() => { | ||||||||||||||
| expect(clean(lastFrame())).toContain('❯ 4.'); | ||||||||||||||
| }); | ||||||||||||||
| await wait(); | ||||||||||||||
|
|
||||||||||||||
| stdin.write('j'); | ||||||||||||||
| await wait(150); | ||||||||||||||
| await waitForFrame(() => { | ||||||||||||||
| const frame = clean(lastFrame()); | ||||||||||||||
| expect(frame).toContain('❯ 4.'); | ||||||||||||||
| expect(frame).toContain('j'); | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| stdin.write('k'); | ||||||||||||||
| await wait(150); | ||||||||||||||
| expect(clean(lastFrame())).toContain('❯ 4.'); | ||||||||||||||
| await waitForFrame(() => { | ||||||||||||||
| const frame = clean(lastFrame()); | ||||||||||||||
| expect(frame).toContain('❯ 4.'); | ||||||||||||||
| expect(frame).toContain('jk'); | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| stdin.write('\u0010'); // Ctrl+P | ||||||||||||||
| await wait(); | ||||||||||||||
| expect(clean(lastFrame())).toContain('❯ 3. Green'); | ||||||||||||||
| await waitForFrame(() => { | ||||||||||||||
| expect(clean(lastFrame())).toContain('❯ 3. Green'); | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| stdin.write('\u000E'); // Ctrl+N | ||||||||||||||
| await wait(); | ||||||||||||||
| expect(clean(lastFrame())).toContain('❯ 4.'); | ||||||||||||||
| await waitForFrame(() => { | ||||||||||||||
| expect(clean(lastFrame())).toContain('❯ 4.'); | ||||||||||||||
| }); | ||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] After the Ctrl+P/N round-trip, the assertion only checks
Suggested change
— qwen3.7-max via Qwen Code /review
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done, the Ctrl+P/N round-trip now also checks that the custom input text is still there. |
||||||||||||||
|
|
||||||||||||||
| unmount(); | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,7 @@ | |
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| import { useEffect } from 'react'; | ||
| import { useCallback, useEffect, useRef } from 'react'; | ||
| import type { KeypressHandler, Key } from '../contexts/KeypressContext.js'; | ||
| import { useKeypressContext } from '../contexts/KeypressContext.js'; | ||
|
|
||
|
|
@@ -22,15 +22,22 @@ export function useKeypress( | |
| { isActive }: { isActive: boolean }, | ||
| ) { | ||
| const { subscribe, unsubscribe } = useKeypressContext(); | ||
| const onKeypressRef = useRef(onKeypress); | ||
|
|
||
| onKeypressRef.current = onKeypress; | ||
|
|
||
| const handleKeypress = useCallback<KeypressHandler>((key) => { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] The core fix (ref + Consider adding a regression test: it('always invokes the latest onKeypress callback after re-render', () => {
const first = vi.fn();
const second = vi.fn();
const { rerender } = renderHook(
({ handler }) => useKeypress(handler, { isActive: true }),
{ initialProps: { handler: first }, wrapper },
);
rerender({ handler: second });
act(() => stdin.pressKey({ name: 'a', sequence: 'a' }));
expect(first).not.toHaveBeenCalled();
expect(second).toHaveBeenCalledTimes(1);
});— qwen3.7-max via Qwen Code /review
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added a focused hook-level regression test for rerendered handlers. |
||
| onKeypressRef.current(key); | ||
| }, []); | ||
|
|
||
| useEffect(() => { | ||
| if (!isActive) { | ||
| return; | ||
| } | ||
|
|
||
| subscribe(onKeypress); | ||
| subscribe(handleKeypress); | ||
| return () => { | ||
| unsubscribe(onKeypress); | ||
| unsubscribe(handleKeypress); | ||
| }; | ||
| }, [isActive, onKeypress, subscribe, unsubscribe]); | ||
| }, [isActive, handleKeypress, subscribe, unsubscribe]); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Suggestion]
waitForFrameis functionally identical tovi.waitForfrom vitest, which is already used 17+ times in neighboring test files (e.g.,HooksManagementDialog.test.tsx). The custom helper adds ~20 lines of duplicate infrastructure.Replace with
vi.waitForand delete the helper:— qwen3.7-max via Qwen Code /review
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done, switched the dialog waits to vi.waitFor and removed the custom helper.