如何在 Jest 中使用 requestAnimationFrame 测试延迟事件处理程序?

How to test a delayed event handler with requestAnimationFrame in Jest?

我有一个函数应该在不久的将来执行,我想开玩笑地测试它,但不知道如何正确地执行此操作。实际上,我正在使用 React + testing react library 但这并没有改变我想的任何东西。
这是函数本身:

let start;
const holdClick = (el, delayedClickHandler, time = 0) => {
  if (time === 0) return delayedClickHandler();
  let counter = 0;
  function countToExecute() {
    if (counter === time) delayedClickHandler();
    else {
      start = requestAnimationFrame(countToExecute);
      counter += 1;
    }
  }

  start = window.requestAnimationFrame(countToExecute);

  el.ontouchend = () => {
    window.cancelAnimationFrame(start);
  };
};

我的组件

function Component() {
  const [clicked, setClicked] = useState(false);

  return (
    <span
      // data attribute must be changed to "active" after 30 ticks
      data-testid={clicked ? 'active' : 'static'}
      onTouchStart={e => holdClick(e.target, () => setClicked(true), 30)}
    >
      click me
    </span>
  );
}

还有我的测试

test('execute delayed click', () => {
  beforeEach(() => {
    jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => cb());
  });

  const { getByTestId } = render(<App />);
  const el = getByTestId('static');

  fireEvent.click(el);

  jest.useFakeTimers();
  setTimeout(() => {
    expect(el).toHaveAttribute('data-testid', 'active');
  }, 10000);
  jest.runAllTimers();
});

我的猜测是问题出在 requestAnimationFrame 但正如我之前提到的,我几乎不知道有什么方法可以解决这个问题。我试图添加 jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => cb()); 但它没有改变任何东西。 所以我想知道是否有不需要麻烦的 window 对象模拟的解决方案。

首先,您的回调是在 onTouchStart 道具上设置的,这意味着您需要触发 touchStart 事件才能触发它。

fireEvent.touchStart(el);

其次,在您的 countToExecute 函数中,counter 变量应在 调用 requestAnimationFrame 之前增加 ,否则 counter永远不会增加。这是因为实际 requestAnimationFrame 会在每次重绘(每个“tick”)时调用其回调,但由于它在测试中被模拟,因此回调会立即被调用。

function countToExecute() {
    if (counter === time) {
        delayedClickHandler();
    } else {
      counter += 1;
      start = requestAnimationFrame(countToExecute);
    }
}

您的测试应该如下所示:

test('execute delayed click', () => {
    jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => cb());
    const { getByTestId } = render(<MyComponent />);
    const el = getByTestId('static');
    fireEvent.touchStart(el);
    expect(el).toHaveAttribute('data-testid', 'active');
});