Commit Graph

10 Commits

Author SHA1 Message Date
Ricky
cd63ef7921
Add simulateEventDispatch to test ReactDOMEventListener (#28079)
## Overview

For events, the browser will yield to microtasks between calling event
handers, allowing time to flush work inbetween. For example, in the
browser, this code will log the flushes between events:

```js
<body onclick="console.log('body'); Promise.resolve().then(() => console.log('flush body'));">
  <div onclick="console.log('div'); Promise.resolve().then(() => console.log('flush div'));">
    hi
  </div>
</body>

// Logs
div 
flush div 
body 
flush body 
```


[Sandbox](https://codesandbox.io/s/eloquent-noether-mw2cjg?file=/index.html)

The problem is, `dispatchEvent` (either in the browser, or JSDOM) does
not yield to microtasks. Which means, this code will log the flushes
after the events:

```js
const target = document.getElementsByTagName("div")[0];
const nativeEvent = document.createEvent("Event");

nativeEvent.initEvent("click", true, true);
target.dispatchEvent(nativeEvent);

// Logs
div
body
flush div
flush body
```

## The problem
This mostly isn't a problem because React attaches event handler at the
root, and calls the event handlers on components via the synthetic event
system. We handle flushing between calling event handlers as needed.

However, if you're mixing capture and bubbling events, or using multiple
roots, then the problem of not flushing microtasks between events can
come into play. This was found when converting a test to `createRoot` in
https://github.com/facebook/react/pull/28050#discussion_r1462118422, and
that test is an example of where this is an issue with nested roots.

Here's a sandox for
[discrete](https://codesandbox.io/p/sandbox/red-http-2wg8k5) and
[continuous](https://codesandbox.io/p/sandbox/gracious-voice-6r7tsc?file=%2Fsrc%2Findex.js%3A25%2C28)
events, showing how the test should behave. The existing test, when
switched to `createRoot` matches the browser behavior for continuous
events, but not discrete. Continuous events should be batched, and
discrete should flush individually.

## The fix

This PR implements the fix suggested by @sebmarkbage, to manually
traverse the path up from the element and dispatch events, yielding
between each call.
2024-02-08 16:06:03 -05:00
Josh Story
657698e48d
[Tests] waitForThrow should diff strings (#26568)
Currently, `waitForThrow` tries to diff the expected value against the
thrown value if it doesn't match. However if the expectation is a
string, we are not diffing against the thrown message. This commit makes
it so if we are matching against message we also diff against message.
2023-04-07 14:14:08 -07:00
Andrew Clark
f0aafa1a7e
Convert a few more tests to waitFor test helpers (#26509)
Continuing my journey to migrate all the Scheduler flush* methods to
async versions of the same helpers.
2023-03-29 17:02:15 -04:00
Andrew Clark
e0bbc26623
Improve tests that deal with microtasks (#26493)
I rewrote some of our tests that deal with microtasks with the aim of
making them less coupled to implementation details. This is related to
an upcoming change to move update processing into a microtask.
2023-03-27 23:17:55 -04:00
Andrew Clark
8faf751937
Codemod some expiration tests to waitForExpired (#26491)
Continuing my journey to migrate all the Scheduler flush* methods to
async versions of the same helpers.

`unstable_flushExpired` is a rarely used helper that is only meant to be
used to test a very particular implementation detail (update starvation
prevention, or what we sometimes refer to as "expiration").

I've prefixed the new helper with `unstable_`, too, to indicate that our
tests should almost always prefer one of the other patterns instead.
2023-03-27 15:52:25 -04:00
Andrew Clark
69fd78fe37
Update Float tests to check for specific errors (#26367)
I updated some of the Float tests that intentionally trigger an error to
assert on the specific error message, rather than swallow any errors
that may or may not happen.
2023-03-10 18:37:32 -05:00
Andrew Clark
44d3807945
Move internalAct to internal-test-utils package (#26344)
This is not a public API. We only use it for our internal tests, the
ones in this repo. Let's move it to this private package. Practically
speaking this will also let us use async/await in the implementation.
2023-03-08 12:58:31 -05:00
Andrew Clark
1528c5ccdf
SchedulerMock.unstable_yieldValue -> SchedulerMock.log (#26312)
(This only affects our own internal repo; it's not a public API.)

I think most of us agree this is a less confusing name. It's possible
someone will confuse it with `console.log`. If that becomes a problem we
can warn in dev or something.
2023-03-06 11:09:07 -05:00
Andrew Clark
9a52cc8bcd
Convert ReactLazy-test to waitFor pattern (#26304)
I'm in the process of codemodding our test suite to the waitFor pattern.
See #26285 for full context.

This module required a lot of manual changes so I'm doing it as its own
PR. The reason is that most of the tests involved simulating an async
import by wrapping them in `Promise.resolve()`, which means they would
immediately resolve the next time the microtask queue was flushed. I
rewrote the tests to resolve the simulated import explicitly.

While converting these tests, I also realized that the `waitFor` helpers
weren't properly waiting for the entire microtask queue to recursively
finish — if a microtask schedules another microtask, the subsequent one
wouldn't fire until after `waitFor` had resolved. To fix this, I used
the same strategy as `act` — wait for a real task to finish before
proceeding, such as a message event.
2023-03-04 11:44:41 -05:00
Andrew Clark
e524467338
New internal testing helpers: waitFor, waitForAll, waitForPaint (#26285)
Over the years, we've gradually aligned on a set of best practices for
for testing concurrent React features in this repo. The default in most
cases is to use `act`, the same as you would do when testing a real
React app. However, because we're testing React itself, as opposed to an
app that uses React, our internal tests sometimes need to make
assertions on intermediate states that `act` intentionally disallows.

For those cases, we built a custom set of Jest assertion matchers that
provide greater control over the concurrent work queue. It works by
mocking the Scheduler package. (When we eventually migrate to using
native postTask, it would probably work by stubbing that instead.)

A problem with these helpers that we recently discovered is, because
they are synchronous function calls, they aren't sufficient if the work
you need to flush is scheduled in a microtask — we don't control the
microtask queue, and can't mock it.

`act` addresses this problem by encouraging you to await the result of
the `act` call. (It's not currently required to await, but in future
versions of React it likely will be.) It will then continue flushing
work until both the microtask queue and the Scheduler queue is
exhausted.

We can follow a similar strategy for our custom test helpers, by
replacing the current set of synchronous helpers with a corresponding
set of async ones:

- `expect(Scheduler).toFlushAndYield(log)` -> `await waitForAll(log)`
- `expect(Scheduler).toFlushAndYieldThrough(log)` -> `await
waitFor(log)`
- `expect(Scheduler).toFlushUntilNextPaint(log)` -> `await
waitForPaint(log)`

These APIs are inspired by the existing best practice for writing e2e
React tests. Rather than mock all task queues, in an e2e test you set up
a timer loop and wait for the UI to match an expecte condition. Although
we are mocking _some_ of the task queues in our tests, the general
principle still holds: it makes it less likely that our tests will
diverge from real world behavior in an actual browser.

In this commit, I've implemented the new testing helpers and converted
one of the Suspense tests to use them. In subsequent steps, I'll codemod
the rest of our test suite.
2023-03-02 21:58:11 -05:00