You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 21:53:19