From 35efa9f04a5dccf452c624b3481522365627f73f Mon Sep 17 00:00:00 2001 From: tanzhenxin Date: Tue, 16 Sep 2025 14:59:03 +0800 Subject: [PATCH] fix: try to fix test case failures on Windows --- packages/cli/src/ui/App.tsx | 2 + .../src/ui/contexts/KeypressContext.test.tsx | 55 +++++++++++-------- .../cli/src/ui/contexts/KeypressContext.tsx | 27 +++++---- packages/cli/src/ui/hooks/useKeypress.test.ts | 45 ++++++++++----- 4 files changed, 78 insertions(+), 51 deletions(-) diff --git a/packages/cli/src/ui/App.tsx b/packages/cli/src/ui/App.tsx index 5039a170..737a927d 100644 --- a/packages/cli/src/ui/App.tsx +++ b/packages/cli/src/ui/App.tsx @@ -127,9 +127,11 @@ interface AppProps { export const AppWrapper = (props: AppProps) => { const kittyProtocolStatus = useKittyKeyboardProtocol(); + const nodeMajorVersion = parseInt(process.versions.node.split('.')[0], 10); return ( diff --git a/packages/cli/src/ui/contexts/KeypressContext.test.tsx b/packages/cli/src/ui/contexts/KeypressContext.test.tsx index e45b8811..1fec5fd6 100644 --- a/packages/cli/src/ui/contexts/KeypressContext.test.tsx +++ b/packages/cli/src/ui/contexts/KeypressContext.test.tsx @@ -66,11 +66,16 @@ describe('KeypressContext - Kitty Protocol', () => { const wrapper = ({ children, kittyProtocolEnabled = true, + pasteWorkaround = false, }: { children: React.ReactNode; kittyProtocolEnabled?: boolean; + pasteWorkaround?: boolean; }) => ( - + {children} ); @@ -389,17 +394,15 @@ describe('KeypressContext - Kitty Protocol', () => { }); describe('paste mode markers', () => { - beforeEach(() => { - // Force passthrough mode for raw keypress testing - vi.stubEnv('PASTE_WORKAROUND', '1'); - }); + // These tests use pasteWorkaround=true to force passthrough mode for raw keypress testing it('should handle complete paste sequence with markers', async () => { const keyHandler = vi.fn(); const pastedText = 'pasted content'; const { result } = renderHook(() => useKeypressContext(), { - wrapper, + wrapper: ({ children }) => + wrapper({ children, pasteWorkaround: true }), }); act(() => { @@ -429,7 +432,8 @@ describe('KeypressContext - Kitty Protocol', () => { const keyHandler = vi.fn(); const { result } = renderHook(() => useKeypressContext(), { - wrapper, + wrapper: ({ children }) => + wrapper({ children, pasteWorkaround: true }), }); act(() => { @@ -459,7 +463,8 @@ describe('KeypressContext - Kitty Protocol', () => { const keyHandler = vi.fn(); const { result } = renderHook(() => useKeypressContext(), { - wrapper, + wrapper: ({ children }) => + wrapper({ children, pasteWorkaround: true }), }); act(() => { @@ -515,7 +520,8 @@ describe('KeypressContext - Kitty Protocol', () => { const keyHandler = vi.fn(); const { result } = renderHook(() => useKeypressContext(), { - wrapper, + wrapper: ({ children }) => + wrapper({ children, pasteWorkaround: true }), }); act(() => { @@ -554,7 +560,8 @@ describe('KeypressContext - Kitty Protocol', () => { const keyHandler = vi.fn(); const { result } = renderHook(() => useKeypressContext(), { - wrapper, + wrapper: ({ children }) => + wrapper({ children, pasteWorkaround: true }), }); act(() => { @@ -658,7 +665,8 @@ describe('KeypressContext - Kitty Protocol', () => { const keyHandler = vi.fn(); const { result } = renderHook(() => useKeypressContext(), { - wrapper, + wrapper: ({ children }) => + wrapper({ children, pasteWorkaround: true }), }); act(() => { @@ -689,7 +697,8 @@ describe('KeypressContext - Kitty Protocol', () => { const multilineContent = 'line1\nline2\nline3'; const { result } = renderHook(() => useKeypressContext(), { - wrapper, + wrapper: ({ children }) => + wrapper({ children, pasteWorkaround: true }), }); act(() => { @@ -721,7 +730,8 @@ describe('KeypressContext - Kitty Protocol', () => { const keyHandler = vi.fn(); const { result } = renderHook(() => useKeypressContext(), { - wrapper, + wrapper: ({ children }) => + wrapper({ children, pasteWorkaround: true }), }); act(() => { @@ -754,7 +764,7 @@ describe('KeypressContext - Kitty Protocol', () => { const keyHandler = vi.fn(); const { result } = renderHook(() => useKeypressContext(), { - wrapper, + wrapper: ({ children }) => wrapper({ children, pasteWorkaround: true }), }); act(() => { @@ -789,17 +799,14 @@ describe('KeypressContext - Kitty Protocol', () => { }); describe('Raw keypress pipeline', () => { - beforeEach(() => { - // Force passthrough mode for raw keypress testing - vi.stubEnv('PASTE_WORKAROUND', '1'); - }); + // These tests use pasteWorkaround=true to force passthrough mode for raw keypress testing it('should buffer input data and wait for timeout', () => { vi.useFakeTimers(); const keyHandler = vi.fn(); const { result } = renderHook(() => useKeypressContext(), { - wrapper, + wrapper: ({ children }) => wrapper({ children, pasteWorkaround: true }), }); act(() => { @@ -829,7 +836,7 @@ describe('KeypressContext - Kitty Protocol', () => { const keyHandler = vi.fn(); const { result } = renderHook(() => useKeypressContext(), { - wrapper, + wrapper: ({ children }) => wrapper({ children, pasteWorkaround: true }), }); act(() => { @@ -892,7 +899,7 @@ describe('KeypressContext - Kitty Protocol', () => { const keyHandler = vi.fn(); const { result } = renderHook(() => useKeypressContext(), { - wrapper, + wrapper: ({ children }) => wrapper({ children, pasteWorkaround: true }), }); act(() => { @@ -934,7 +941,7 @@ describe('KeypressContext - Kitty Protocol', () => { const keyHandler = vi.fn(); const { result } = renderHook(() => useKeypressContext(), { - wrapper, + wrapper: ({ children }) => wrapper({ children, pasteWorkaround: true }), }); act(() => { @@ -987,7 +994,7 @@ describe('KeypressContext - Kitty Protocol', () => { const keyHandler = vi.fn(); const { result } = renderHook(() => useKeypressContext(), { - wrapper, + wrapper: ({ children }) => wrapper({ children, pasteWorkaround: true }), }); act(() => { @@ -1036,7 +1043,7 @@ describe('KeypressContext - Kitty Protocol', () => { const keyHandler = vi.fn(); const { result } = renderHook(() => useKeypressContext(), { - wrapper, + wrapper: ({ children }) => wrapper({ children, pasteWorkaround: true }), }); act(() => { diff --git a/packages/cli/src/ui/contexts/KeypressContext.tsx b/packages/cli/src/ui/contexts/KeypressContext.tsx index cc0b1dd3..bc6da7e4 100644 --- a/packages/cli/src/ui/contexts/KeypressContext.tsx +++ b/packages/cli/src/ui/contexts/KeypressContext.tsx @@ -69,10 +69,12 @@ export function useKeypressContext() { export function KeypressProvider({ children, kittyProtocolEnabled, + pasteWorkaround = false, config, }: { - children: React.ReactNode; + children?: React.ReactNode; kittyProtocolEnabled: boolean; + pasteWorkaround?: boolean; config?: Config; }) { const { stdin, setRawMode } = useStdin(); @@ -97,18 +99,8 @@ export function KeypressProvider({ const keypressStream = new PassThrough(); let usePassthrough = false; - const nodeMajorVersion = parseInt(process.versions.node.split('.')[0], 10); - const isWindows = process.platform === 'win32'; - // On Windows, Node's readline keypress stream often loses bracketed paste - // boundaries, causing multi-line pastes to be delivered as plain Return - // key events. This leads to accidental submits on Enter within pasted text. - // Force passthrough on Windows to parse raw bytes and detect ESC[200~...201~. - if ( - nodeMajorVersion < 20 || - isWindows || - process.env['PASTE_WORKAROUND'] === '1' || - process.env['PASTE_WORKAROUND'] === 'true' - ) { + // Use passthrough mode when pasteWorkaround is enabled, + if (pasteWorkaround) { usePassthrough = true; } @@ -500,7 +492,14 @@ export function KeypressProvider({ pasteBuffer = Buffer.alloc(0); } }; - }, [stdin, setRawMode, kittyProtocolEnabled, config, subscribers]); + }, [ + stdin, + setRawMode, + kittyProtocolEnabled, + pasteWorkaround, + config, + subscribers, + ]); return ( diff --git a/packages/cli/src/ui/hooks/useKeypress.test.ts b/packages/cli/src/ui/hooks/useKeypress.test.ts index d542f967..8c4aa9e1 100644 --- a/packages/cli/src/ui/hooks/useKeypress.test.ts +++ b/packages/cli/src/ui/hooks/useKeypress.test.ts @@ -105,7 +105,28 @@ describe('useKeypress', () => { let originalNodeVersion: string; const wrapper = ({ children }: { children: React.ReactNode }) => - React.createElement(KeypressProvider, null, children); + React.createElement( + KeypressProvider, + { + kittyProtocolEnabled: false, + pasteWoraround: false, + }, + children, + ); + + const wrapperWithWindowsWorkaround = ({ + children, + }: { + children: React.ReactNode; + }) => + React.createElement( + KeypressProvider, + { + kittyProtocolEnabled: false, + pasteWoraround: true, + }, + children, + ); beforeEach(() => { vi.clearAllMocks(); @@ -187,21 +208,17 @@ describe('useKeypress', () => { description: 'Modern Node (>= v20)', setup: () => setNodeVersion('20.0.0'), isLegacy: false, + pasteWoraround: false, }, { - description: 'Legacy Node (< v20)', - setup: () => setNodeVersion('18.0.0'), - isLegacy: true, - }, - { - description: 'Workaround Env Var', + description: 'PasteWorkaround Environment Variable', setup: () => { setNodeVersion('20.0.0'); - vi.stubEnv('PASTE_WORKAROUND', 'true'); }, - isLegacy: true, + isLegacy: false, + pasteWoraround: true, }, - ])('in $description', ({ setup, isLegacy }) => { + ])('in $description', ({ setup, isLegacy, pasteWoraround }) => { beforeEach(() => { setup(); stdin.setLegacy(isLegacy); @@ -209,7 +226,7 @@ describe('useKeypress', () => { it('should process a paste as a single event', async () => { renderHook(() => useKeypress(onKeypress, { isActive: true }), { - wrapper, + wrapper: pasteWoraround ? wrapperWithWindowsWorkaround : wrapper, }); const pasteText = 'hello world'; act(() => stdin.paste(pasteText)); @@ -230,7 +247,7 @@ describe('useKeypress', () => { it('should handle keypress interspersed with pastes', async () => { renderHook(() => useKeypress(onKeypress, { isActive: true }), { - wrapper, + wrapper: pasteWoraround ? wrapperWithWindowsWorkaround : wrapper, }); const keyA = { name: 'a', sequence: 'a' }; @@ -266,7 +283,9 @@ describe('useKeypress', () => { it('should emit partial paste content if unmounted mid-paste', async () => { const { unmount } = renderHook( () => useKeypress(onKeypress, { isActive: true }), - { wrapper }, + { + wrapper: pasteWoraround ? wrapperWithWindowsWorkaround : wrapper, + }, ); const pasteText = 'incomplete paste';