react-testing-library组件重渲染等待及输入掩码测试异常解决
问题
我有一个用于输入特定格式数据的组件,数据格式会根据输入字符数切换两种模式:当字符数≤11时,使用一种掩码;输入第12位字符时,通过setMask()切换为另一种掩码,切换逻辑在onKeyDown事件中触发。
该组件人工交互时运行正常,但在Storybook故事的play函数中执行测试代码时,输入的第12位数字会触发掩码切换,但数字未显示在输入框中,导致测试失败。请问是组件代码存在问题,还是需要调整测试写法?
测试代码
export const CNPJTaxId: Story = { args: { name: 'cnpj', label: 'CNPJ', }, render: Render, play: async ({ canvasElement }) => { const canvas = within(canvasElement); await userEvent.type( canvas.getByLabelText('CNPJ'), '12345678000195', { delay: 125 } ); await userEvent.click(canvas.getByRole('button')); expect(canvas.getByText(/12345678000195/gi)).toBeTruthy(); }, }
组件相关事件代码
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => { if (event.nativeEvent.key.match(/^\d$/) || event.code === 'Backspace') { const currLength = (event.target as HTMLInputElement).value.replace(/[^\d]/g, '').length; if (currLength === 11 && event.nativeEvent.key.match(/^\d$/)) { setMask(TAX_ID_MASK.CNPJ); } else if (currLength === 12 && event.code === 'Backspace') { setMask(TAX_ID_MASK.CPF); event.preventDefault(); event.stopPropagation(); } } }; const handleKeyUp = (event: KeyboardEvent<HTMLInputElement>) => { const currLength = (event.target as HTMLInputElement).value.replace(/[^\d]/g, '').length; if (currLength === 11 && event.code === 'Delete') { setMask(TAX_ID_MASK.CPF); } };
分析与解决方案
组件代码问题
核心问题出在掩码切换的时机上:
onKeyDown触发时,输入框的当前值还未包含刚按下的数字。输入第12位数字时,此时计算的currLength是11,会触发setMask切换掩码。- 掩码切换会导致输入框重新渲染或值被格式化,干扰了后续字符插入的流程(字符插入通常在
keyUp阶段完成),导致第12位数字丢失。
调整后的组件代码
将掩码切换逻辑移到onKeyUp事件中,此时输入框的值已经更新为包含刚输入的数字,计算长度更准确,也不会干扰字符插入:
const handleKeyUp = (event: KeyboardEvent<HTMLInputElement>) => { const inputEl = event.target as HTMLInputElement; const currLength = inputEl.value.replace(/[^\d]/g, '').length; // 输入数字后切换掩码 if (event.nativeEvent.key.match(/^\d$/)) { if (currLength === 12) { setMask(TAX_ID_MASK.CNPJ); } else if (currLength <= 11) { setMask(TAX_ID_MASK.CPF); } } // Backspace删除后切换掩码 else if (event.code === 'Backspace') { if (currLength === 11) { setMask(TAX_ID_MASK.CPF); } } // Delete删除后切换掩码 else if (event.code === 'Delete') { if (currLength === 11) { setMask(TAX_ID_MASK.CPF); } } }; // 仅保留keydown阶段的必要逻辑,比如阻止非数字输入 const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => { if (!event.nativeEvent.key.match(/^\d$/) && event.code !== 'Backspace' && event.code !== 'Delete') { event.preventDefault(); } };
测试代码优化
修复组件代码后,原测试基本可以正常运行,也可以做小调整让测试更贴近真实交互:
export const CNPJTaxId: Story = { args: { name: 'cnpj', label: 'CNPJ', }, render: Render, play: async ({ canvasElement }) => { const canvas = within(canvasElement); const input = canvas.getByLabelText('CNPJ'); await userEvent.type(input, '12345678000195', { delay: 125 }); await userEvent.click(canvas.getByRole('button')); // 若需断言输入框原始值,可替换为以下代码 // expect(input.value.replace(/[^\d]/g, '')).toBe('12345678000195'); expect(canvas.getByText(/12345678000195/gi)).toBeInTheDocument(); }, }
内容的提问来源于stack exchange,提问作者Diego Bevilaqua
相关产品推荐
相关产品推荐

