fix: try to fix test case failures on Windows

This commit is contained in:
tanzhenxin
2025-09-16 14:59:03 +08:00
parent c093bed38c
commit 35efa9f04a
4 changed files with 78 additions and 51 deletions

View File

@@ -127,9 +127,11 @@ interface AppProps {
export const AppWrapper = (props: AppProps) => { export const AppWrapper = (props: AppProps) => {
const kittyProtocolStatus = useKittyKeyboardProtocol(); const kittyProtocolStatus = useKittyKeyboardProtocol();
const nodeMajorVersion = parseInt(process.versions.node.split('.')[0], 10);
return ( return (
<KeypressProvider <KeypressProvider
kittyProtocolEnabled={kittyProtocolStatus.enabled} kittyProtocolEnabled={kittyProtocolStatus.enabled}
pasteWorkaround={process.platform === 'win32' || nodeMajorVersion < 20}
config={props.config} config={props.config}
> >
<SessionStatsProvider> <SessionStatsProvider>

View File

@@ -66,11 +66,16 @@ describe('KeypressContext - Kitty Protocol', () => {
const wrapper = ({ const wrapper = ({
children, children,
kittyProtocolEnabled = true, kittyProtocolEnabled = true,
pasteWorkaround = false,
}: { }: {
children: React.ReactNode; children: React.ReactNode;
kittyProtocolEnabled?: boolean; kittyProtocolEnabled?: boolean;
pasteWorkaround?: boolean;
}) => ( }) => (
<KeypressProvider kittyProtocolEnabled={kittyProtocolEnabled}> <KeypressProvider
kittyProtocolEnabled={kittyProtocolEnabled}
pasteWorkaround={pasteWorkaround}
>
{children} {children}
</KeypressProvider> </KeypressProvider>
); );
@@ -389,17 +394,15 @@ describe('KeypressContext - Kitty Protocol', () => {
}); });
describe('paste mode markers', () => { describe('paste mode markers', () => {
beforeEach(() => { // These tests use pasteWorkaround=true to force passthrough mode for raw keypress testing
// Force passthrough mode for raw keypress testing
vi.stubEnv('PASTE_WORKAROUND', '1');
});
it('should handle complete paste sequence with markers', async () => { it('should handle complete paste sequence with markers', async () => {
const keyHandler = vi.fn(); const keyHandler = vi.fn();
const pastedText = 'pasted content'; const pastedText = 'pasted content';
const { result } = renderHook(() => useKeypressContext(), { const { result } = renderHook(() => useKeypressContext(), {
wrapper, wrapper: ({ children }) =>
wrapper({ children, pasteWorkaround: true }),
}); });
act(() => { act(() => {
@@ -429,7 +432,8 @@ describe('KeypressContext - Kitty Protocol', () => {
const keyHandler = vi.fn(); const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { const { result } = renderHook(() => useKeypressContext(), {
wrapper, wrapper: ({ children }) =>
wrapper({ children, pasteWorkaround: true }),
}); });
act(() => { act(() => {
@@ -459,7 +463,8 @@ describe('KeypressContext - Kitty Protocol', () => {
const keyHandler = vi.fn(); const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { const { result } = renderHook(() => useKeypressContext(), {
wrapper, wrapper: ({ children }) =>
wrapper({ children, pasteWorkaround: true }),
}); });
act(() => { act(() => {
@@ -515,7 +520,8 @@ describe('KeypressContext - Kitty Protocol', () => {
const keyHandler = vi.fn(); const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { const { result } = renderHook(() => useKeypressContext(), {
wrapper, wrapper: ({ children }) =>
wrapper({ children, pasteWorkaround: true }),
}); });
act(() => { act(() => {
@@ -554,7 +560,8 @@ describe('KeypressContext - Kitty Protocol', () => {
const keyHandler = vi.fn(); const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { const { result } = renderHook(() => useKeypressContext(), {
wrapper, wrapper: ({ children }) =>
wrapper({ children, pasteWorkaround: true }),
}); });
act(() => { act(() => {
@@ -658,7 +665,8 @@ describe('KeypressContext - Kitty Protocol', () => {
const keyHandler = vi.fn(); const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { const { result } = renderHook(() => useKeypressContext(), {
wrapper, wrapper: ({ children }) =>
wrapper({ children, pasteWorkaround: true }),
}); });
act(() => { act(() => {
@@ -689,7 +697,8 @@ describe('KeypressContext - Kitty Protocol', () => {
const multilineContent = 'line1\nline2\nline3'; const multilineContent = 'line1\nline2\nline3';
const { result } = renderHook(() => useKeypressContext(), { const { result } = renderHook(() => useKeypressContext(), {
wrapper, wrapper: ({ children }) =>
wrapper({ children, pasteWorkaround: true }),
}); });
act(() => { act(() => {
@@ -721,7 +730,8 @@ describe('KeypressContext - Kitty Protocol', () => {
const keyHandler = vi.fn(); const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { const { result } = renderHook(() => useKeypressContext(), {
wrapper, wrapper: ({ children }) =>
wrapper({ children, pasteWorkaround: true }),
}); });
act(() => { act(() => {
@@ -754,7 +764,7 @@ describe('KeypressContext - Kitty Protocol', () => {
const keyHandler = vi.fn(); const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { const { result } = renderHook(() => useKeypressContext(), {
wrapper, wrapper: ({ children }) => wrapper({ children, pasteWorkaround: true }),
}); });
act(() => { act(() => {
@@ -789,17 +799,14 @@ describe('KeypressContext - Kitty Protocol', () => {
}); });
describe('Raw keypress pipeline', () => { describe('Raw keypress pipeline', () => {
beforeEach(() => { // These tests use pasteWorkaround=true to force passthrough mode for raw keypress testing
// Force passthrough mode for raw keypress testing
vi.stubEnv('PASTE_WORKAROUND', '1');
});
it('should buffer input data and wait for timeout', () => { it('should buffer input data and wait for timeout', () => {
vi.useFakeTimers(); vi.useFakeTimers();
const keyHandler = vi.fn(); const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { const { result } = renderHook(() => useKeypressContext(), {
wrapper, wrapper: ({ children }) => wrapper({ children, pasteWorkaround: true }),
}); });
act(() => { act(() => {
@@ -829,7 +836,7 @@ describe('KeypressContext - Kitty Protocol', () => {
const keyHandler = vi.fn(); const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { const { result } = renderHook(() => useKeypressContext(), {
wrapper, wrapper: ({ children }) => wrapper({ children, pasteWorkaround: true }),
}); });
act(() => { act(() => {
@@ -892,7 +899,7 @@ describe('KeypressContext - Kitty Protocol', () => {
const keyHandler = vi.fn(); const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { const { result } = renderHook(() => useKeypressContext(), {
wrapper, wrapper: ({ children }) => wrapper({ children, pasteWorkaround: true }),
}); });
act(() => { act(() => {
@@ -934,7 +941,7 @@ describe('KeypressContext - Kitty Protocol', () => {
const keyHandler = vi.fn(); const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { const { result } = renderHook(() => useKeypressContext(), {
wrapper, wrapper: ({ children }) => wrapper({ children, pasteWorkaround: true }),
}); });
act(() => { act(() => {
@@ -987,7 +994,7 @@ describe('KeypressContext - Kitty Protocol', () => {
const keyHandler = vi.fn(); const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { const { result } = renderHook(() => useKeypressContext(), {
wrapper, wrapper: ({ children }) => wrapper({ children, pasteWorkaround: true }),
}); });
act(() => { act(() => {
@@ -1036,7 +1043,7 @@ describe('KeypressContext - Kitty Protocol', () => {
const keyHandler = vi.fn(); const keyHandler = vi.fn();
const { result } = renderHook(() => useKeypressContext(), { const { result } = renderHook(() => useKeypressContext(), {
wrapper, wrapper: ({ children }) => wrapper({ children, pasteWorkaround: true }),
}); });
act(() => { act(() => {

View File

@@ -69,10 +69,12 @@ export function useKeypressContext() {
export function KeypressProvider({ export function KeypressProvider({
children, children,
kittyProtocolEnabled, kittyProtocolEnabled,
pasteWorkaround = false,
config, config,
}: { }: {
children: React.ReactNode; children?: React.ReactNode;
kittyProtocolEnabled: boolean; kittyProtocolEnabled: boolean;
pasteWorkaround?: boolean;
config?: Config; config?: Config;
}) { }) {
const { stdin, setRawMode } = useStdin(); const { stdin, setRawMode } = useStdin();
@@ -97,18 +99,8 @@ export function KeypressProvider({
const keypressStream = new PassThrough(); const keypressStream = new PassThrough();
let usePassthrough = false; let usePassthrough = false;
const nodeMajorVersion = parseInt(process.versions.node.split('.')[0], 10); // Use passthrough mode when pasteWorkaround is enabled,
const isWindows = process.platform === 'win32'; if (pasteWorkaround) {
// 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'
) {
usePassthrough = true; usePassthrough = true;
} }
@@ -500,7 +492,14 @@ export function KeypressProvider({
pasteBuffer = Buffer.alloc(0); pasteBuffer = Buffer.alloc(0);
} }
}; };
}, [stdin, setRawMode, kittyProtocolEnabled, config, subscribers]); }, [
stdin,
setRawMode,
kittyProtocolEnabled,
pasteWorkaround,
config,
subscribers,
]);
return ( return (
<KeypressContext.Provider value={{ subscribe, unsubscribe }}> <KeypressContext.Provider value={{ subscribe, unsubscribe }}>

View File

@@ -105,7 +105,28 @@ describe('useKeypress', () => {
let originalNodeVersion: string; let originalNodeVersion: string;
const wrapper = ({ children }: { children: React.ReactNode }) => 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(() => { beforeEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
@@ -187,21 +208,17 @@ describe('useKeypress', () => {
description: 'Modern Node (>= v20)', description: 'Modern Node (>= v20)',
setup: () => setNodeVersion('20.0.0'), setup: () => setNodeVersion('20.0.0'),
isLegacy: false, isLegacy: false,
pasteWoraround: false,
}, },
{ {
description: 'Legacy Node (< v20)', description: 'PasteWorkaround Environment Variable',
setup: () => setNodeVersion('18.0.0'),
isLegacy: true,
},
{
description: 'Workaround Env Var',
setup: () => { setup: () => {
setNodeVersion('20.0.0'); 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(() => { beforeEach(() => {
setup(); setup();
stdin.setLegacy(isLegacy); stdin.setLegacy(isLegacy);
@@ -209,7 +226,7 @@ describe('useKeypress', () => {
it('should process a paste as a single event', async () => { it('should process a paste as a single event', async () => {
renderHook(() => useKeypress(onKeypress, { isActive: true }), { renderHook(() => useKeypress(onKeypress, { isActive: true }), {
wrapper, wrapper: pasteWoraround ? wrapperWithWindowsWorkaround : wrapper,
}); });
const pasteText = 'hello world'; const pasteText = 'hello world';
act(() => stdin.paste(pasteText)); act(() => stdin.paste(pasteText));
@@ -230,7 +247,7 @@ describe('useKeypress', () => {
it('should handle keypress interspersed with pastes', async () => { it('should handle keypress interspersed with pastes', async () => {
renderHook(() => useKeypress(onKeypress, { isActive: true }), { renderHook(() => useKeypress(onKeypress, { isActive: true }), {
wrapper, wrapper: pasteWoraround ? wrapperWithWindowsWorkaround : wrapper,
}); });
const keyA = { name: 'a', sequence: 'a' }; const keyA = { name: 'a', sequence: 'a' };
@@ -266,7 +283,9 @@ describe('useKeypress', () => {
it('should emit partial paste content if unmounted mid-paste', async () => { it('should emit partial paste content if unmounted mid-paste', async () => {
const { unmount } = renderHook( const { unmount } = renderHook(
() => useKeypress(onKeypress, { isActive: true }), () => useKeypress(onKeypress, { isActive: true }),
{ wrapper }, {
wrapper: pasteWoraround ? wrapperWithWindowsWorkaround : wrapper,
},
); );
const pasteText = 'incomplete paste'; const pasteText = 'incomplete paste';