@samitouri / QOS-React-2 / commits / 1515cb3218

[be][sprout] Add ErrorBoundary to test exceptions in sequentialRenders

Mofei Zhang committed Mar 27, 2024 at 20:26 UTC 1515cb32182e48c25369673607abb76ba773bab6
35 files changed +650 -269
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/cfg-condexpr.expect.md
+21 -13
@@ -6,11 +6,18 @@
6 // scope that produces x, since it is accessed unconditionally in all cfg
7 // paths
8
9 -function TestCondDepInConditionalExpr(props, other) {
10 - const x = foo(other) ? bar(props.a.b) : baz(props.a.b);
9 +import { identity, addOne } from "shared-runtime";
10 +
11 +function useCondDepInConditionalExpr(props, cond) {
12 + const x = identity(cond) ? addOne(props.a.b) : identity(props.a.b);
13 return x;
14 }
15
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: useCondDepInConditionalExpr,
18 + params: [{ a: { b: 2 } }, true],
19 +};
20 +
21 ```
22
23 ## Code
@@ -20,12 +27,14 @@ import { unstable_useMemoCache as useMemoCache } from "react"; // props.a.b shou
27 // scope that produces x, since it is accessed unconditionally in all cfg
28 // paths
29
23 -function TestCondDepInConditionalExpr(props, other) {
30 +import { identity, addOne } from "shared-runtime";
31 +
32 +function useCondDepInConditionalExpr(props, cond) {
33 const $ = useMemoCache(3);
34 let t0;
26 - if ($[0] !== other || $[1] !== props.a.b) {
27 - t0 = foo(other) ? bar(props.a.b) : baz(props.a.b);
28 - $[0] = other;
35 + if ($[0] !== cond || $[1] !== props.a.b) {
36 + t0 = identity(cond) ? addOne(props.a.b) : identity(props.a.b);
37 + $[0] = cond;
38 $[1] = props.a.b;
39 $[2] = t0;
40 } else {
@@ -35,13 +44,12 @@ function TestCondDepInConditionalExpr(props, other) {
44 return x;
45 }
46
47 +export const FIXTURE_ENTRYPOINT = {
48 + fn: useCondDepInConditionalExpr,
49 + params: [{ a: { b: 2 } }, true],
50 +};
51 +
52 ```
53
54 ### Eval output
41 -(kind: exception) Fixture not implemented!
42 -logs: ['The above error occurred in the <WrapperTestComponent> component:\n' +
43 - '\n' +
44 - ' at WrapperTestComponent (<project_root>/packages/snap/dist/sprout/evaluator.js:54:26)\n' +
45 - '\n' +
46 - 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
47 - 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.']
\ No newline at end of file
55 +(kind: ok) 3
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/cfg-condexpr.js
+9 -2
@@ -2,7 +2,14 @@
2 // scope that produces x, since it is accessed unconditionally in all cfg
3 // paths
4
5 -function TestCondDepInConditionalExpr(props, other) {
6 - const x = foo(other) ? bar(props.a.b) : baz(props.a.b);
5 +import { identity, addOne } from "shared-runtime";
6 +
7 +function useCondDepInConditionalExpr(props, cond) {
8 + const x = identity(cond) ? addOne(props.a.b) : identity(props.a.b);
9 return x;
10 }
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: useCondDepInConditionalExpr,
14 + params: [{ a: { b: 2 } }, true],
15 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/cfg-ifelse.expect.md
+21 -13
@@ -6,9 +6,11 @@
6 // scope that produces x, since it is accessed unconditionally in all cfg
7 // paths
8
9 -function TestCondDepInDirectIfElse(props, other) {
9 +import { identity } from "shared-runtime";
10 +
11 +function useCondDepInDirectIfElse(props, cond) {
12 const x = {};
11 - if (foo(other)) {
13 + if (identity(cond)) {
14 x.b = props.a.b;
15 } else {
16 x.c = props.a.b;
@@ -16,6 +18,11 @@ function TestCondDepInDirectIfElse(props, other) {
18 return x;
19 }
20
21 +export const FIXTURE_ENTRYPOINT = {
22 + fn: useCondDepInDirectIfElse,
23 + params: [{ a: { b: 2 } }, true],
24 +};
25 +
26 ```
27
28 ## Code
@@ -25,17 +32,19 @@ import { unstable_useMemoCache as useMemoCache } from "react"; // props.a.b shou
32 // scope that produces x, since it is accessed unconditionally in all cfg
33 // paths
34
28 -function TestCondDepInDirectIfElse(props, other) {
35 +import { identity } from "shared-runtime";
36 +
37 +function useCondDepInDirectIfElse(props, cond) {
38 const $ = useMemoCache(3);
39 let x;
31 - if ($[0] !== other || $[1] !== props.a.b) {
40 + if ($[0] !== cond || $[1] !== props.a.b) {
41 x = {};
33 - if (foo(other)) {
42 + if (identity(cond)) {
43 x.b = props.a.b;
44 } else {
45 x.c = props.a.b;
46 }
38 - $[0] = other;
47 + $[0] = cond;
48 $[1] = props.a.b;
49 $[2] = x;
50 } else {
@@ -44,13 +53,12 @@ function TestCondDepInDirectIfElse(props, other) {
53 return x;
54 }
55
56 +export const FIXTURE_ENTRYPOINT = {
57 + fn: useCondDepInDirectIfElse,
58 + params: [{ a: { b: 2 } }, true],
59 +};
60 +
61 ```
62
63 ### Eval output
50 -(kind: exception) Fixture not implemented!
51 -logs: ['The above error occurred in the <WrapperTestComponent> component:\n' +
52 - '\n' +
53 - ' at WrapperTestComponent (<project_root>/packages/snap/dist/sprout/evaluator.js:54:26)\n' +
54 - '\n' +
55 - 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
56 - 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.']
\ No newline at end of file
64 +(kind: ok) {"b":2}
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/cfg-ifelse.js
+9 -2
@@ -2,12 +2,19 @@
2 // scope that produces x, since it is accessed unconditionally in all cfg
3 // paths
4
5 -function TestCondDepInDirectIfElse(props, other) {
5 +import { identity } from "shared-runtime";
6 +
7 +function useCondDepInDirectIfElse(props, cond) {
8 const x = {};
7 - if (foo(other)) {
9 + if (identity(cond)) {
10 x.b = props.a.b;
11 } else {
12 x.c = props.a.b;
13 }
14 return x;
15 }
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: useCondDepInDirectIfElse,
19 + params: [{ a: { b: 2 } }, true],
20 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/cfg-nested-ifelse-missing.expect.md
+23 -15
@@ -5,10 +5,12 @@
5 // props.a.b should NOT be added as a unconditional dependency to the reactive
6 // scope that produces x if it is not accessed in every path
7
8 -function TestCondDepInNestedIfElse(props, other) {
8 +import { identity, getNull } from "shared-runtime";
9 +
10 +function useCondDepInNestedIfElse(props, cond) {
11 const x = {};
10 - if (foo(other)) {
11 - if (bar()) {
12 + if (identity(cond)) {
13 + if (getNull()) {
14 x.a = props.a.b;
15 }
16 } else {
@@ -17,6 +19,11 @@ function TestCondDepInNestedIfElse(props, other) {
19 return x;
20 }
21
22 +export const FIXTURE_ENTRYPOINT = {
23 + fn: useCondDepInNestedIfElse,
24 + params: [{ a: { b: 2 } }, true],
25 +};
26 +
27 ```
28
29 ## Code
@@ -25,19 +32,21 @@ function TestCondDepInNestedIfElse(props, other) {
32 import { unstable_useMemoCache as useMemoCache } from "react"; // props.a.b should NOT be added as a unconditional dependency to the reactive
33 // scope that produces x if it is not accessed in every path
34
28 -function TestCondDepInNestedIfElse(props, other) {
35 +import { identity, getNull } from "shared-runtime";
36 +
37 +function useCondDepInNestedIfElse(props, cond) {
38 const $ = useMemoCache(3);
39 let x;
31 - if ($[0] !== other || $[1] !== props) {
40 + if ($[0] !== cond || $[1] !== props) {
41 x = {};
33 - if (foo(other)) {
34 - if (bar()) {
42 + if (identity(cond)) {
43 + if (getNull()) {
44 x.a = props.a.b;
45 }
46 } else {
47 x.d = props.a.b;
48 }
40 - $[0] = other;
49 + $[0] = cond;
50 $[1] = props;
51 $[2] = x;
52 } else {
@@ -46,13 +55,12 @@ function TestCondDepInNestedIfElse(props, other) {
55 return x;
56 }
57
58 +export const FIXTURE_ENTRYPOINT = {
59 + fn: useCondDepInNestedIfElse,
60 + params: [{ a: { b: 2 } }, true],
61 +};
62 +
63 ```
64
65 ### Eval output
52 -(kind: exception) Fixture not implemented!
53 -logs: ['The above error occurred in the <WrapperTestComponent> component:\n' +
54 - '\n' +
55 - ' at WrapperTestComponent (<project_root>/packages/snap/dist/sprout/evaluator.js:54:26)\n' +
56 - '\n' +
57 - 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
58 - 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.']
\ No newline at end of file
66 +(kind: ok) {}
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/cfg-nested-ifelse-missing.js
+10 -3
@@ -1,10 +1,12 @@
1 // props.a.b should NOT be added as a unconditional dependency to the reactive
2 // scope that produces x if it is not accessed in every path
3
4 -function TestCondDepInNestedIfElse(props, other) {
4 +import { identity, getNull } from "shared-runtime";
5 +
6 +function useCondDepInNestedIfElse(props, cond) {
7 const x = {};
6 - if (foo(other)) {
7 - if (bar()) {
8 + if (identity(cond)) {
9 + if (getNull()) {
10 x.a = props.a.b;
11 }
12 } else {
@@ -12,3 +14,8 @@ function TestCondDepInNestedIfElse(props, other) {
14 }
15 return x;
16 }
17 +
18 +export const FIXTURE_ENTRYPOINT = {
19 + fn: useCondDepInNestedIfElse,
20 + params: [{ a: { b: 2 } }, true],
21 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/cfg-nested-ifelse.expect.md
+25 -17
@@ -6,15 +6,17 @@
6 // scope that produces x, since it is accessed unconditionally in all cfg
7 // paths
8
9 -function TestCondDepInNestedIfElse(props, other) {
9 +import { getNull, identity } from "shared-runtime";
10 +
11 +function useCondDepInNestedIfElse(props, cond) {
12 const x = {};
11 - if (foo(other)) {
12 - if (bar()) {
13 + if (identity(cond)) {
14 + if (getNull()) {
15 x.a = props.a.b;
16 } else {
17 x.b = props.a.b;
18 }
17 - } else if (baz(other)) {
19 + } else if (identity(cond)) {
20 x.c = props.a.b;
21 } else {
22 x.d = props.a.b;
@@ -22,6 +24,11 @@ function TestCondDepInNestedIfElse(props, other) {
24 return x;
25 }
26
27 +export const FIXTURE_ENTRYPOINT = {
28 + fn: useCondDepInNestedIfElse,
29 + params: [{ a: { b: 2 } }, true],
30 +};
31 +
32 ```
33
34 ## Code
@@ -31,25 +38,27 @@ import { unstable_useMemoCache as useMemoCache } from "react"; // props.a.b shou
38 // scope that produces x, since it is accessed unconditionally in all cfg
39 // paths
40
34 -function TestCondDepInNestedIfElse(props, other) {
41 +import { getNull, identity } from "shared-runtime";
42 +
43 +function useCondDepInNestedIfElse(props, cond) {
44 const $ = useMemoCache(3);
45 let x;
37 - if ($[0] !== other || $[1] !== props.a.b) {
46 + if ($[0] !== cond || $[1] !== props.a.b) {
47 x = {};
39 - if (foo(other)) {
40 - if (bar()) {
48 + if (identity(cond)) {
49 + if (getNull()) {
50 x.a = props.a.b;
51 } else {
52 x.b = props.a.b;
53 }
54 } else {
46 - if (baz(other)) {
55 + if (identity(cond)) {
56 x.c = props.a.b;
57 } else {
58 x.d = props.a.b;
59 }
60 }
52 - $[0] = other;
61 + $[0] = cond;
62 $[1] = props.a.b;
63 $[2] = x;
64 } else {
@@ -58,13 +67,12 @@ function TestCondDepInNestedIfElse(props, other) {
67 return x;
68 }
69
70 +export const FIXTURE_ENTRYPOINT = {
71 + fn: useCondDepInNestedIfElse,
72 + params: [{ a: { b: 2 } }, true],
73 +};
74 +
75 ```
76
77 ### Eval output
64 -(kind: exception) Fixture not implemented!
65 -logs: ['The above error occurred in the <WrapperTestComponent> component:\n' +
66 - '\n' +
67 - ' at WrapperTestComponent (<project_root>/packages/snap/dist/sprout/evaluator.js:54:26)\n' +
68 - '\n' +
69 - 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
70 - 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.']
\ No newline at end of file
78 +(kind: ok) {"b":2}
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/cfg-nested-ifelse.js
+11 -4
@@ -2,18 +2,25 @@
2 // scope that produces x, since it is accessed unconditionally in all cfg
3 // paths
4
5 -function TestCondDepInNestedIfElse(props, other) {
5 +import { getNull, identity } from "shared-runtime";
6 +
7 +function useCondDepInNestedIfElse(props, cond) {
8 const x = {};
7 - if (foo(other)) {
8 - if (bar()) {
9 + if (identity(cond)) {
10 + if (getNull()) {
11 x.a = props.a.b;
12 } else {
13 x.b = props.a.b;
14 }
13 - } else if (baz(other)) {
15 + } else if (identity(cond)) {
16 x.c = props.a.b;
17 } else {
18 x.d = props.a.b;
19 }
20 return x;
21 }
22 +
23 +export const FIXTURE_ENTRYPOINT = {
24 + fn: useCondDepInNestedIfElse,
25 + params: [{ a: { b: 2 } }, true],
26 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/cfg-switch-exhaustive.expect.md
+19 -11
@@ -6,9 +6,11 @@
6 // scope that produces x, since it is accessed unconditionally in all cfg
7 // paths
8
9 -function TestCondDepInSwitch(props, other) {
9 +import { identity } from "shared-runtime";
10 +
11 +function useCondDepInSwitch(props, other) {
12 const x = {};
11 - switch (foo(other)) {
13 + switch (identity(other)) {
14 case 1:
15 x.a = props.a.b;
16 break;
@@ -21,6 +23,11 @@ function TestCondDepInSwitch(props, other) {
23 return x;
24 }
25
26 +export const FIXTURE_ENTRYPOINT = {
27 + fn: useCondDepInSwitch,
28 + params: [{ a: { b: 2 } }, 2],
29 +};
30 +
31 ```
32
33 ## Code
@@ -30,12 +37,14 @@ import { unstable_useMemoCache as useMemoCache } from "react"; // props.a.b shou
37 // scope that produces x, since it is accessed unconditionally in all cfg
38 // paths
39
33 -function TestCondDepInSwitch(props, other) {
40 +import { identity } from "shared-runtime";
41 +
42 +function useCondDepInSwitch(props, other) {
43 const $ = useMemoCache(3);
44 let x;
45 if ($[0] !== other || $[1] !== props.a.b) {
46 x = {};
38 - bb1: switch (foo(other)) {
47 + bb1: switch (identity(other)) {
48 case 1: {
49 x.a = props.a.b;
50 break bb1;
@@ -57,13 +66,12 @@ function TestCondDepInSwitch(props, other) {
66 return x;
67 }
68
69 +export const FIXTURE_ENTRYPOINT = {
70 + fn: useCondDepInSwitch,
71 + params: [{ a: { b: 2 } }, 2],
72 +};
73 +
74 ```
75
76 ### Eval output
63 -(kind: exception) Fixture not implemented!
64 -logs: ['The above error occurred in the <WrapperTestComponent> component:\n' +
65 - '\n' +
66 - ' at WrapperTestComponent (<project_root>/packages/snap/dist/sprout/evaluator.js:54:26)\n' +
67 - '\n' +
68 - 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
69 - 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.']
\ No newline at end of file
77 +(kind: ok) {"b":2}
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/cfg-switch-exhaustive.js
+9 -2
@@ -2,9 +2,11 @@
2 // scope that produces x, since it is accessed unconditionally in all cfg
3 // paths
4
5 -function TestCondDepInSwitch(props, other) {
5 +import { identity } from "shared-runtime";
6 +
7 +function useCondDepInSwitch(props, other) {
8 const x = {};
7 - switch (foo(other)) {
9 + switch (identity(other)) {
10 case 1:
11 x.a = props.a.b;
12 break;
@@ -16,3 +18,8 @@ function TestCondDepInSwitch(props, other) {
18 }
19 return x;
20 }
21 +
22 +export const FIXTURE_ENTRYPOINT = {
23 + fn: useCondDepInSwitch,
24 + params: [{ a: { b: 2 } }, 2],
25 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/cfg-switch-missing-case.expect.md
+19 -11
@@ -5,9 +5,11 @@
5 // props.a.b should NOT be added as a unconditional dependency to the reactive
6 // scope that produces x if it is not accessed in every path
7
8 -function TestCondDepInSwitchMissingCase(props, other) {
8 +import { identity } from "shared-runtime";
9 +
10 +function useCondDepInSwitchMissingCase(props, other) {
11 const x = {};
10 - switch (foo(other)) {
12 + switch (identity(other)) {
13 case 1:
14 x.a = props.a.b;
15 break;
@@ -21,6 +23,11 @@ function TestCondDepInSwitchMissingCase(props, other) {
23 return x;
24 }
25
26 +export const FIXTURE_ENTRYPOINT = {
27 + fn: useCondDepInSwitchMissingCase,
28 + params: [{ a: { b: 2 } }, 2],
29 +};
30 +
31 ```
32
33 ## Code
@@ -29,12 +36,14 @@ function TestCondDepInSwitchMissingCase(props, other) {
36 import { unstable_useMemoCache as useMemoCache } from "react"; // props.a.b should NOT be added as a unconditional dependency to the reactive
37 // scope that produces x if it is not accessed in every path
38
32 -function TestCondDepInSwitchMissingCase(props, other) {
39 +import { identity } from "shared-runtime";
40 +
41 +function useCondDepInSwitchMissingCase(props, other) {
42 const $ = useMemoCache(3);
43 let x;
44 if ($[0] !== other || $[1] !== props) {
45 x = {};
37 - bb1: switch (foo(other)) {
46 + bb1: switch (identity(other)) {
47 case 1: {
48 x.a = props.a.b;
49 break bb1;
@@ -56,13 +65,12 @@ function TestCondDepInSwitchMissingCase(props, other) {
65 return x;
66 }
67
68 +export const FIXTURE_ENTRYPOINT = {
69 + fn: useCondDepInSwitchMissingCase,
70 + params: [{ a: { b: 2 } }, 2],
71 +};
72 +
73 ```
74
75 ### Eval output
62 -(kind: exception) Fixture not implemented!
63 -logs: ['The above error occurred in the <WrapperTestComponent> component:\n' +
64 - '\n' +
65 - ' at WrapperTestComponent (<project_root>/packages/snap/dist/sprout/evaluator.js:54:26)\n' +
66 - '\n' +
67 - 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
68 - 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.']
\ No newline at end of file
76 +(kind: ok) {"b":42}
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/cfg-switch-missing-case.js
+9 -2
@@ -1,9 +1,11 @@
1 // props.a.b should NOT be added as a unconditional dependency to the reactive
2 // scope that produces x if it is not accessed in every path
3
4 -function TestCondDepInSwitchMissingCase(props, other) {
4 +import { identity } from "shared-runtime";
5 +
6 +function useCondDepInSwitchMissingCase(props, other) {
7 const x = {};
6 - switch (foo(other)) {
8 + switch (identity(other)) {
9 case 1:
10 x.a = props.a.b;
11 break;
@@ -16,3 +18,8 @@ function TestCondDepInSwitchMissingCase(props, other) {
18 }
19 return x;
20 }
21 +
22 +export const FIXTURE_ENTRYPOINT = {
23 + fn: useCondDepInSwitchMissingCase,
24 + params: [{ a: { b: 2 } }, 2],
25 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/cfg-switch-missing-default.expect.md
+19 -11
@@ -5,9 +5,11 @@
5 // props.a.b should NOT be added as a unconditional dependency to the reactive
6 // scope that produces x if it is not accessed in the default case.
7
8 -function TestCondDepInSwitchMissingDefault(props, other) {
8 +import { identity } from "shared-runtime";
9 +
10 +function useCondDepInSwitchMissingDefault(props, other) {
11 const x = {};
10 - switch (foo(other)) {
12 + switch (identity(other)) {
13 case 1:
14 x.a = props.a.b;
15 break;
@@ -18,6 +20,11 @@ function TestCondDepInSwitchMissingDefault(props, other) {
20 return x;
21 }
22
23 +export const FIXTURE_ENTRYPOINT = {
24 + fn: useCondDepInSwitchMissingDefault,
25 + params: [{ a: { b: 2 } }, 3],
26 +};
27 +
28 ```
29
30 ## Code
@@ -26,12 +33,14 @@ function TestCondDepInSwitchMissingDefault(props, other) {
33 import { unstable_useMemoCache as useMemoCache } from "react"; // props.a.b should NOT be added as a unconditional dependency to the reactive
34 // scope that produces x if it is not accessed in the default case.
35
29 -function TestCondDepInSwitchMissingDefault(props, other) {
36 +import { identity } from "shared-runtime";
37 +
38 +function useCondDepInSwitchMissingDefault(props, other) {
39 const $ = useMemoCache(3);
40 let x;
41 if ($[0] !== other || $[1] !== props) {
42 x = {};
34 - bb1: switch (foo(other)) {
43 + bb1: switch (identity(other)) {
44 case 1: {
45 x.a = props.a.b;
46 break bb1;
@@ -49,13 +58,12 @@ function TestCondDepInSwitchMissingDefault(props, other) {
58 return x;
59 }
60
61 +export const FIXTURE_ENTRYPOINT = {
62 + fn: useCondDepInSwitchMissingDefault,
63 + params: [{ a: { b: 2 } }, 3],
64 +};
65 +
66 ```
67
68 ### Eval output
55 -(kind: exception) Fixture not implemented!
56 -logs: ['The above error occurred in the <WrapperTestComponent> component:\n' +
57 - '\n' +
58 - ' at WrapperTestComponent (<project_root>/packages/snap/dist/sprout/evaluator.js:54:26)\n' +
59 - '\n' +
60 - 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
61 - 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.']
\ No newline at end of file
69 +(kind: ok) {}
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/cfg-switch-missing-default.js
+9 -2
@@ -1,9 +1,11 @@
1 // props.a.b should NOT be added as a unconditional dependency to the reactive
2 // scope that produces x if it is not accessed in the default case.
3
4 -function TestCondDepInSwitchMissingDefault(props, other) {
4 +import { identity } from "shared-runtime";
5 +
6 +function useCondDepInSwitchMissingDefault(props, other) {
7 const x = {};
6 - switch (foo(other)) {
8 + switch (identity(other)) {
9 case 1:
10 x.a = props.a.b;
11 break;
@@ -13,3 +15,8 @@ function TestCondDepInSwitchMissingDefault(props, other) {
15 }
16 return x;
17 }
18 +
19 +export const FIXTURE_ENTRYPOINT = {
20 + fn: useCondDepInSwitchMissingDefault,
21 + params: [{ a: { b: 2 } }, 3],
22 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/cond-scope.expect.md
+21 -13
@@ -20,15 +20,22 @@
20 // return x;
21 // ```
22
23 -function TestReactiveDepsInCondScope(props) {
23 +import { CONST_FALSE, identity } from "shared-runtime";
24 +
25 +function useReactiveDepsInCondScope(props) {
26 let x = {};
25 - if (foo) {
26 - let tmp = bar(props.a.b);
27 + if (CONST_FALSE) {
28 + let tmp = identity(props.a.b);
29 x.a = tmp;
30 }
31 return x;
32 }
33
34 +export const FIXTURE_ENTRYPOINT = {
35 + fn: useReactiveDepsInCondScope,
36 + params: [{}],
37 +};
38 +
39 ```
40
41 ## Code
@@ -52,15 +59,17 @@ import { unstable_useMemoCache as useMemoCache } from "react"; // Some reactive
59 // return x;
60 // ```
61
55 -function TestReactiveDepsInCondScope(props) {
62 +import { CONST_FALSE, identity } from "shared-runtime";
63 +
64 +function useReactiveDepsInCondScope(props) {
65 const $ = useMemoCache(4);
66 let x;
67 if ($[0] !== props) {
68 x = {};
60 - if (foo) {
69 + if (CONST_FALSE) {
70 let t0;
71 if ($[2] !== props.a.b) {
63 - t0 = bar(props.a.b);
72 + t0 = identity(props.a.b);
73 $[2] = props.a.b;
74 $[3] = t0;
75 } else {
@@ -77,13 +86,12 @@ function TestReactiveDepsInCondScope(props) {
86 return x;
87 }
88
89 +export const FIXTURE_ENTRYPOINT = {
90 + fn: useReactiveDepsInCondScope,
91 + params: [{}],
92 +};
93 +
94 ```
95
96 ### Eval output
83 -(kind: exception) Fixture not implemented!
84 -logs: ['The above error occurred in the <WrapperTestComponent> component:\n' +
85 - '\n' +
86 - ' at WrapperTestComponent (<project_root>/packages/snap/dist/sprout/evaluator.js:54:26)\n' +
87 - '\n' +
88 - 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
89 - 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.']
\ No newline at end of file
97 +(kind: ok) {}
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/cond-scope.js
+10 -3
@@ -16,11 +16,18 @@
16 // return x;
17 // ```
18
19 -function TestReactiveDepsInCondScope(props) {
19 +import { CONST_FALSE, identity } from "shared-runtime";
20 +
21 +function useReactiveDepsInCondScope(props) {
22 let x = {};
21 - if (foo) {
22 - let tmp = bar(props.a.b);
23 + if (CONST_FALSE) {
24 + let tmp = identity(props.a.b);
25 x.a = tmp;
26 }
27 return x;
28 }
29 +
30 +export const FIXTURE_ENTRYPOINT = {
31 + fn: useReactiveDepsInCondScope,
32 + params: [{}],
33 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.expect.md
+23 -15
@@ -19,16 +19,23 @@
19 // mutate2(y, props.a.b);
20 // }
21
22 -function TestJoinCondDepsInUncondScopes(props) {
22 +import { CONST_TRUE, setProperty } from "shared-runtime";
23 +
24 +function useJoinCondDepsInUncondScopes(props) {
25 let y = {};
26 let x = {};
25 - if (foo) {
26 - mutate1(x, props.a.b);
27 + if (CONST_TRUE) {
28 + setProperty(x, props.a.b);
29 }
28 - mutate2(y, props.a.b);
30 + setProperty(y, props.a.b);
31 return [x, y];
32 }
33
34 +export const FIXTURE_ENTRYPOINT = {
35 + fn: useJoinCondDepsInUncondScopes,
36 + params: [{ a: { b: 3 } }],
37 +};
38 +
39 ```
40
41 ## Code
@@ -51,7 +58,9 @@ import { unstable_useMemoCache as useMemoCache } from "react"; // This tests an
58 // mutate2(y, props.a.b);
59 // }
60
54 -function TestJoinCondDepsInUncondScopes(props) {
61 +import { CONST_TRUE, setProperty } from "shared-runtime";
62 +
63 +function useJoinCondDepsInUncondScopes(props) {
64 const $ = useMemoCache(8);
65 let x;
66 let y;
@@ -59,8 +68,8 @@ function TestJoinCondDepsInUncondScopes(props) {
68 y = {};
69 if ($[3] !== props) {
70 x = {};
62 - if (foo) {
63 - mutate1(x, props.a.b);
71 + if (CONST_TRUE) {
72 + setProperty(x, props.a.b);
73 }
74 $[3] = props;
75 $[4] = x;
@@ -68,7 +77,7 @@ function TestJoinCondDepsInUncondScopes(props) {
77 x = $[4];
78 }
79
71 - mutate2(y, props.a.b);
80 + setProperty(y, props.a.b);
81 $[0] = props.a.b;
82 $[1] = x;
83 $[2] = y;
@@ -88,13 +97,12 @@ function TestJoinCondDepsInUncondScopes(props) {
97 return t0;
98 }
99
100 +export const FIXTURE_ENTRYPOINT = {
101 + fn: useJoinCondDepsInUncondScopes,
102 + params: [{ a: { b: 3 } }],
103 +};
104 +
105 ```
106
107 ### Eval output
94 -(kind: exception) Fixture not implemented!
95 -logs: ['The above error occurred in the <WrapperTestComponent> component:\n' +
96 - '\n' +
97 - ' at WrapperTestComponent (<project_root>/packages/snap/dist/sprout/evaluator.js:54:26)\n' +
98 - '\n' +
99 - 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
100 - 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.']
\ No newline at end of file
108 +(kind: ok) [{"wat0":3},{"wat0":3}]
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/join-uncond-scopes-cond-deps.js
+11 -4
@@ -15,12 +15,19 @@
15 // mutate2(y, props.a.b);
16 // }
17
18 -function TestJoinCondDepsInUncondScopes(props) {
18 +import { CONST_TRUE, setProperty } from "shared-runtime";
19 +
20 +function useJoinCondDepsInUncondScopes(props) {
21 let y = {};
22 let x = {};
21 - if (foo) {
22 - mutate1(x, props.a.b);
23 + if (CONST_TRUE) {
24 + setProperty(x, props.a.b);
25 }
24 - mutate2(y, props.a.b);
26 + setProperty(y, props.a.b);
27 return [x, y];
28 }
29 +
30 +export const FIXTURE_ENTRYPOINT = {
31 + fn: useJoinCondDepsInUncondScopes,
32 + params: [{ a: { b: 3 } }],
33 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/no-uncond.expect.md
+47 -13
@@ -3,33 +3,54 @@
3
4 ```javascript
5 // When an object's properties are only read conditionally, we should
6 +
7 +import { identity } from "shared-runtime";
8 +
9 // track the base object as a dependency.
7 -function TestOnlyConditionalDependencies(props, other) {
10 +function useOnlyConditionalDependencies({ props, cond }) {
11 const x = {};
9 - if (foo(other)) {
12 + if (identity(cond)) {
13 x.b = props.a.b;
14 x.c = props.a.b.c;
15 }
16 return x;
17 }
18
19 +export const FIXTURE_ENTRYPOINT = {
20 + fn: useOnlyConditionalDependencies,
21 + params: [{ props: { a: { b: 2 } }, cond: true }],
22 + sequentialRenders: [
23 + { props: { a: { b: 2 } }, cond: true },
24 + { props: null, cond: false },
25 + // check we preserve nullthrows
26 + { props: { a: { b: { c: undefined } } }, cond: true },
27 + { props: { a: { b: undefined } }, cond: true },
28 + { props: { a: { b: { c: undefined } } }, cond: true },
29 + { props: undefined, cond: true },
30 + ],
31 +};
32 +
33 ```
34
35 ## Code
36
37 ```javascript
38 import { unstable_useMemoCache as useMemoCache } from "react"; // When an object's properties are only read conditionally, we should
39 +
40 +import { identity } from "shared-runtime";
41 +
42 // track the base object as a dependency.
23 -function TestOnlyConditionalDependencies(props, other) {
43 +function useOnlyConditionalDependencies(t0) {
44 const $ = useMemoCache(3);
45 + const { props, cond } = t0;
46 let x;
26 - if ($[0] !== other || $[1] !== props) {
47 + if ($[0] !== cond || $[1] !== props) {
48 x = {};
28 - if (foo(other)) {
49 + if (identity(cond)) {
50 x.b = props.a.b;
51 x.c = props.a.b.c;
52 }
32 - $[0] = other;
53 + $[0] = cond;
54 $[1] = props;
55 $[2] = x;
56 } else {
@@ -38,13 +59,26 @@ function TestOnlyConditionalDependencies(props, other) {
59 return x;
60 }
61
62 +export const FIXTURE_ENTRYPOINT = {
63 + fn: useOnlyConditionalDependencies,
64 + params: [{ props: { a: { b: 2 } }, cond: true }],
65 + sequentialRenders: [
66 + { props: { a: { b: 2 } }, cond: true },
67 + { props: null, cond: false },
68 + // check we preserve nullthrows
69 + { props: { a: { b: { c: undefined } } }, cond: true },
70 + { props: { a: { b: undefined } }, cond: true },
71 + { props: { a: { b: { c: undefined } } }, cond: true },
72 + { props: undefined, cond: true },
73 + ],
74 +};
75 +
76 ```
77
78 ### Eval output
44 -(kind: exception) Fixture not implemented!
45 -logs: ['The above error occurred in the <WrapperTestComponent> component:\n' +
46 - '\n' +
47 - ' at WrapperTestComponent (<project_root>/packages/snap/dist/sprout/evaluator.js:54:26)\n' +
48 - '\n' +
49 - 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
50 - 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.']
\ No newline at end of file
79 +(kind: ok) {"b":2}
80 +{}
81 +{"b":{}}
82 +[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]]
83 +{"b":{}}
84 +[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'a') ]]
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/no-uncond.js
+19 -2
@@ -1,10 +1,27 @@
1 // When an object's properties are only read conditionally, we should
2 +
3 +import { identity } from "shared-runtime";
4 +
5 // track the base object as a dependency.
3 -function TestOnlyConditionalDependencies(props, other) {
6 +function useOnlyConditionalDependencies({ props, cond }) {
7 const x = {};
5 - if (foo(other)) {
8 + if (identity(cond)) {
9 x.b = props.a.b;
10 x.c = props.a.b.c;
11 }
12 return x;
13 }
14 +
15 +export const FIXTURE_ENTRYPOINT = {
16 + fn: useOnlyConditionalDependencies,
17 + params: [{ props: { a: { b: 2 } }, cond: true }],
18 + sequentialRenders: [
19 + { props: { a: { b: 2 } }, cond: true },
20 + { props: null, cond: false },
21 + // check we preserve nullthrows
22 + { props: { a: { b: { c: undefined } } }, cond: true },
23 + { props: { a: { b: undefined } }, cond: true },
24 + { props: { a: { b: { c: undefined } } }, cond: true },
25 + { props: undefined, cond: true },
26 + ],
27 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.expect.md
+21 -11
@@ -4,16 +4,24 @@
4 ```javascript
5 // When a conditional dependency `props.a.b.c` has no unconditional dependency
6 // in its subpath or superpath, we should find the nearest unconditional access
7 +
8 +import { identity } from "shared-runtime";
9 +
10 // and promote it to an unconditional dependency.
8 -function TestPromoteUnconditionalAccessToDependency(props, other) {
11 +function usePromoteUnconditionalAccessToDependency(props, other) {
12 const x = {};
13 x.a = props.a.a.a;
11 - if (foo(other)) {
14 + if (identity(other)) {
15 x.c = props.a.b.c;
16 }
17 return x;
18 }
19
20 +export const FIXTURE_ENTRYPOINT = {
21 + fn: usePromoteUnconditionalAccessToDependency,
22 + params: [{ a: { a: { a: 3 } } }, false],
23 +};
24 +
25 ```
26
27 ## Code
@@ -21,14 +29,17 @@ function TestPromoteUnconditionalAccessToDependency(props, other) {
29 ```javascript
30 import { unstable_useMemoCache as useMemoCache } from "react"; // When a conditional dependency `props.a.b.c` has no unconditional dependency
31 // in its subpath or superpath, we should find the nearest unconditional access
32 +
33 +import { identity } from "shared-runtime";
34 +
35 // and promote it to an unconditional dependency.
25 -function TestPromoteUnconditionalAccessToDependency(props, other) {
36 +function usePromoteUnconditionalAccessToDependency(props, other) {
37 const $ = useMemoCache(3);
38 let x;
39 if ($[0] !== props.a || $[1] !== other) {
40 x = {};
41 x.a = props.a.a.a;
31 - if (foo(other)) {
42 + if (identity(other)) {
43 x.c = props.a.b.c;
44 }
45 $[0] = props.a;
@@ -40,13 +51,12 @@ function TestPromoteUnconditionalAccessToDependency(props, other) {
51 return x;
52 }
53
54 +export const FIXTURE_ENTRYPOINT = {
55 + fn: usePromoteUnconditionalAccessToDependency,
56 + params: [{ a: { a: { a: 3 } } }, false],
57 +};
58 +
59 ```
60
61 ### Eval output
46 -(kind: exception) Fixture not implemented!
47 -logs: ['The above error occurred in the <WrapperTestComponent> component:\n' +
48 - '\n' +
49 - ' at WrapperTestComponent (<project_root>/packages/snap/dist/sprout/evaluator.js:54:26)\n' +
50 - '\n' +
51 - 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
52 - 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.']
\ No newline at end of file
62 +(kind: ok) {"a":3}
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/promote-uncond.js
+10 -2
@@ -1,11 +1,19 @@
1 // When a conditional dependency `props.a.b.c` has no unconditional dependency
2 // in its subpath or superpath, we should find the nearest unconditional access
3 +
4 +import { identity } from "shared-runtime";
5 +
6 // and promote it to an unconditional dependency.
4 -function TestPromoteUnconditionalAccessToDependency(props, other) {
7 +function usePromoteUnconditionalAccessToDependency(props, other) {
8 const x = {};
9 x.a = props.a.a.a;
7 - if (foo(other)) {
10 + if (identity(other)) {
11 x.c = props.a.b.c;
12 }
13 return x;
14 }
15 +
16 +export const FIXTURE_ENTRYPOINT = {
17 + fn: usePromoteUnconditionalAccessToDependency,
18 + params: [{ a: { a: { a: 3 } } }, false],
19 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.expect.md
+23 -13
@@ -6,16 +6,24 @@
6 // dependency `props.a.b`, we can access `props.a` while preserving program
7 // semantics (with respect to nullthrows).
8 // deps: {`props.a`, `props.a.b`} can further reduce to just `props.a`
9 +
10 +import { identity } from "shared-runtime";
11 +
12 // ordering of accesses should not matter
10 -function TestConditionalSubpath1(props, other) {
13 +function useConditionalSubpath1(props, cond) {
14 const x = {};
15 x.b = props.a.b;
13 - if (foo(other)) {
16 + if (identity(cond)) {
17 x.a = props.a;
18 }
19 return x;
20 }
21
22 +export const FIXTURE_ENTRYPOINT = {
23 + fn: useConditionalSubpath1,
24 + params: [{ a: { b: 3 } }, false],
25 +};
26 +
27 ```
28
29 ## Code
@@ -25,18 +33,21 @@ import { unstable_useMemoCache as useMemoCache } from "react"; // When a conditi
33 // dependency `props.a.b`, we can access `props.a` while preserving program
34 // semantics (with respect to nullthrows).
35 // deps: {`props.a`, `props.a.b`} can further reduce to just `props.a`
36 +
37 +import { identity } from "shared-runtime";
38 +
39 // ordering of accesses should not matter
29 -function TestConditionalSubpath1(props, other) {
40 +function useConditionalSubpath1(props, cond) {
41 const $ = useMemoCache(3);
42 let x;
32 - if ($[0] !== props.a || $[1] !== other) {
43 + if ($[0] !== props.a || $[1] !== cond) {
44 x = {};
45 x.b = props.a.b;
35 - if (foo(other)) {
46 + if (identity(cond)) {
47 x.a = props.a;
48 }
49 $[0] = props.a;
39 - $[1] = other;
50 + $[1] = cond;
51 $[2] = x;
52 } else {
53 x = $[2];
@@ -44,13 +55,12 @@ function TestConditionalSubpath1(props, other) {
55 return x;
56 }
57
58 +export const FIXTURE_ENTRYPOINT = {
59 + fn: useConditionalSubpath1,
60 + params: [{ a: { b: 3 } }, false],
61 +};
62 +
63 ```
64
65 ### Eval output
50 -(kind: exception) Fixture not implemented!
51 -logs: ['The above error occurred in the <WrapperTestComponent> component:\n' +
52 - '\n' +
53 - ' at WrapperTestComponent (<project_root>/packages/snap/dist/sprout/evaluator.js:54:26)\n' +
54 - '\n' +
55 - 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
56 - 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.']
\ No newline at end of file
66 +(kind: ok) {"b":3}
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order1.js
+10 -2
@@ -2,12 +2,20 @@
2 // dependency `props.a.b`, we can access `props.a` while preserving program
3 // semantics (with respect to nullthrows).
4 // deps: {`props.a`, `props.a.b`} can further reduce to just `props.a`
5 +
6 +import { identity } from "shared-runtime";
7 +
8 // ordering of accesses should not matter
6 -function TestConditionalSubpath1(props, other) {
9 +function useConditionalSubpath1(props, cond) {
10 const x = {};
11 x.b = props.a.b;
9 - if (foo(other)) {
12 + if (identity(cond)) {
13 x.a = props.a;
14 }
15 return x;
16 }
17 +
18 +export const FIXTURE_ENTRYPOINT = {
19 + fn: useConditionalSubpath1,
20 + params: [{ a: { b: 3 } }, false],
21 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order2.expect.md
+21 -11
@@ -6,16 +6,24 @@
6 // dependency `props.a.b`, we can access `props.a` while preserving program
7 // semantics (with respect to nullthrows).
8 // deps: {`props.a`, `props.a.b`} can further reduce to just `props.a`
9 +
10 +import { identity } from "shared-runtime";
11 +
12 // ordering of accesses should not matter
10 -function TestConditionalSubpath2(props, other) {
13 +function useConditionalSubpath2(props, other) {
14 const x = {};
12 - if (foo(other)) {
15 + if (identity(other)) {
16 x.a = props.a;
17 }
18 x.b = props.a.b;
19 return x;
20 }
21
22 +export const FIXTURE_ENTRYPOINT = {
23 + fn: useConditionalSubpath2,
24 + params: [{ a: { b: 3 } }, false],
25 +};
26 +
27 ```
28
29 ## Code
@@ -25,13 +33,16 @@ import { unstable_useMemoCache as useMemoCache } from "react"; // When a conditi
33 // dependency `props.a.b`, we can access `props.a` while preserving program
34 // semantics (with respect to nullthrows).
35 // deps: {`props.a`, `props.a.b`} can further reduce to just `props.a`
36 +
37 +import { identity } from "shared-runtime";
38 +
39 // ordering of accesses should not matter
29 -function TestConditionalSubpath2(props, other) {
40 +function useConditionalSubpath2(props, other) {
41 const $ = useMemoCache(3);
42 let x;
43 if ($[0] !== other || $[1] !== props.a) {
44 x = {};
34 - if (foo(other)) {
45 + if (identity(other)) {
46 x.a = props.a;
47 }
48
@@ -45,13 +56,12 @@ function TestConditionalSubpath2(props, other) {
56 return x;
57 }
58
59 +export const FIXTURE_ENTRYPOINT = {
60 + fn: useConditionalSubpath2,
61 + params: [{ a: { b: 3 } }, false],
62 +};
63 +
64 ```
65
66 ### Eval output
51 -(kind: exception) Fixture not implemented!
52 -logs: ['The above error occurred in the <WrapperTestComponent> component:\n' +
53 - '\n' +
54 - ' at WrapperTestComponent (<project_root>/packages/snap/dist/sprout/evaluator.js:54:26)\n' +
55 - '\n' +
56 - 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
57 - 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.']
\ No newline at end of file
67 +(kind: ok) {"b":3}
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/subpath-order2.js
+10 -2
@@ -2,12 +2,20 @@
2 // dependency `props.a.b`, we can access `props.a` while preserving program
3 // semantics (with respect to nullthrows).
4 // deps: {`props.a`, `props.a.b`} can further reduce to just `props.a`
5 +
6 +import { identity } from "shared-runtime";
7 +
8 // ordering of accesses should not matter
6 -function TestConditionalSubpath2(props, other) {
9 +function useConditionalSubpath2(props, other) {
10 const x = {};
8 - if (foo(other)) {
11 + if (identity(other)) {
12 x.a = props.a;
13 }
14 x.b = props.a.b;
15 return x;
16 }
17 +
18 +export const FIXTURE_ENTRYPOINT = {
19 + fn: useConditionalSubpath2,
20 + params: [{ a: { b: 3 } }, false],
21 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.expect.md
+47 -13
@@ -5,16 +5,33 @@
5 // When an unconditional dependency `props.a` is the subpath of a conditional
6 // dependency `props.a.b`, we can safely overestimate and only track `props.a`
7 // as a dependency
8 +
9 +import { identity } from "shared-runtime";
10 +
11 // ordering of accesses should not matter
9 -function TestConditionalSuperpath1(props, other) {
12 +function useConditionalSuperpath1({ props, cond }) {
13 const x = {};
14 x.a = props.a;
12 - if (foo(other)) {
15 + if (identity(cond)) {
16 x.b = props.a.b;
17 }
18 return x;
19 }
20
21 +export const FIXTURE_ENTRYPOINT = {
22 + fn: useConditionalSuperpath1,
23 + params: [{ props: { a: null }, cond: false }],
24 + sequentialRenders: [
25 + { props: { a: null }, cond: false },
26 + { props: { a: {} }, cond: true },
27 + { props: { a: { b: 3 } }, cond: true },
28 + { props: {}, cond: false },
29 + // test that we preserve nullthrows
30 + { props: { a: { b: undefined } }, cond: true },
31 + { props: { a: undefined }, cond: true },
32 + ],
33 +};
34 +
35 ```
36
37 ## Code
@@ -23,18 +40,22 @@ function TestConditionalSuperpath1(props, other) {
40 import { unstable_useMemoCache as useMemoCache } from "react"; // When an unconditional dependency `props.a` is the subpath of a conditional
41 // dependency `props.a.b`, we can safely overestimate and only track `props.a`
42 // as a dependency
43 +
44 +import { identity } from "shared-runtime";
45 +
46 // ordering of accesses should not matter
27 -function TestConditionalSuperpath1(props, other) {
47 +function useConditionalSuperpath1(t0) {
48 const $ = useMemoCache(3);
49 + const { props, cond } = t0;
50 let x;
30 - if ($[0] !== props.a || $[1] !== other) {
51 + if ($[0] !== props.a || $[1] !== cond) {
52 x = {};
53 x.a = props.a;
33 - if (foo(other)) {
54 + if (identity(cond)) {
55 x.b = props.a.b;
56 }
57 $[0] = props.a;
37 - $[1] = other;
58 + $[1] = cond;
59 $[2] = x;
60 } else {
61 x = $[2];
@@ -42,13 +63,26 @@ function TestConditionalSuperpath1(props, other) {
63 return x;
64 }
65
66 +export const FIXTURE_ENTRYPOINT = {
67 + fn: useConditionalSuperpath1,
68 + params: [{ props: { a: null }, cond: false }],
69 + sequentialRenders: [
70 + { props: { a: null }, cond: false },
71 + { props: { a: {} }, cond: true },
72 + { props: { a: { b: 3 } }, cond: true },
73 + { props: {}, cond: false },
74 + // test that we preserve nullthrows
75 + { props: { a: { b: undefined } }, cond: true },
76 + { props: { a: undefined }, cond: true },
77 + ],
78 +};
79 +
80 ```
81
82 ### Eval output
48 -(kind: exception) Fixture not implemented!
49 -logs: ['The above error occurred in the <WrapperTestComponent> component:\n' +
50 - '\n' +
51 - ' at WrapperTestComponent (<project_root>/packages/snap/dist/sprout/evaluator.js:54:26)\n' +
52 - '\n' +
53 - 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
54 - 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.']
\ No newline at end of file
83 +(kind: ok) {"a":null}
84 +{"a":{}}
85 +{"a":{"b":3},"b":3}
86 +{}
87 +{"a":{}}
88 +[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'b') ]]
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order1.js
+19 -2
@@ -1,12 +1,29 @@
1 // When an unconditional dependency `props.a` is the subpath of a conditional
2 // dependency `props.a.b`, we can safely overestimate and only track `props.a`
3 // as a dependency
4 +
5 +import { identity } from "shared-runtime";
6 +
7 // ordering of accesses should not matter
5 -function TestConditionalSuperpath1(props, other) {
8 +function useConditionalSuperpath1({ props, cond }) {
9 const x = {};
10 x.a = props.a;
8 - if (foo(other)) {
11 + if (identity(cond)) {
12 x.b = props.a.b;
13 }
14 return x;
15 }
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: useConditionalSuperpath1,
19 + params: [{ props: { a: null }, cond: false }],
20 + sequentialRenders: [
21 + { props: { a: null }, cond: false },
22 + { props: { a: {} }, cond: true },
23 + { props: { a: { b: 3 } }, cond: true },
24 + { props: {}, cond: false },
25 + // test that we preserve nullthrows
26 + { props: { a: { b: undefined } }, cond: true },
27 + { props: { a: undefined }, cond: true },
28 + ],
29 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order2.expect.md
+47 -13
@@ -5,16 +5,33 @@
5 // When an unconditional dependency `props.a` is the subpath of a conditional
6 // dependency `props.a.b`, we can safely overestimate and only track `props.a`
7 // as a dependency
8 +
9 +import { identity } from "shared-runtime";
10 +
11 // ordering of accesses should not matter
9 -function TestConditionalSuperpath2(props, other) {
12 +function useConditionalSuperpath2({ props, cond }) {
13 const x = {};
11 - if (foo(other)) {
14 + if (identity(cond)) {
15 x.b = props.a.b;
16 }
17 x.a = props.a;
18 return x;
19 }
20
21 +export const FIXTURE_ENTRYPOINT = {
22 + fn: useConditionalSuperpath2,
23 + params: [{ props: { a: null }, cond: false }],
24 + sequentialRenders: [
25 + { props: { a: null }, cond: false },
26 + { props: { a: {} }, cond: true },
27 + { props: { a: { b: 3 } }, cond: true },
28 + { props: {}, cond: false },
29 + // test that we preserve nullthrows
30 + { props: { a: { b: undefined } }, cond: true },
31 + { props: { a: undefined }, cond: true },
32 + ],
33 +};
34 +
35 ```
36
37 ## Code
@@ -23,18 +40,22 @@ function TestConditionalSuperpath2(props, other) {
40 import { unstable_useMemoCache as useMemoCache } from "react"; // When an unconditional dependency `props.a` is the subpath of a conditional
41 // dependency `props.a.b`, we can safely overestimate and only track `props.a`
42 // as a dependency
43 +
44 +import { identity } from "shared-runtime";
45 +
46 // ordering of accesses should not matter
27 -function TestConditionalSuperpath2(props, other) {
47 +function useConditionalSuperpath2(t0) {
48 const $ = useMemoCache(3);
49 + const { props, cond } = t0;
50 let x;
30 - if ($[0] !== other || $[1] !== props.a) {
51 + if ($[0] !== cond || $[1] !== props.a) {
52 x = {};
32 - if (foo(other)) {
53 + if (identity(cond)) {
54 x.b = props.a.b;
55 }
56
57 x.a = props.a;
37 - $[0] = other;
58 + $[0] = cond;
59 $[1] = props.a;
60 $[2] = x;
61 } else {
@@ -43,13 +64,26 @@ function TestConditionalSuperpath2(props, other) {
64 return x;
65 }
66
67 +export const FIXTURE_ENTRYPOINT = {
68 + fn: useConditionalSuperpath2,
69 + params: [{ props: { a: null }, cond: false }],
70 + sequentialRenders: [
71 + { props: { a: null }, cond: false },
72 + { props: { a: {} }, cond: true },
73 + { props: { a: { b: 3 } }, cond: true },
74 + { props: {}, cond: false },
75 + // test that we preserve nullthrows
76 + { props: { a: { b: undefined } }, cond: true },
77 + { props: { a: undefined }, cond: true },
78 + ],
79 +};
80 +
81 ```
82
83 ### Eval output
49 -(kind: exception) Fixture not implemented!
50 -logs: ['The above error occurred in the <WrapperTestComponent> component:\n' +
51 - '\n' +
52 - ' at WrapperTestComponent (<project_root>/packages/snap/dist/sprout/evaluator.js:54:26)\n' +
53 - '\n' +
54 - 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
55 - 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.']
\ No newline at end of file
84 +(kind: ok) {"a":null}
85 +{"a":{}}
86 +{"b":3,"a":{"b":3}}
87 +{}
88 +{"a":{}}
89 +[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'b') ]]
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/reduce-reactive-deps/superpath-order2.js
+19 -2
@@ -1,12 +1,29 @@
1 // When an unconditional dependency `props.a` is the subpath of a conditional
2 // dependency `props.a.b`, we can safely overestimate and only track `props.a`
3 // as a dependency
4 +
5 +import { identity } from "shared-runtime";
6 +
7 // ordering of accesses should not matter
5 -function TestConditionalSuperpath2(props, other) {
8 +function useConditionalSuperpath2({ props, cond }) {
9 const x = {};
7 - if (foo(other)) {
10 + if (identity(cond)) {
11 x.b = props.a.b;
12 }
13 x.a = props.a;
14 return x;
15 }
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: useConditionalSuperpath2,
19 + params: [{ props: { a: null }, cond: false }],
20 + sequentialRenders: [
21 + { props: { a: null }, cond: false },
22 + { props: { a: {} }, cond: true },
23 + { props: { a: { b: 3 } }, cond: true },
24 + { props: {}, cond: false },
25 + // test that we preserve nullthrows
26 + { props: { a: { b: undefined } }, cond: true },
27 + { props: { a: undefined }, cond: true },
28 + ],
29 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/repro-no-declarations-in-reactive-scope-with-early-return.expect.md
+1 -7
@@ -112,10 +112,4 @@ function Component() {
112 ```
113
114 ### Eval output
115 -(kind: exception) Fixture not implemented!
116 -logs: ['The above error occurred in the <WrapperTestComponent> component:\n' +
117 - '\n' +
118 - ' at WrapperTestComponent (<project_root>/packages/snap/dist/sprout/evaluator.js:54:26)\n' +
119 - '\n' +
120 - 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
121 - 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.']
\ No newline at end of file
115 +(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/repro-no-value-for-temporary.expect.md
+1 -7
@@ -51,10 +51,4 @@ function Component(listItem, thread) {
51 ```
52
53 ### Eval output
54 -(kind: exception) Fixture not implemented!
55 -logs: ['The above error occurred in the <WrapperTestComponent> component:\n' +
56 - '\n' +
57 - ' at WrapperTestComponent (<project_root>/packages/snap/dist/sprout/evaluator.js:54:26)\n' +
58 - '\n' +
59 - 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
60 - 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.']
\ No newline at end of file
54 +(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/rules-of-hooks/allow-locals-named-like-hooks.expect.md
+1 -7
@@ -75,10 +75,4 @@ export const FIXTURE_ENTRYPOINT = {
75 ```
76
77 ### Eval output
78 -(kind: exception) Stringify is not defined
79 -logs: ['The above error occurred in the <WrapperTestComponent> component:\n' +
80 - '\n' +
81 - ' at WrapperTestComponent (<project_root>/packages/snap/dist/sprout/evaluator.js:54:26)\n' +
82 - '\n' +
83 - 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
84 - 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.']
\ No newline at end of file
78 +(kind: exception) Stringify is not defined
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ssa-throw.expect.md
+1 -7
@@ -34,10 +34,4 @@ export const FIXTURE_ENTRYPOINT = {
34 ```
35
36 ### Eval output
37 -(kind: exception) undefined
38 -logs: ['The above error occurred in the <WrapperTestComponent> component:\n' +
39 - '\n' +
40 - ' at WrapperTestComponent (<project_root>/packages/snap/dist/sprout/evaluator.js:54:26)\n' +
41 - '\n' +
42 - 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
43 - 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.']
\ No newline at end of file
37 +(kind: exception) undefined
\ No newline at end of file
compiler/packages/snap/src/sprout/evaluator.ts
+75 -12
@@ -7,12 +7,11 @@
7
8 import { render } from "@testing-library/react";
9 import { JSDOM } from "jsdom";
10 +import React, { MutableRefObject } from "react";
11 import util from "util";
12 import { z } from "zod";
13 import { fromZodError } from "zod-validation-error";
13 -import { PROJECT_ROOT } from "../constants";
14 import { initFbt, toJSON } from "./shared-runtime";
15 -const React = require("react");
15
16 /**
17 * Set up the global environment for JSDOM tests.
@@ -31,7 +30,6 @@ initFbt();
30 (globalThis as any).placeholderFn = function (..._args: Array<any>) {
31 throw new Error("Fixture not implemented!");
32 };
34 -
33 export type EvaluatorResult = {
34 kind: "ok" | "exception" | "UnexpectedError";
35 value: string;
@@ -57,6 +55,45 @@ const ExportSchema = z.object({
55 FIXTURE_ENTRYPOINT: EntrypointSchema,
56 });
57
58 +/**
59 + * Wraps WrapperTestComponent in an error boundary to simplify re-rendering
60 + * when an exception is thrown.
61 + * A simpler alternative may be to re-mount test components manually.
62 + */
63 +class WrapperTestComponentWithErrorBoundary extends React.Component<
64 + { fn: any; params: Array<any> },
65 + { hasError: boolean; error: any }
66 +> {
67 + propsErrorMap: MutableRefObject<Map<any, any>>;
68 + constructor(props: any) {
69 + super(props);
70 + this.state = { hasError: false, error: null };
71 + this.propsErrorMap = React.createRef() as MutableRefObject<Map<any, any>>;
72 + this.propsErrorMap.current = new Map();
73 + }
74 + static getDerivedStateFromError(error: any) {
75 + return { hasError: true, error: error };
76 + }
77 + override componentDidUpdate() {
78 + if (this.state.hasError) {
79 + this.setState({ hasError: false, error: null });
80 + }
81 + }
82 + override render() {
83 + if (this.state.hasError) {
84 + this.propsErrorMap.current!.set(
85 + this.props,
86 + `[[ (exception in render) ${this.state.error?.toString()} ]]`
87 + );
88 + }
89 + const cachedError = this.propsErrorMap.current!.get(this.props);
90 + if (cachedError != null) {
91 + return cachedError;
92 + }
93 + return React.createElement(WrapperTestComponent, this.props);
94 + }
95 +}
96 +
97 function WrapperTestComponent(props: { fn: any; params: Array<any> }) {
98 const result = props.fn(...props.params);
99 // Hacky solution to determine whether the fixture returned jsx (which
@@ -81,13 +118,16 @@ function renderComponentSequentiallyForEachProps(
118 const initialProps = sequentialRenders[0]!;
119 const results = [];
120 const { rerender, container } = render(
84 - React.createElement(WrapperTestComponent, { fn, params: [initialProps] })
121 + React.createElement(WrapperTestComponentWithErrorBoundary, {
122 + fn,
123 + params: [initialProps],
124 + })
125 );
126 results.push(container.innerHTML);
127
128 for (let i = 1; i < sequentialRenders.length; i++) {
129 rerender(
90 - React.createElement(WrapperTestComponent, {
130 + React.createElement(WrapperTestComponentWithErrorBoundary, {
131 fn,
132 params: [sequentialRenders[i]],
133 })
@@ -127,7 +167,7 @@ type FixtureEvaluatorResult = Omit<EvaluatorResult, "logs">;
167 // Try to run fixture as a react component. This is necessary because not
168 // all components are functions (some are ForwardRef or Memo objects).
169 const result = render(
130 - React.createElement(entrypoint.fn, entrypoint.params[0])
170 + React.createElement(entrypoint.fn as any, entrypoint.params[0])
171 ).container.innerHTML;
172
173 return {
@@ -151,12 +191,14 @@ export function doEval(source: string): EvaluatorResult {
191 const originalConsole = globalThis.console;
192 const logs: Array<string> = [];
193 const mockedLog = (...args: Array<any>) => {
154 - // Some hackery: React will use the JS engine to log source location,
155 - // which doesn't play well with snapshot files.
194 logs.push(
157 - `${args.map((arg) =>
158 - util.inspect(arg).replaceAll(PROJECT_ROOT, "<project_root>")
159 - )}`
195 + `${args.map((arg) => {
196 + if (arg instanceof Error) {
197 + return arg.toString();
198 + } else {
199 + return util.inspect(arg);
200 + }
201 + })}`
202 );
203 };
204
@@ -164,7 +206,22 @@ export function doEval(source: string): EvaluatorResult {
206 info: mockedLog,
207 log: mockedLog,
208 warn: mockedLog,
167 - error: mockedLog,
209 + error: (...args: Array<any>) => {
210 + const stack = new Error().stack?.split("\n", 5) ?? [];
211 + for (const stackFrame of stack) {
212 + // React warns on exceptions thrown during render, we avoid printing
213 + // here to reduce noise in test fixture outputs.
214 + if (
215 + (stackFrame.includes("at logCapturedError") &&
216 + stackFrame.includes("react-dom.development")) ||
217 + (stackFrame.includes("at defaultOnRecoverableError") &&
218 + stackFrame.includes("react-dom.development"))
219 + ) {
220 + return;
221 + }
222 + }
223 + mockedLog(...args);
224 + },
225 table: mockedLog,
226 trace: () => {},
227 };
@@ -184,6 +241,12 @@ export function doEval(source: string): EvaluatorResult {
241 // run in an iife to avoid naming collisions
242 (() => {${source}})();
243 reachedInvoke = true;
244 + if (exports.FIXTURE_ENTRYPOINT?.fn === globalThis.placeholderFn) {
245 + return {
246 + kind: "exception",
247 + value: "Fixture not implemented",
248 + };
249 + }
250 return evaluateFixtureExport(exports);
251 } catch (e) {
252 if (!reachedInvoke) {