Compare commits
2 Commits
eslint-plu
...
gh/mofeiZ/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56cb408bd2 | ||
|
|
616d45cf28 |
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {render, fireEvent} from '@testing-library/react';
|
||||
import * as React from 'react';
|
||||
import {useState} from 'react';
|
||||
|
||||
type Data = {
|
||||
isDataFetched: boolean;
|
||||
fieldData: {
|
||||
username: {
|
||||
errorMessage: string | null;
|
||||
value: string | null;
|
||||
};
|
||||
address: {
|
||||
errorMessage: string | null;
|
||||
value: string | null;
|
||||
};
|
||||
};
|
||||
};
|
||||
function useRoRViolation(): [
|
||||
Data,
|
||||
(
|
||||
field: 'username' | 'address',
|
||||
kind: 'errorMessage' | 'value',
|
||||
newValue: string,
|
||||
) => void,
|
||||
] {
|
||||
'use no forget';
|
||||
const [count, setCount] = useState<number>(0);
|
||||
const [state, setState] = useState<Data>({
|
||||
isDataFetched: false,
|
||||
fieldData: {
|
||||
username: {
|
||||
errorMessage: null,
|
||||
value: null,
|
||||
},
|
||||
address: {
|
||||
errorMessage: null,
|
||||
value: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
const update = (
|
||||
field: 'username' | 'address',
|
||||
kind: 'errorMessage' | 'value',
|
||||
) => {
|
||||
setState((prevState: Data) => {
|
||||
const newState = {...prevState};
|
||||
const data =
|
||||
field === 'username'
|
||||
? newState.fieldData.username
|
||||
: newState.fieldData.address;
|
||||
if (kind === 'errorMessage') {
|
||||
data.errorMessage = `value${count}`;
|
||||
} else {
|
||||
data.errorMessage = `value${count}`;
|
||||
}
|
||||
return newState;
|
||||
});
|
||||
setCount(count + 1);
|
||||
};
|
||||
|
||||
return [state, update];
|
||||
}
|
||||
|
||||
function Component({
|
||||
label,
|
||||
showFields,
|
||||
}: {
|
||||
label: keyof Data['fieldData'];
|
||||
showFields: boolean;
|
||||
}) {
|
||||
const [state, update] = useRoRViolation();
|
||||
function readState(label: keyof Data['fieldData']) {
|
||||
return state.fieldData[label];
|
||||
}
|
||||
const field = readState(label);
|
||||
return (
|
||||
<>
|
||||
{showFields ? (
|
||||
<div>
|
||||
{field.errorMessage ?? '(none)'} {field.value ?? '(none)'}
|
||||
</div>
|
||||
) : (
|
||||
<div>no data</div>
|
||||
)}
|
||||
<button
|
||||
data-testid="state_update"
|
||||
onClick={() => update(label, 'errorMessage', 'new value')}></button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
test('update-button', () => {
|
||||
const label: 'username' = 'username';
|
||||
const {asFragment, rerender, getByTestId} = render(
|
||||
<Component label={label} showFields={true} />,
|
||||
);
|
||||
|
||||
expect(asFragment()).toMatchInlineSnapshot(`
|
||||
<DocumentFragment>
|
||||
<div>
|
||||
(none) (none)
|
||||
</div>
|
||||
<button
|
||||
data-testid="state_update"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`);
|
||||
|
||||
fireEvent.click(getByTestId('state_update'));
|
||||
|
||||
if (__FORGET__) {
|
||||
/**
|
||||
* Now we have a reactivity bug due to a rule-of-react violation!
|
||||
*/
|
||||
expect(asFragment()).toMatchInlineSnapshot(`
|
||||
<DocumentFragment>
|
||||
<div>
|
||||
(none) (none)
|
||||
</div>
|
||||
<button
|
||||
data-testid="state_update"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`);
|
||||
} else {
|
||||
expect(asFragment()).toMatchInlineSnapshot(`
|
||||
<DocumentFragment>
|
||||
<div>
|
||||
value0 (none)
|
||||
</div>
|
||||
<button
|
||||
data-testid="state_update"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {CONST_TRUE, Stringify, mutate, useIdentity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Fixture showing an edge case for ReactiveScope variable propagation.
|
||||
*
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* <div>{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}</div>
|
||||
* <div>{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}</div>
|
||||
* Forget:
|
||||
* <div>{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}</div>
|
||||
* [[ (exception in render) Error: invariant broken ]]
|
||||
*
|
||||
*/
|
||||
function Component() {
|
||||
const obj = CONST_TRUE ? {inner: {value: 'hello'}} : null;
|
||||
const boxedInner = [obj?.inner];
|
||||
useIdentity(null);
|
||||
mutate(obj);
|
||||
if (boxedInner[0] !== obj?.inner) {
|
||||
throw new Error('invariant broken');
|
||||
}
|
||||
return <Stringify obj={obj} inner={boxedInner} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{arg: 0}],
|
||||
sequentialRenders: [{arg: 0}, {arg: 1}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { CONST_TRUE, Stringify, mutate, useIdentity } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Fixture showing an edge case for ReactiveScope variable propagation.
|
||||
*
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* <div>{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}</div>
|
||||
* <div>{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}</div>
|
||||
* Forget:
|
||||
* <div>{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}</div>
|
||||
* [[ (exception in render) Error: invariant broken ]]
|
||||
*
|
||||
*/
|
||||
function Component() {
|
||||
const $ = _c(4);
|
||||
const obj = CONST_TRUE ? { inner: { value: "hello" } } : null;
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = [obj?.inner];
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
const boxedInner = t0;
|
||||
useIdentity(null);
|
||||
mutate(obj);
|
||||
if (boxedInner[0] !== obj?.inner) {
|
||||
throw new Error("invariant broken");
|
||||
}
|
||||
let t1;
|
||||
if ($[1] !== obj || $[2] !== boxedInner) {
|
||||
t1 = <Stringify obj={obj} inner={boxedInner} />;
|
||||
$[1] = obj;
|
||||
$[2] = boxedInner;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ arg: 0 }],
|
||||
sequentialRenders: [{ arg: 0 }, { arg: 1 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import {CONST_TRUE, Stringify, mutate, useIdentity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Fixture showing an edge case for ReactiveScope variable propagation.
|
||||
*
|
||||
* Found differences in evaluator results
|
||||
* Non-forget (expected):
|
||||
* <div>{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}</div>
|
||||
* <div>{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}</div>
|
||||
* Forget:
|
||||
* <div>{"obj":{"inner":{"value":"hello"},"wat0":"joe"},"inner":["[[ cyclic ref *2 ]]"]}</div>
|
||||
* [[ (exception in render) Error: invariant broken ]]
|
||||
*
|
||||
*/
|
||||
function Component() {
|
||||
const obj = CONST_TRUE ? {inner: {value: 'hello'}} : null;
|
||||
const boxedInner = [obj?.inner];
|
||||
useIdentity(null);
|
||||
mutate(obj);
|
||||
if (boxedInner[0] !== obj?.inner) {
|
||||
throw new Error('invariant broken');
|
||||
}
|
||||
return <Stringify obj={obj} inner={boxedInner} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{arg: 0}],
|
||||
sequentialRenders: [{arg: 0}, {arg: 1}],
|
||||
};
|
||||
@@ -481,6 +481,7 @@ const skipFilter = new Set([
|
||||
'bug-invalid-hoisting-functionexpr',
|
||||
'bug-try-catch-maybe-null-dependency',
|
||||
'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted',
|
||||
'bug-invalid-phi-as-dependency',
|
||||
'reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond',
|
||||
'original-reactive-scopes-fork/bug-nonmutating-capture-in-unsplittable-memo-block',
|
||||
'original-reactive-scopes-fork/bug-hoisted-declaration-with-scope',
|
||||
|
||||
Reference in New Issue
Block a user