* Move renderer `act` to work loop
* Delete `flushSuspenseFallbacksInTests`
This was meant to be a temporary hack to unblock the `act` work, but it
quickly spread throughout our tests.
What it's meant to do is force fallbacks to flush inside `act` even in
Concurrent Mode. It does this by wrapping the `setTimeout` call in a
check to see if it's in an `act` context. If so, it skips the delay and
immediately commits the fallback.
Really this is only meant for our internal React tests that need to
incrementally render. Nobody outside our team (and Relay) needs to do
that, yet. Even if/when we do support that, it may or may not be with
the same `flushAndYield` pattern we use internally.
However, even for our internal purposes, the behavior isn't right
because a really common reason we flush work incrementally is to make
assertions on the "suspended" state, before the fallback has committed.
There's no way to do that from inside `act` with the behavior of this
flag, because it causes the fallback to immediately commit. This has led
us to *not* use `act` in a lot of our tests, or to write code that
doesn't match what would actually happen in a real environment.
What we really want is for the fallbacks to be flushed at the *end` of
the `act` scope. Not within it.
This only affects the noop and test renderer versions of `act`, which
are implemented inside the reconciler. Whereas `ReactTestUtils.act` is
implemented in "userspace" for backwards compatibility. This is fine
because we didn't have any DOM Suspense tests that relied on this flag;
they all use test renderer or noop.
In the future, we'll probably want to move always use the reconciler
implementation of `act`. It will not affect the prod bundle, because we
currently only plan to support `act` in dev. Though we still haven't
completely figured that out. However, regardless of whether we support a
production `act` for users, we'll still need to write internal React
tests in production mode. For that use case, we'll likely add our own
internal version of `act` that assumes a mock Scheduler and might rely
on hacks that don't 100% align up with the public one.
* Migrate conditional tests to gate pragma
I searched through the codebase for this pattern:
```js
describe('test suite', () => {
if (!__EXPERIMENTAL__) { // or some other condition
test("empty test so Jest doesn't complain", () => {});
return;
}
// Unless we're in experimental mode, none of the tests in this block
// will run.
})
```
and converted them to the `@gate` pragma instead.
The reason this pattern isn't preferred is because you end up disabling
more tests than you need to.
* Add flag for www release channels
Using a heuristic where I check a flag that is known to only be enabled
in www. I left a TODO to instead set the release channel explicitly in
each test config.
* Add more edge cases to fixture
Also adjust some expectations. I think the column should ideally be 1 but varies.
The Example row is one line off because it throws on the hook but should ideally be the component.
Similarly class components with constructors may have the line in the constructor.
* Account for the construct call taking a stack frame
We do this by first searching for the first different frame, then find
the same frames and then find the first different frame again.
* Throw controls
Otherwise they don't get a stack frame associated with them in IE.
* Protect against generating stacks failing
Errors while generating stacks will bubble to the root. Since this technique
is a bit sketchy, we should probably protect against it.
* Don't construct the thing that throws
Instead, we pass the prototype as the "this". It's new every time anyway.
* Implement component stack extraction hack
* Normalize errors in tests
This drops the requirement to include owner to pass the test.
* Special case tests
* Add destructuring to force toObject which throws before the side-effects
This ensures that we don't double call yieldValue or advanceTime in tests.
Ideally we could use empty destructuring but ES lint doesn't like it.
* Cache the result in DEV
In DEV it's somewhat likely that we'll see many logs that add component
stacks. This could be slow so we cache the results of previous components.
* Fixture
* Add Reflect to lint
* Log if out of range.
* Fix special case when the function call throws in V8
In V8 we need to ignore the first line. Normally we would never get there
because the stacks would differ before that, but the stacks are the same if
we end up throwing at the same place as the control.
* Remove unnecessary workInProgress line
* Mutate workInProgress instead of returning
We were ambivalent about this before.
* Make handleError a void method too
We currently use the expiration time to represent the timeout of a
transition. Since we intend to stop treating work priority as a
timeline, we can no longer use this trick.
In this commit, I've changed it to store the event time on the update
object instead. Long term, we will store event time on the root as a map
of transition -> event time. I'm only storing it on the update object
as a temporary workaround to unblock the rest of the changes.
We can't patch the row. We could give these their own "built-in" stack
frame since they're conceptually HoCs. However, from a debugging
perspective this is not very useful meta data and quite noisy. So I'm
just going to exclude them.
All changes in this commit were generated by the following commands.
Copy each module that ends with `.old` to a new file that ends
with `.new`:
```sh
for f in packages/react-reconciler/src/*.old.js; do cp "$f" "$(echo "$f" | sed s/\.old/\.new/)"; done
```
Then transform the internal imports:
```sh
grep -rl --include="*.new.js" '.old' packages/react-reconciler/src/| xargs sed -i '' "s/\.old\'/\.new\'/g"
```
Some of our internal reconciler types have leaked into other packages.
Usually, these types are treated as opaque; we don't read and write
to its fields. This is good.
However, the type is often passed back to a reconciler method. For
example, React DOM creates a FiberRoot with `createContainer`, then
passes that root to `updateContainer`. It doesn't do anything with the
root except pass it through, but because `updateContainer` expects a
full FiberRoot, React DOM is still coupled to all its fields.
I don't know if there's an idiomatic way to handle this in Flow. Opaque
types are simlar, but those only work within a single file. AFAIK,
there's no way to use a package as the boundary for opaqueness.
The immediate problem this presents is that the reconciler refactor will
involve changes to our internal data structures. I don't want to have to
fork every single package that happens to pass through a Fiber or
FiberRoot, or access any one of its fields. So my current plan is to
share the same Flow type across both forks. The shared type will be a
superset of each implementation's type, e.g. Fiber will have both an
`expirationTime` field and a `lanes` field. The implementations will
diverge, but not the types.
To do this, I lifted the type definitions into a separate module.
* Drop the .internal.js suffix on some files that don't need it anymore
* Port some ops patterns to scheduler yield
* Fix triangle test to avoid side-effects in constructor
* Move replaying of setState updaters until after the effect
Otherwise any warnings get silenced if they're deduped.
* Drop .internal.js in more files
* Don't check propTypes on a simple memo component unless it's lazy
Comparing the elementType doesn't work for this because it will never be
the same for a simple element.
This caused us to double validate these. This was covered up because in
internal tests this was deduped since they shared the prop types cache
but since we now inline it, it doesn't get deduped.
* Don't use closures in DevTools injection
Nested closures are tricky. They're not super efficient and when they share
scope between multiple closures they're hard for a compiler to optimize.
It's also unclear how many versions will be created.
By hoisting things out an just make it simple calls the compiler can do
a much better job.
* Store injected hook to work around fast refresh
* Disable console log during the second rerender
* Use the disabled log to avoid double yielding values in scheduler mock
* Reenable debugRenderPhaseSideEffectsForStrictMode in tests that can
* Add failing tests for lazy components
* Fix bailout broken in lazy components due to default props resolving
We should never compare unresolved props with resolved props. Since comparing
resolved props by reference doesn't make sense, we use unresolved props in that
case. Otherwise, resolved props are used.
* Avoid reassigning props warning when we bailout
* Bugfix: Render phase update leads to dropped work
Render phase updates should not affect the `fiber.expirationTime` field.
We don't have to set anything on the fiber because we're going to
process the render phase update immediately.
We also shouldn't reset the `expirationTime` field in between render
passes because it represents the remaining work left in the update
queues. During the re-render, the updates that were skipped in the
original pass are not processed again.
I think my original motivation for using this field for render phase
updates was so I didn't have to add another module level variable.
* Add repro case for #18486
Co-authored-by: Dan Abramov <dan.abramov@me.com>
* Add another test for #18515 using pings
Adds a regression test for the same underlying bug as #18515 but using
pings.
Test already passes, but I confirmed it fails if you revert the fix
in #18515.
* Set nextPendingLevel after commit, too
* Reproduce a bug where `flushDiscreteUpdates` causes fallback never to be committed
* Ping suspended level when canceling its timer
Make sure the suspended level is marked as pinged so that we return back
to it later, in case the render we're about to start gets aborted.
Generally we only reach this path via a ping, but we shouldn't assume
that will always be the case.
* Clear finished discrete updates during commit phase
If a root is finished at a priority lower than that of the latest pending discrete
updates on it, these updates must have been finished so we can clear them now.
Otherwise, a later call of `flushDiscreteUpdates` would start a new empty render
pass which may cause a scheduled timeout to be cancelled.
* Add TODO
Happened to find this while writing a test. A JSX element comparison
failed because one of them elements had a functional component as an
owner, which should ever happen.
I'll add a regression test later.
Co-authored-by: Andrew Clark <git@andrewclark.io>