[tests] Require exact error messages in assertConsole helpers (#35497)
Requires full error message in assert helpers. Some of the error messages we asset on add a native javascript stack trace, which would be a pain to add to the messages and maintain. This PR allows you to just add `\n in <stack>` placeholder to the error message to denote a native stack trace is present in the message. --- Note: i vibe coded this so it was a pain to backtrack this to break this into a stack, I tried and gave up, sorry.
Ricky committed
Jan 13, 2026 at 15:52 UTC
3e1abcc8d7083a13adf4774feb0d67ecbe4a2bc4
16 files changed
+690
-320
.github/workflows/runtime_build_and_test.yml
+3
@@ -278,6 +278,7 @@ jobs:
278
if: steps.node_modules.outputs.cache-hit != 'true'
279
- run: yarn --cwd compiler install --frozen-lockfile
280
if: steps.node_modules.outputs.cache-hit != 'true'
281
+ - run: node --version
282
- run: yarn test ${{ matrix.params }} --ci --shard=${{ matrix.shard }}
283
284
# Hardcoded to improve parallelism
@@ -445,6 +446,7 @@ jobs:
446
merge-multiple: true
447
- name: Display structure of build
448
run: ls -R build
449
+ - run: node --version
450
- run: yarn test --build ${{ matrix.test_params }} --shard=${{ matrix.shard }} --ci
451
452
test_build_devtools:
@@ -489,6 +491,7 @@ jobs:
491
merge-multiple: true
492
- name: Display structure of build
493
run: ls -R build
494
+ - run: node --version
495
- run: yarn test --build --project=devtools -r=experimental --shard=${{ matrix.shard }} --ci
496
497
process_artifacts_combined:
packages/internal-test-utils/__tests__/ReactInternalTestUtils-test.js
+309
-225
@@ -879,7 +879,7 @@ describe('ReactInternalTestUtils console assertions', () => {
879
if (__DEV__) {
880
console.warn('Hello\n in div');
881
}
882
- assertConsoleWarnDev(['Hello']);
882
+ assertConsoleWarnDev(['Hello\n in div']);
883
});
884
885
it('passes if all warnings contain a stack', () => {
@@ -888,7 +888,11 @@ describe('ReactInternalTestUtils console assertions', () => {
888
console.warn('Good day\n in div');
889
console.warn('Bye\n in div');
890
}
891
- assertConsoleWarnDev(['Hello', 'Good day', 'Bye']);
891
+ assertConsoleWarnDev([
892
+ 'Hello\n in div',
893
+ 'Good day\n in div',
894
+ 'Bye\n in div',
895
+ ]);
896
});
897
898
it('fails if act is called without assertConsoleWarnDev', async () => {
@@ -1075,7 +1079,11 @@ describe('ReactInternalTestUtils console assertions', () => {
1079
const message = expectToThrowFailure(() => {
1080
console.warn('Hi \n in div');
1081
console.warn('Wow \n in div');
1078
- assertConsoleWarnDev(['Hi', 'Wow', 'Bye']);
1082
+ assertConsoleWarnDev([
1083
+ 'Hi \n in div',
1084
+ 'Wow \n in div',
1085
+ 'Bye \n in div',
1086
+ ]);
1087
});
1088
expect(message).toMatchInlineSnapshot(`
1089
"assertConsoleWarnDev(expected)
@@ -1085,9 +1093,9 @@ describe('ReactInternalTestUtils console assertions', () => {
1093
- Expected warnings
1094
+ Received warnings
1095
1088
- - Hi
1089
- - Wow
1090
- - Bye
1096
+ - Hi in div
1097
+ - Wow in div
1098
+ - Bye in div
1099
+ Hi in div (at **)
1100
+ Wow in div (at **)"
1101
`);
@@ -1188,16 +1196,26 @@ describe('ReactInternalTestUtils console assertions', () => {
1196
console.warn('Hello');
1197
console.warn('Good day\n in div');
1198
console.warn('Bye\n in div');
1191
- assertConsoleWarnDev(['Hello', 'Good day', 'Bye']);
1199
+ assertConsoleWarnDev([
1200
+ 'Hello\n in div',
1201
+ 'Good day\n in div',
1202
+ 'Bye\n in div',
1203
+ ]);
1204
});
1205
expect(message).toMatchInlineSnapshot(`
1206
"assertConsoleWarnDev(expected)
1207
1196
- Missing component stack for:
1197
- "Hello"
1208
+ Unexpected warning(s) recorded.
1209
1199
- If this warning should omit a component stack, pass [log, {withoutStack: true}].
1200
- If all warnings should omit the component stack, add {withoutStack: true} to the assertConsoleWarnDev call."
1210
+ - Expected warnings
1211
+ + Received warnings
1212
+
1213
+ - Hello in div
1214
+ - Good day in div
1215
+ - Bye in div
1216
+ + Hello
1217
+ + Good day in div (at **)
1218
+ + Bye in div (at **)"
1219
`);
1220
});
1221
@@ -1207,16 +1225,26 @@ describe('ReactInternalTestUtils console assertions', () => {
1225
console.warn('Hello\n in div');
1226
console.warn('Good day');
1227
console.warn('Bye\n in div');
1210
- assertConsoleWarnDev(['Hello', 'Good day', 'Bye']);
1228
+ assertConsoleWarnDev([
1229
+ 'Hello\n in div',
1230
+ 'Good day\n in div',
1231
+ 'Bye\n in div',
1232
+ ]);
1233
});
1234
expect(message).toMatchInlineSnapshot(`
1235
"assertConsoleWarnDev(expected)
1236
1215
- Missing component stack for:
1216
- "Good day"
1237
+ Unexpected warning(s) recorded.
1238
1218
- If this warning should omit a component stack, pass [log, {withoutStack: true}].
1219
- If all warnings should omit the component stack, add {withoutStack: true} to the assertConsoleWarnDev call."
1239
+ - Expected warnings
1240
+ + Received warnings
1241
+
1242
+ - Hello in div
1243
+ - Good day in div
1244
+ - Bye in div
1245
+ + Hello in div (at **)
1246
+ + Good day
1247
+ + Bye in div (at **)"
1248
`);
1249
});
1250
@@ -1226,41 +1254,26 @@ describe('ReactInternalTestUtils console assertions', () => {
1254
console.warn('Hello\n in div');
1255
console.warn('Good day\n in div');
1256
console.warn('Bye');
1229
- assertConsoleWarnDev(['Hello', 'Good day', 'Bye']);
1230
- });
1231
- expect(message).toMatchInlineSnapshot(`
1232
- "assertConsoleWarnDev(expected)
1233
-
1234
- Missing component stack for:
1235
- "Bye"
1236
-
1237
- If this warning should omit a component stack, pass [log, {withoutStack: true}].
1238
- If all warnings should omit the component stack, add {withoutStack: true} to the assertConsoleWarnDev call."
1239
- `);
1240
- });
1241
-
1242
- // @gate __DEV__
1243
- it('fails if all warnings do not contain a stack', () => {
1244
- const message = expectToThrowFailure(() => {
1245
- console.warn('Hello');
1246
- console.warn('Good day');
1247
- console.warn('Bye');
1248
- assertConsoleWarnDev(['Hello', 'Good day', 'Bye']);
1257
+ assertConsoleWarnDev([
1258
+ 'Hello\n in div',
1259
+ 'Good day\n in div',
1260
+ 'Bye\n in div',
1261
+ ]);
1262
});
1263
expect(message).toMatchInlineSnapshot(`
1264
"assertConsoleWarnDev(expected)
1265
1253
- Missing component stack for:
1254
- "Hello"
1255
-
1256
- Missing component stack for:
1257
- "Good day"
1266
+ Unexpected warning(s) recorded.
1267
1259
- Missing component stack for:
1260
- "Bye"
1268
+ - Expected warnings
1269
+ + Received warnings
1270
1262
- If this warning should omit a component stack, pass [log, {withoutStack: true}].
1263
- If all warnings should omit the component stack, add {withoutStack: true} to the assertConsoleWarnDev call."
1271
+ - Hello in div
1272
+ - Good day in div
1273
+ - Bye in div
1274
+ + Hello in div (at **)
1275
+ + Good day in div (at **)
1276
+ + Bye"
1277
`);
1278
});
1279
@@ -1339,12 +1352,13 @@ describe('ReactInternalTestUtils console assertions', () => {
1352
expect(message).toMatchInlineSnapshot(`
1353
"assertConsoleWarnDev(expected)
1354
1342
- Unexpected component stack for:
1343
- "Hello
1344
- in div (at **)"
1355
+ Unexpected warning(s) recorded.
1356
+
1357
+ - Expected warnings
1358
+ + Received warnings
1359
1346
- If this warning should include a component stack, remove {withoutStack: true} from this warning.
1347
- If all warnings should include the component stack, you may need to remove {withoutStack: true} from the assertConsoleWarnDev call."
1360
+ - Hello
1361
+ + Hello in div (at **)"
1362
`);
1363
});
1364
@@ -1361,16 +1375,16 @@ describe('ReactInternalTestUtils console assertions', () => {
1375
expect(message).toMatchInlineSnapshot(`
1376
"assertConsoleWarnDev(expected)
1377
1364
- Unexpected component stack for:
1365
- "Hello
1366
- in div (at **)"
1378
+ Unexpected warning(s) recorded.
1379
1368
- Unexpected component stack for:
1369
- "Bye
1370
- in div (at **)"
1380
+ - Expected warnings
1381
+ + Received warnings
1382
1372
- If this warning should include a component stack, remove {withoutStack: true} from this warning.
1373
- If all warnings should include the component stack, you may need to remove {withoutStack: true} from the assertConsoleWarnDev call."
1383
+ - Hello
1384
+ + Hello in div (at **)
1385
+ Good day
1386
+ - Bye
1387
+ + Bye in div (at **)"
1388
`);
1389
});
1390
});
@@ -1382,9 +1396,9 @@ describe('ReactInternalTestUtils console assertions', () => {
1396
console.warn('Bye\n in div');
1397
}
1398
assertConsoleWarnDev([
1385
- 'Hello',
1399
+ 'Hello\n in div',
1400
['Good day', {withoutStack: true}],
1387
- 'Bye',
1401
+ 'Bye\n in div',
1402
]);
1403
});
1404
@@ -1490,12 +1504,13 @@ describe('ReactInternalTestUtils console assertions', () => {
1504
expect(message).toMatchInlineSnapshot(`
1505
"assertConsoleWarnDev(expected)
1506
1493
- Unexpected component stack for:
1494
- "Hello
1495
- in div (at **)"
1507
+ Unexpected warning(s) recorded.
1508
1497
- If this warning should include a component stack, remove {withoutStack: true} from this warning.
1498
- If all warnings should include the component stack, you may need to remove {withoutStack: true} from the assertConsoleWarnDev call."
1509
+ - Expected warnings
1510
+ + Received warnings
1511
+
1512
+ - Hello
1513
+ + Hello in div (at **)"
1514
`);
1515
});
1516
@@ -1524,16 +1539,16 @@ describe('ReactInternalTestUtils console assertions', () => {
1539
expect(message).toMatchInlineSnapshot(`
1540
"assertConsoleWarnDev(expected)
1541
1527
- Unexpected component stack for:
1528
- "Hello
1529
- in div (at **)"
1542
+ Unexpected warning(s) recorded.
1543
1531
- Unexpected component stack for:
1532
- "Bye
1533
- in div (at **)"
1544
+ - Expected warnings
1545
+ + Received warnings
1546
1535
- If this warning should include a component stack, remove {withoutStack: true} from this warning.
1536
- If all warnings should include the component stack, you may need to remove {withoutStack: true} from the assertConsoleWarnDev call."
1547
+ - Hello
1548
+ + Hello in div (at **)
1549
+ Good day
1550
+ - Bye
1551
+ + Bye in div (at **)"
1552
`);
1553
});
1554
});
@@ -1606,13 +1621,18 @@ describe('ReactInternalTestUtils console assertions', () => {
1621
it('fails if component stack is passed twice', () => {
1622
const message = expectToThrowFailure(() => {
1623
console.warn('Hi %s%s', '\n in div', '\n in div');
1609
- assertConsoleWarnDev(['Hi']);
1624
+ assertConsoleWarnDev(['Hi \n in div (at **)']);
1625
});
1626
expect(message).toMatchInlineSnapshot(`
1627
"assertConsoleWarnDev(expected)
1628
1614
- Received more than one component stack for a warning:
1615
- "Hi %s%s""
1629
+ Unexpected warning(s) recorded.
1630
+
1631
+ - Expected warnings
1632
+ + Received warnings
1633
+
1634
+ Hi in div (at **)
1635
+ + in div (at **)"
1636
`);
1637
});
1638
@@ -1621,16 +1641,23 @@ describe('ReactInternalTestUtils console assertions', () => {
1641
const message = expectToThrowFailure(() => {
1642
console.warn('Hi %s%s', '\n in div', '\n in div');
1643
console.warn('Bye %s%s', '\n in div', '\n in div');
1624
- assertConsoleWarnDev(['Hi', 'Bye']);
1644
+ assertConsoleWarnDev([
1645
+ 'Hi \n in div (at **)',
1646
+ 'Bye \n in div (at **)',
1647
+ ]);
1648
});
1649
expect(message).toMatchInlineSnapshot(`
1650
"assertConsoleWarnDev(expected)
1651
1629
- Received more than one component stack for a warning:
1630
- "Hi %s%s"
1652
+ Unexpected warning(s) recorded.
1653
+
1654
+ - Expected warnings
1655
+ + Received warnings
1656
1632
- Received more than one component stack for a warning:
1633
- "Bye %s%s""
1657
+ Hi in div (at **)
1658
+ + in div (at **)
1659
+ Bye in div (at **)
1660
+ + in div (at **)"
1661
`);
1662
});
1663
@@ -1646,7 +1673,7 @@ describe('ReactInternalTestUtils console assertions', () => {
1673
1674
Expected messages should be an array of strings but was given type "string"."
1675
`);
1649
- assertConsoleWarnDev(['Hi', 'Bye']);
1676
+ assertConsoleWarnDev(['Hi \n in div', 'Bye \n in div']);
1677
});
1678
1679
// @gate __DEV__
@@ -1661,7 +1688,7 @@ describe('ReactInternalTestUtils console assertions', () => {
1688
1689
Expected messages should be an array of strings but was given type "string"."
1690
`);
1664
- assertConsoleWarnDev(['Hi', 'Bye']);
1691
+ assertConsoleWarnDev(['Hi \n in div', 'Bye \n in div']);
1692
});
1693
1694
// @gate __DEV__
@@ -1677,7 +1704,11 @@ describe('ReactInternalTestUtils console assertions', () => {
1704
1705
Expected messages should be an array of strings but was given type "string"."
1706
`);
1680
- assertConsoleWarnDev(['Hi', 'Wow', 'Bye']);
1707
+ assertConsoleWarnDev([
1708
+ 'Hi \n in div',
1709
+ 'Wow \n in div',
1710
+ 'Bye \n in div',
1711
+ ]);
1712
});
1713
1714
it('should fail if waitFor is called before asserting', async () => {
@@ -1884,7 +1915,7 @@ describe('ReactInternalTestUtils console assertions', () => {
1915
if (__DEV__) {
1916
console.error('Hello\n in div');
1917
}
1887
- assertConsoleErrorDev(['Hello']);
1918
+ assertConsoleErrorDev(['Hello\n in div']);
1919
});
1920
1921
it('passes if all errors contain a stack', () => {
@@ -1893,7 +1924,11 @@ describe('ReactInternalTestUtils console assertions', () => {
1924
console.error('Good day\n in div');
1925
console.error('Bye\n in div');
1926
}
1896
- assertConsoleErrorDev(['Hello', 'Good day', 'Bye']);
1927
+ assertConsoleErrorDev([
1928
+ 'Hello\n in div',
1929
+ 'Good day\n in div',
1930
+ 'Bye\n in div',
1931
+ ]);
1932
});
1933
1934
it('fails if act is called without assertConsoleErrorDev', async () => {
@@ -2080,7 +2115,11 @@ describe('ReactInternalTestUtils console assertions', () => {
2115
const message = expectToThrowFailure(() => {
2116
console.error('Hi \n in div');
2117
console.error('Wow \n in div');
2083
- assertConsoleErrorDev(['Hi', 'Wow', 'Bye']);
2118
+ assertConsoleErrorDev([
2119
+ 'Hi \n in div',
2120
+ 'Wow \n in div',
2121
+ 'Bye \n in div',
2122
+ ]);
2123
});
2124
expect(message).toMatchInlineSnapshot(`
2125
"assertConsoleErrorDev(expected)
@@ -2090,9 +2129,9 @@ describe('ReactInternalTestUtils console assertions', () => {
2129
- Expected errors
2130
+ Received errors
2131
2093
- - Hi
2094
- - Wow
2095
- - Bye
2132
+ - Hi in div
2133
+ - Wow in div
2134
+ - Bye in div
2135
+ Hi in div (at **)
2136
+ Wow in div (at **)"
2137
`);
@@ -2192,101 +2231,6 @@ describe('ReactInternalTestUtils console assertions', () => {
2231
+ TypeError: Cannot read properties of undefined (reading 'stack') in Foo (at **)"
2232
`);
2233
});
2195
- // @gate __DEV__
2196
- it('fails if only error does not contain a stack', () => {
2197
- const message = expectToThrowFailure(() => {
2198
- console.error('Hello');
2199
- assertConsoleErrorDev(['Hello']);
2200
- });
2201
- expect(message).toMatchInlineSnapshot(`
2202
- "assertConsoleErrorDev(expected)
2203
-
2204
- Missing component stack for:
2205
- "Hello"
2206
-
2207
- If this error should omit a component stack, pass [log, {withoutStack: true}].
2208
- If all errors should omit the component stack, add {withoutStack: true} to the assertConsoleErrorDev call."
2209
- `);
2210
- });
2211
-
2212
- // @gate __DEV__
2213
- it('fails if first error does not contain a stack', () => {
2214
- const message = expectToThrowFailure(() => {
2215
- console.error('Hello\n in div');
2216
- console.error('Good day\n in div');
2217
- console.error('Bye');
2218
- assertConsoleErrorDev(['Hello', 'Good day', 'Bye']);
2219
- });
2220
- expect(message).toMatchInlineSnapshot(`
2221
- "assertConsoleErrorDev(expected)
2222
-
2223
- Missing component stack for:
2224
- "Bye"
2225
-
2226
- If this error should omit a component stack, pass [log, {withoutStack: true}].
2227
- If all errors should omit the component stack, add {withoutStack: true} to the assertConsoleErrorDev call."
2228
- `);
2229
- });
2230
- // @gate __DEV__
2231
- it('fails if last error does not contain a stack', () => {
2232
- const message = expectToThrowFailure(() => {
2233
- console.error('Hello');
2234
- console.error('Good day\n in div');
2235
- console.error('Bye\n in div');
2236
- assertConsoleErrorDev(['Hello', 'Good day', 'Bye']);
2237
- });
2238
- expect(message).toMatchInlineSnapshot(`
2239
- "assertConsoleErrorDev(expected)
2240
-
2241
- Missing component stack for:
2242
- "Hello"
2243
-
2244
- If this error should omit a component stack, pass [log, {withoutStack: true}].
2245
- If all errors should omit the component stack, add {withoutStack: true} to the assertConsoleErrorDev call."
2246
- `);
2247
- });
2248
- // @gate __DEV__
2249
- it('fails if middle error does not contain a stack', () => {
2250
- const message = expectToThrowFailure(() => {
2251
- console.error('Hello\n in div');
2252
- console.error('Good day');
2253
- console.error('Bye\n in div');
2254
- assertConsoleErrorDev(['Hello', 'Good day', 'Bye']);
2255
- });
2256
- expect(message).toMatchInlineSnapshot(`
2257
- "assertConsoleErrorDev(expected)
2258
-
2259
- Missing component stack for:
2260
- "Good day"
2261
-
2262
- If this error should omit a component stack, pass [log, {withoutStack: true}].
2263
- If all errors should omit the component stack, add {withoutStack: true} to the assertConsoleErrorDev call."
2264
- `);
2265
- });
2266
- // @gate __DEV__
2267
- it('fails if all errors do not contain a stack', () => {
2268
- const message = expectToThrowFailure(() => {
2269
- console.error('Hello');
2270
- console.error('Good day');
2271
- console.error('Bye');
2272
- assertConsoleErrorDev(['Hello', 'Good day', 'Bye']);
2273
- });
2274
- expect(message).toMatchInlineSnapshot(`
2275
- "assertConsoleErrorDev(expected)
2276
-
2277
- Missing component stack for:
2278
- "Hello"
2279
-
2280
- Missing component stack for:
2281
- "Good day"
2282
-
2283
- Missing component stack for:
2284
- "Bye"
2285
-
2286
- If this error should omit a component stack, pass [log, {withoutStack: true}].
2287
- If all errors should omit the component stack, add {withoutStack: true} to the assertConsoleErrorDev call."
2288
- `);
2289
- });
2234
2235
// @gate __DEV__
2236
it('regression: checks entire string, not just the first letter', async () => {
@@ -2385,12 +2329,13 @@ describe('ReactInternalTestUtils console assertions', () => {
2329
expect(message).toMatchInlineSnapshot(`
2330
"assertConsoleErrorDev(expected)
2331
2388
- Unexpected component stack for:
2389
- "Hello
2390
- in div (at **)"
2332
+ Unexpected error(s) recorded.
2333
2392
- If this error should include a component stack, remove {withoutStack: true} from this error.
2393
- If all errors should include the component stack, you may need to remove {withoutStack: true} from the assertConsoleErrorDev call."
2334
+ - Expected errors
2335
+ + Received errors
2336
+
2337
+ - Hello
2338
+ + Hello in div (at **)"
2339
`);
2340
});
2341
@@ -2407,16 +2352,16 @@ describe('ReactInternalTestUtils console assertions', () => {
2352
expect(message).toMatchInlineSnapshot(`
2353
"assertConsoleErrorDev(expected)
2354
2410
- Unexpected component stack for:
2411
- "Hello
2412
- in div (at **)"
2355
+ Unexpected error(s) recorded.
2356
2414
- Unexpected component stack for:
2415
- "Bye
2416
- in div (at **)"
2357
+ - Expected errors
2358
+ + Received errors
2359
2418
- If this error should include a component stack, remove {withoutStack: true} from this error.
2419
- If all errors should include the component stack, you may need to remove {withoutStack: true} from the assertConsoleErrorDev call."
2360
+ - Hello
2361
+ + Hello in div (at **)
2362
+ Good day
2363
+ - Bye
2364
+ + Bye in div (at **)"
2365
`);
2366
});
2367
});
@@ -2428,9 +2373,9 @@ describe('ReactInternalTestUtils console assertions', () => {
2373
console.error('Bye\n in div');
2374
}
2375
assertConsoleErrorDev([
2431
- 'Hello',
2376
+ 'Hello\n in div',
2377
['Good day', {withoutStack: true}],
2433
- 'Bye',
2378
+ 'Bye\n in div',
2379
]);
2380
});
2381
@@ -2536,12 +2481,13 @@ describe('ReactInternalTestUtils console assertions', () => {
2481
expect(message).toMatchInlineSnapshot(`
2482
"assertConsoleErrorDev(expected)
2483
2539
- Unexpected component stack for:
2540
- "Hello
2541
- in div (at **)"
2484
+ Unexpected error(s) recorded.
2485
+
2486
+ - Expected errors
2487
+ + Received errors
2488
2543
- If this error should include a component stack, remove {withoutStack: true} from this error.
2544
- If all errors should include the component stack, you may need to remove {withoutStack: true} from the assertConsoleErrorDev call."
2489
+ - Hello
2490
+ + Hello in div (at **)"
2491
`);
2492
});
2493
@@ -2570,16 +2516,16 @@ describe('ReactInternalTestUtils console assertions', () => {
2516
expect(message).toMatchInlineSnapshot(`
2517
"assertConsoleErrorDev(expected)
2518
2573
- Unexpected component stack for:
2574
- "Hello
2575
- in div (at **)"
2519
+ Unexpected error(s) recorded.
2520
2577
- Unexpected component stack for:
2578
- "Bye
2579
- in div (at **)"
2521
+ - Expected errors
2522
+ + Received errors
2523
2581
- If this error should include a component stack, remove {withoutStack: true} from this error.
2582
- If all errors should include the component stack, you may need to remove {withoutStack: true} from the assertConsoleErrorDev call."
2524
+ - Hello
2525
+ + Hello in div (at **)
2526
+ Good day
2527
+ - Bye
2528
+ + Bye in div (at **)"
2529
`);
2530
});
2531
@@ -2678,13 +2624,18 @@ describe('ReactInternalTestUtils console assertions', () => {
2624
it('fails if component stack is passed twice', () => {
2625
const message = expectToThrowFailure(() => {
2626
console.error('Hi %s%s', '\n in div', '\n in div');
2681
- assertConsoleErrorDev(['Hi']);
2627
+ assertConsoleErrorDev(['Hi \n in div (at **)']);
2628
});
2629
expect(message).toMatchInlineSnapshot(`
2630
"assertConsoleErrorDev(expected)
2631
2686
- Received more than one component stack for a warning:
2687
- "Hi %s%s""
2632
+ Unexpected error(s) recorded.
2633
+
2634
+ - Expected errors
2635
+ + Received errors
2636
+
2637
+ Hi in div (at **)
2638
+ + in div (at **)"
2639
`);
2640
});
2641
@@ -2693,16 +2644,23 @@ describe('ReactInternalTestUtils console assertions', () => {
2644
const message = expectToThrowFailure(() => {
2645
console.error('Hi %s%s', '\n in div', '\n in div');
2646
console.error('Bye %s%s', '\n in div', '\n in div');
2696
- assertConsoleErrorDev(['Hi', 'Bye']);
2647
+ assertConsoleErrorDev([
2648
+ 'Hi \n in div (at **)',
2649
+ 'Bye \n in div (at **)',
2650
+ ]);
2651
});
2652
expect(message).toMatchInlineSnapshot(`
2653
"assertConsoleErrorDev(expected)
2654
2701
- Received more than one component stack for a warning:
2702
- "Hi %s%s"
2655
+ Unexpected error(s) recorded.
2656
+
2657
+ - Expected errors
2658
+ + Received errors
2659
2704
- Received more than one component stack for a warning:
2705
- "Bye %s%s""
2660
+ Hi in div (at **)
2661
+ + in div (at **)
2662
+ Bye in div (at **)
2663
+ + in div (at **)"
2664
`);
2665
});
2666
@@ -2711,14 +2669,14 @@ describe('ReactInternalTestUtils console assertions', () => {
2669
const message = expectToThrowFailure(() => {
2670
console.error('Hi \n in div');
2671
console.error('Bye \n in div');
2714
- assertConsoleErrorDev('Hi', 'Bye');
2672
+ assertConsoleErrorDev('Hi \n in div', 'Bye \n in div');
2673
});
2674
expect(message).toMatchInlineSnapshot(`
2675
"assertConsoleErrorDev(expected)
2676
2677
Expected messages should be an array of strings but was given type "string"."
2678
`);
2721
- assertConsoleErrorDev(['Hi', 'Bye']);
2679
+ assertConsoleErrorDev(['Hi \n in div', 'Bye \n in div']);
2680
});
2681
2682
// @gate __DEV__
@@ -2733,7 +2691,7 @@ describe('ReactInternalTestUtils console assertions', () => {
2691
2692
Expected messages should be an array of strings but was given type "string"."
2693
`);
2736
- assertConsoleErrorDev(['Hi', 'Bye']);
2694
+ assertConsoleErrorDev(['Hi \n in div', 'Bye \n in div']);
2695
});
2696
2697
// @gate __DEV__
@@ -2749,7 +2707,133 @@ describe('ReactInternalTestUtils console assertions', () => {
2707
2708
Expected messages should be an array of strings but was given type "string"."
2709
`);
2752
- assertConsoleErrorDev(['Hi', 'Wow', 'Bye']);
2710
+ assertConsoleErrorDev([
2711
+ 'Hi \n in div',
2712
+ 'Wow \n in div',
2713
+ 'Bye \n in div',
2714
+ ]);
2715
+ });
2716
+
2717
+ describe('in <stack> placeholder', () => {
2718
+ // @gate __DEV__
2719
+ it('fails if `in <stack>` is used for a component stack instead of an error stack', () => {
2720
+ const message = expectToThrowFailure(() => {
2721
+ console.error('Warning message\n in div');
2722
+ assertConsoleErrorDev(['Warning message\n in <stack>']);
2723
+ });
2724
+ expect(message).toMatchInlineSnapshot(`
2725
+ "assertConsoleErrorDev(expected)
2726
+
2727
+ Incorrect use of \\n in <stack> placeholder. The placeholder is for JavaScript Error stack traces (messages starting with "Error:"), not for React component stacks.
2728
+
2729
+ Expected: "Warning message
2730
+ in <stack>"
2731
+ Received: "Warning message
2732
+ in div (at **)"
2733
+
2734
+ If this error has a component stack, include the full component stack in your expected message (e.g., "Warning message\\n in ComponentName (at **)")."
2735
+ `);
2736
+ });
2737
+
2738
+ // @gate __DEV__
2739
+ it('fails if `in <stack>` is used for multiple component stacks', () => {
2740
+ const message = expectToThrowFailure(() => {
2741
+ console.error('First warning\n in span');
2742
+ console.error('Second warning\n in div');
2743
+ assertConsoleErrorDev([
2744
+ 'First warning\n in <stack>',
2745
+ 'Second warning\n in <stack>',
2746
+ ]);
2747
+ });
2748
+ expect(message).toMatchInlineSnapshot(`
2749
+ "assertConsoleErrorDev(expected)
2750
+
2751
+ Incorrect use of \\n in <stack> placeholder. The placeholder is for JavaScript Error stack traces (messages starting with "Error:"), not for React component stacks.
2752
+
2753
+ Expected: "First warning
2754
+ in <stack>"
2755
+ Received: "First warning
2756
+ in span (at **)"
2757
+
2758
+ If this error has a component stack, include the full component stack in your expected message (e.g., "Warning message\\n in ComponentName (at **)").
2759
+
2760
+ Incorrect use of \\n in <stack> placeholder. The placeholder is for JavaScript Error stack traces (messages starting with "Error:"), not for React component stacks.
2761
+
2762
+ Expected: "Second warning
2763
+ in <stack>"
2764
+ Received: "Second warning
2765
+ in div (at **)"
2766
+
2767
+ If this error has a component stack, include the full component stack in your expected message (e.g., "Warning message\\n in ComponentName (at **)")."
2768
+ `);
2769
+ });
2770
+
2771
+ it('allows `in <stack>` for actual error stack traces', () => {
2772
+ // This should pass - \n in <stack> is correctly used for an error stack
2773
+ console.error(new Error('Something went wrong'));
2774
+ assertConsoleErrorDev(['Error: Something went wrong\n in <stack>']);
2775
+ });
2776
+
2777
+ // @gate __DEV__
2778
+ it('fails if error stack trace is present but \\n in <stack> is not expected', () => {
2779
+ const message = expectToThrowFailure(() => {
2780
+ console.error(new Error('Something went wrong'));
2781
+ assertConsoleErrorDev(['Error: Something went wrong']);
2782
+ });
2783
+ expect(message).toMatch(`Unexpected error stack trace for:`);
2784
+ expect(message).toMatch(`Error: Something went wrong`);
2785
+ expect(message).toMatch(
2786
+ 'If this error should include an error stack trace, add \\n in <stack> to your expected message'
2787
+ );
2788
+ });
2789
+
2790
+ // @gate __DEV__
2791
+ it('fails if `in <stack>` is expected but no stack is present', () => {
2792
+ const message = expectToThrowFailure(() => {
2793
+ console.error('Error: Something went wrong');
2794
+ assertConsoleErrorDev([
2795
+ 'Error: Something went wrong\n in <stack>',
2796
+ ]);
2797
+ });
2798
+ expect(message).toMatchInlineSnapshot(`
2799
+ "assertConsoleErrorDev(expected)
2800
+
2801
+ Missing error stack trace for:
2802
+ "Error: Something went wrong"
2803
+
2804
+ The expected message uses \\n in <stack> but the actual error doesn't include an error stack trace.
2805
+ If this error should not have an error stack trace, remove \\n in <stack> from your expected message."
2806
+ `);
2807
+ });
2808
+ });
2809
+
2810
+ describe('[Environment] placeholder', () => {
2811
+ // @gate __DEV__
2812
+ it('expands [Server] to ANSI escape sequence for server badge', () => {
2813
+ const badge = '\u001b[0m\u001b[7m Server \u001b[0m';
2814
+ console.error(badge + 'Error: something went wrong');
2815
+ assertConsoleErrorDev([
2816
+ ['[Server] Error: something went wrong', {withoutStack: true}],
2817
+ ]);
2818
+ });
2819
+
2820
+ // @gate __DEV__
2821
+ it('expands [Prerender] to ANSI escape sequence for server badge', () => {
2822
+ const badge = '\u001b[0m\u001b[7m Prerender \u001b[0m';
2823
+ console.error(badge + 'Error: something went wrong');
2824
+ assertConsoleErrorDev([
2825
+ ['[Prerender] Error: something went wrong', {withoutStack: true}],
2826
+ ]);
2827
+ });
2828
+
2829
+ // @gate __DEV__
2830
+ it('expands [Cache] to ANSI escape sequence for server badge', () => {
2831
+ const badge = '\u001b[0m\u001b[7m Cache \u001b[0m';
2832
+ console.error(badge + 'Error: something went wrong');
2833
+ assertConsoleErrorDev([
2834
+ ['[Cache] Error: something went wrong', {withoutStack: true}],
2835
+ ]);
2836
+ });
2837
});
2838
2839
it('should fail if waitFor is called before asserting', async () => {
packages/internal-test-utils/consoleMock.js
+201
-21
@@ -168,6 +168,53 @@ function normalizeCodeLocInfo(str) {
168
});
169
}
170
171
+// Expands environment placeholders like [Server] into ANSI escape sequences.
172
+// This allows test assertions to use a cleaner syntax like "[Server] Error:"
173
+// instead of the full escape sequence "\u001b[0m\u001b[7m Server \u001b[0mError:"
174
+function expandEnvironmentPlaceholders(str) {
175
+ if (typeof str !== 'string') {
176
+ return str;
177
+ }
178
+ // [Environment] -> ANSI escape sequence for environment badge
179
+ // The format is: reset + inverse + " Environment " + reset
180
+ return str.replace(
181
+ /^\[(\w+)] /g,
182
+ (match, env) => '\u001b[0m\u001b[7m ' + env + ' \u001b[0m',
183
+ );
184
+}
185
+
186
+// The error stack placeholder that can be used in expected messages
187
+const ERROR_STACK_PLACEHOLDER = '\n in <stack>';
188
+// A marker used to protect the placeholder during normalization
189
+const ERROR_STACK_PLACEHOLDER_MARKER = '\n in <__STACK_PLACEHOLDER__>';
190
+
191
+// Normalizes expected messages, handling special placeholders
192
+function normalizeExpectedMessage(str) {
193
+ if (typeof str !== 'string') {
194
+ return str;
195
+ }
196
+ // Protect the error stack placeholder from normalization
197
+ // (normalizeCodeLocInfo would add "(at **)" to it)
198
+ const hasStackPlaceholder = str.includes(ERROR_STACK_PLACEHOLDER);
199
+ let result = str;
200
+ if (hasStackPlaceholder) {
201
+ result = result.replace(
202
+ ERROR_STACK_PLACEHOLDER,
203
+ ERROR_STACK_PLACEHOLDER_MARKER,
204
+ );
205
+ }
206
+ result = normalizeCodeLocInfo(result);
207
+ result = expandEnvironmentPlaceholders(result);
208
+ if (hasStackPlaceholder) {
209
+ // Restore the placeholder (remove the "(at **)" that was added)
210
+ result = result.replace(
211
+ ERROR_STACK_PLACEHOLDER_MARKER + ' (at **)',
212
+ ERROR_STACK_PLACEHOLDER,
213
+ );
214
+ }
215
+ return result;
216
+}
217
+
218
function normalizeComponentStack(entry) {
219
if (
220
typeof entry[0] === 'string' &&
@@ -187,6 +234,15 @@ const isLikelyAComponentStack = message =>
234
message.includes('\n in ') ||
235
message.includes('\n at '));
236
237
+// Error stack traces start with "*Error:" and contain "at" frames with file paths
238
+// Component stacks contain "in ComponentName" patterns
239
+// This helps validate that \n in <stack> is used correctly
240
+const isLikelyAnErrorStackTrace = message =>
241
+ typeof message === 'string' &&
242
+ message.includes('Error:') &&
243
+ // Has "at" frames typical of error stacks (with file:line:col)
244
+ /\n\s+at .+\(.*:\d+:\d+\)/.test(message);
245
+
246
export function createLogAssertion(
247
consoleMethod,
248
matcherName,
@@ -236,13 +292,11 @@ export function createLogAssertion(
292
293
const withoutStack = options.withoutStack;
294
239
- // Warn about invalid global withoutStack values.
295
if (consoleMethod === 'log' && withoutStack !== undefined) {
296
throwFormattedError(
297
`Do not pass withoutStack to assertConsoleLogDev, console.log does not have component stacks.`,
298
);
299
} else if (withoutStack !== undefined && withoutStack !== true) {
245
- // withoutStack can only have a value true.
300
throwFormattedError(
301
`The second argument must be {withoutStack: true}.` +
302
`\n\nInstead received ${JSON.stringify(options)}.`,
@@ -256,8 +310,11 @@ export function createLogAssertion(
310
const unexpectedLogs = [];
311
const unexpectedMissingComponentStack = [];
312
const unexpectedIncludingComponentStack = [];
313
+ const unexpectedMissingErrorStack = [];
314
+ const unexpectedIncludingErrorStack = [];
315
const logsMismatchingFormat = [];
316
const logsWithExtraComponentStack = [];
317
+ const stackTracePlaceholderMisuses = [];
318
319
// Loop over all the observed logs to determine:
320
// - Which expected logs are missing
@@ -319,11 +376,11 @@ export function createLogAssertion(
376
);
377
}
378
322
- expectedMessage = normalizeCodeLocInfo(currentExpectedMessage);
379
+ expectedMessage = normalizeExpectedMessage(currentExpectedMessage);
380
expectedWithoutStack = expectedMessageOrArray[1].withoutStack;
381
} else if (typeof expectedMessageOrArray === 'string') {
325
- // Should be in the form assert(['log']) or assert(['log'], {withoutStack: true})
326
- expectedMessage = normalizeCodeLocInfo(expectedMessageOrArray);
382
+ expectedMessage = normalizeExpectedMessage(expectedMessageOrArray);
383
+ // withoutStack: inherit from global option - simplify when withoutStack is removed.
384
if (consoleMethod === 'log') {
385
expectedWithoutStack = true;
386
} else {
@@ -381,19 +438,93 @@ export function createLogAssertion(
438
}
439
440
// Main logic to check if log is expected, with the component stack.
384
- if (
385
- typeof expectedMessage === 'string' &&
386
- (normalizedMessage === expectedMessage ||
387
- normalizedMessage.includes(expectedMessage))
388
- ) {
441
+ // Check for exact match OR if the message matches with a component stack appended
442
+ let matchesExpectedMessage = false;
443
+ let expectsErrorStack = false;
444
+ const hasErrorStack = isLikelyAnErrorStackTrace(message);
445
+
446
+ if (typeof expectedMessage === 'string') {
447
+ if (normalizedMessage === expectedMessage) {
448
+ matchesExpectedMessage = true;
449
+ } else if (expectedMessage.includes('\n in <stack>')) {
450
+ expectsErrorStack = true;
451
+ // \n in <stack> is ONLY for JavaScript Error stack traces (e.g., "Error: message\n at fn (file.js:1:2)")
452
+ // NOT for React component stacks (e.g., "\n in ComponentName (at **)").
453
+ // Validate that the actual message looks like an error stack trace.
454
+ if (!hasErrorStack) {
455
+ // The actual message doesn't look like an error stack trace.
456
+ // This is likely a misuse - someone used \n in <stack> for a component stack.
457
+ stackTracePlaceholderMisuses.push({
458
+ expected: expectedMessage,
459
+ received: normalizedMessage,
460
+ });
461
+ }
462
+
463
+ const expectedMessageWithoutStack = expectedMessage.replace(
464
+ '\n in <stack>',
465
+ '',
466
+ );
467
+ if (normalizedMessage.startsWith(expectedMessageWithoutStack)) {
468
+ // Remove the stack trace
469
+ const remainder = normalizedMessage.slice(
470
+ expectedMessageWithoutStack.length,
471
+ );
472
+
473
+ // After normalization, both error stacks and component stacks look like
474
+ // component stacks (at frames are converted to "in ... (at **)" format).
475
+ // So we check isLikelyAComponentStack for matching purposes.
476
+ if (isLikelyAComponentStack(remainder)) {
477
+ const messageWithoutStack = normalizedMessage.replace(
478
+ remainder,
479
+ '',
480
+ );
481
+ if (messageWithoutStack === expectedMessageWithoutStack) {
482
+ matchesExpectedMessage = true;
483
+ }
484
+ } else if (remainder === '') {
485
+ // \n in <stack> was expected but there's no stack at all
486
+ matchesExpectedMessage = true;
487
+ }
488
+ } else if (normalizedMessage === expectedMessageWithoutStack) {
489
+ // \n in <stack> was expected but actual has no stack at all (exact match without stack)
490
+ matchesExpectedMessage = true;
491
+ }
492
+ } else if (
493
+ hasErrorStack &&
494
+ !expectedMessage.includes('\n in <stack>') &&
495
+ normalizedMessage.startsWith(expectedMessage)
496
+ ) {
497
+ matchesExpectedMessage = true;
498
+ }
499
+ }
500
+
501
+ if (matchesExpectedMessage) {
502
+ // withoutStack: Check for unexpected/missing component stacks.
503
+ // These checks can be simplified when withoutStack is removed.
504
if (isLikelyAComponentStack(normalizedMessage)) {
390
- if (expectedWithoutStack === true) {
505
+ if (expectedWithoutStack === true && !hasErrorStack) {
506
+ // Only report unexpected component stack if it's not an error stack
507
+ // (error stacks look like component stacks after normalization)
508
unexpectedIncludingComponentStack.push(normalizedMessage);
509
}
393
- } else if (expectedWithoutStack !== true) {
510
+ } else if (expectedWithoutStack !== true && !expectsErrorStack) {
511
unexpectedMissingComponentStack.push(normalizedMessage);
512
}
513
514
+ // Check for unexpected/missing error stacks
515
+ if (hasErrorStack && !expectsErrorStack) {
516
+ // Error stack is present but \n in <stack> was not in the expected message
517
+ unexpectedIncludingErrorStack.push(normalizedMessage);
518
+ } else if (
519
+ expectsErrorStack &&
520
+ !hasErrorStack &&
521
+ !isLikelyAComponentStack(normalizedMessage)
522
+ ) {
523
+ // \n in <stack> was expected but the actual message doesn't have any stack at all
524
+ // (if it has a component stack, stackTracePlaceholderMisuses already handles it)
525
+ unexpectedMissingErrorStack.push(normalizedMessage);
526
+ }
527
+
528
// Found expected log, remove it from missing.
529
missingExpectedLogs.splice(0, 1);
530
} else {
@@ -422,6 +553,21 @@ export function createLogAssertion(
553
)}`;
554
}
555
556
+ // Wrong %s formatting is a failure.
557
+ // This is a common mistake when creating new warnings.
558
+ if (logsMismatchingFormat.length > 0) {
559
+ throwFormattedError(
560
+ logsMismatchingFormat
561
+ .map(
562
+ item =>
563
+ `Received ${item.args.length} arguments for a message with ${
564
+ item.expectedArgCount
565
+ } placeholders:\n ${printReceived(item.format)}`,
566
+ )
567
+ .join('\n\n'),
568
+ );
569
+ }
570
+
571
// Any unexpected warnings should be treated as a failure.
572
if (unexpectedLogs.length > 0) {
573
throwFormattedError(
@@ -466,18 +612,33 @@ export function createLogAssertion(
612
);
613
}
614
469
- // Wrong %s formatting is a failure.
470
- // This is a common mistake when creating new warnings.
471
- if (logsMismatchingFormat.length > 0) {
615
+ // Any logs that include an error stack trace but \n in <stack> wasn't expected.
616
+ if (unexpectedIncludingErrorStack.length > 0) {
617
throwFormattedError(
473
- logsMismatchingFormat
618
+ `${unexpectedIncludingErrorStack
619
.map(
475
- item =>
476
- `Received ${item.args.length} arguments for a message with ${
477
- item.expectedArgCount
478
- } placeholders:\n ${printReceived(item.format)}`,
620
+ stack =>
621
+ `Unexpected error stack trace for:\n ${printReceived(stack)}`,
622
)
480
- .join('\n\n'),
623
+ .join(
624
+ '\n\n',
625
+ )}\n\nIf this ${logName()} should include an error stack trace, add \\n in <stack> to your expected message ` +
626
+ `(e.g., "Error: message\\n in <stack>").`,
627
+ );
628
+ }
629
+
630
+ // Any logs that are missing an error stack trace when \n in <stack> was expected.
631
+ if (unexpectedMissingErrorStack.length > 0) {
632
+ throwFormattedError(
633
+ `${unexpectedMissingErrorStack
634
+ .map(
635
+ stack =>
636
+ `Missing error stack trace for:\n ${printReceived(stack)}`,
637
+ )
638
+ .join(
639
+ '\n\n',
640
+ )}\n\nThe expected message uses \\n in <stack> but the actual ${logName()} doesn't include an error stack trace.` +
641
+ `\nIf this ${logName()} should not have an error stack trace, remove \\n in <stack> from your expected message.`,
642
);
643
}
644
@@ -496,6 +657,25 @@ export function createLogAssertion(
657
.join('\n\n'),
658
);
659
}
660
+
661
+ // Using \n in <stack> for component stacks is a misuse.
662
+ // \n in <stack> should only be used for JavaScript Error stack traces,
663
+ // not for React component stacks.
664
+ if (stackTracePlaceholderMisuses.length > 0) {
665
+ throwFormattedError(
666
+ `${stackTracePlaceholderMisuses
667
+ .map(
668
+ item =>
669
+ `Incorrect use of \\n in <stack> placeholder. The placeholder is for JavaScript Error ` +
670
+ `stack traces (messages starting with "Error:"), not for React component stacks.\n\n` +
671
+ `Expected: ${printReceived(item.expected)}\n` +
672
+ `Received: ${printReceived(item.received)}\n\n` +
673
+ `If this ${logName()} has a component stack, include the full component stack in your expected message ` +
674
+ `(e.g., "Warning message\\n in ComponentName (at **)").`,
675
+ )
676
+ .join('\n\n')}`,
677
+ );
678
+ }
679
}
680
};
681
}
packages/react-client/src/__tests__/ReactFlight-test.js
+3
-2
@@ -1729,7 +1729,8 @@ describe('ReactFlight', () => {
1729
'Only plain objects can be passed to Client Components from Server Components. ' +
1730
'Objects with symbol properties like Symbol.iterator are not supported.\n' +
1731
' <... value={{}}>\n' +
1732
- ' ^^^^\n',
1732
+ ' ^^^^\n' +
1733
+ ' in (at **)',
1734
]);
1735
});
1736
@@ -3258,7 +3259,7 @@ describe('ReactFlight', () => {
3259
const transport = ReactNoopFlightServer.render({
3260
root: ReactServer.createElement(App),
3261
});
3261
- assertConsoleErrorDev(['Error: err']);
3262
+ assertConsoleErrorDev(['Error: err' + '\n in <stack>']);
3263
3264
expect(mockConsoleLog).toHaveBeenCalledTimes(1);
3265
expect(mockConsoleLog.mock.calls[0][0]).toBe('hi');
packages/react-debug-tools/src/__tests__/ReactHooksInspection-test.js
+8
-3
@@ -734,7 +734,11 @@ describe('ReactHooksInspection', () => {
734
});
735
const results = normalizeSourceLoc(tree);
736
expect(results).toHaveLength(1);
737
- expect(results[0]).toMatchInlineSnapshot(`
737
+ expect(results[0]).toMatchInlineSnapshot(
738
+ {
739
+ subHooks: [{value: expect.any(Promise)}],
740
+ },
741
+ `
742
{
743
"debugInfo": null,
744
"hookSource": {
@@ -759,12 +763,13 @@ describe('ReactHooksInspection', () => {
763
"isStateEditable": false,
764
"name": "Use",
765
"subHooks": [],
762
- "value": Promise {},
766
+ "value": Any<Promise>,
767
},
768
],
769
"value": undefined,
770
}
767
- `);
771
+ `,
772
+ );
773
});
774
775
describe('useDebugValue', () => {
packages/react-dom/src/__tests__/ReactDOM-test.js
+15
-4
@@ -548,16 +548,23 @@ describe('ReactDOM', () => {
548
' in App (at **)',
549
// ReactDOM(App > div > ServerEntry) >>> ReactDOMServer(Child) >>> ReactDOMServer(App2) >>> ReactDOMServer(blink)
550
'Invalid ARIA attribute `ariaTypo2`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
551
- ' in blink (at **)',
551
+ ' in blink (at **)\n' +
552
+ ' in App2 (at **)\n' +
553
+ ' in Child (at **)\n' +
554
+ ' in ServerEntry (at **)',
555
// ReactDOM(App > div > ServerEntry) >>> ReactDOMServer(Child) >>> ReactDOMServer(App2 > Child2 > span)
556
'Invalid ARIA attribute `ariaTypo3`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
557
' in span (at **)\n' +
558
' in Child2 (at **)\n' +
556
- ' in App2 (at **)',
559
+ ' in App2 (at **)\n' +
560
+ ' in Child (at **)\n' +
561
+ ' in ServerEntry (at **)',
562
// ReactDOM(App > div > ServerEntry) >>> ReactDOMServer(Child > span)
563
'Invalid ARIA attribute `ariaTypo4`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
564
' in span (at **)\n' +
560
- ' in Child (at **)',
565
+ ' in Child (at **)\n' +
566
+ ' in ServerEntry (at **)',
567
+
568
// ReactDOM(App > div > font)
569
'Invalid ARIA attribute `ariaTypo5`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
570
' in font (at **)\n' +
@@ -775,7 +782,11 @@ describe('ReactDOM', () => {
782
783
// @TODO remove this warning check when we loosen the tag nesting restrictions to allow arbitrary tags at the
784
// root of the application
778
- assertConsoleErrorDev(['In HTML, <head> cannot be a child of <main>']);
785
+ assertConsoleErrorDev([
786
+ 'In HTML, <head> cannot be a child of <main>.\nThis will cause a hydration error.\n' +
787
+ ' in head (at **)\n' +
788
+ ' in App (at **)',
789
+ ]);
790
791
await act(() => {
792
root.render(<App phase={1} />);
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+30
-15
@@ -6879,9 +6879,12 @@ describe('ReactDOMFizzServer', () => {
6879
});
6880
6881
assertConsoleErrorDev([
6882
- 'The render was aborted by the server without a reason.',
6883
- 'The render was aborted by the server without a reason.',
6884
- 'The render was aborted by the server without a reason.',
6882
+ 'Error: The render was aborted by the server without a reason.' +
6883
+ '\n in <stack>',
6884
+ 'Error: The render was aborted by the server without a reason.' +
6885
+ '\n in <stack>',
6886
+ 'Error: The render was aborted by the server without a reason.' +
6887
+ '\n in <stack>',
6888
]);
6889
6890
expect(finished).toBe(true);
@@ -6943,9 +6946,12 @@ describe('ReactDOMFizzServer', () => {
6946
});
6947
6948
assertConsoleErrorDev([
6946
- 'The render was aborted by the server without a reason.',
6947
- 'The render was aborted by the server without a reason.',
6948
- 'The render was aborted by the server without a reason.',
6949
+ 'Error: The render was aborted by the server without a reason.' +
6950
+ '\n in <stack>',
6951
+ 'Error: The render was aborted by the server without a reason.' +
6952
+ '\n in <stack>',
6953
+ 'Error: The render was aborted by the server without a reason.' +
6954
+ '\n in <stack>',
6955
]);
6956
6957
expect(finished).toBe(true);
@@ -7007,9 +7013,12 @@ describe('ReactDOMFizzServer', () => {
7013
});
7014
7015
assertConsoleErrorDev([
7010
- 'The render was aborted by the server without a reason.',
7011
- 'The render was aborted by the server without a reason.',
7012
- 'The render was aborted by the server without a reason.',
7016
+ 'Error: The render was aborted by the server without a reason.' +
7017
+ '\n in <stack>',
7018
+ 'Error: The render was aborted by the server without a reason.' +
7019
+ '\n in <stack>',
7020
+ 'Error: The render was aborted by the server without a reason.' +
7021
+ '\n in <stack>',
7022
]);
7023
7024
expect(finished).toBe(true);
@@ -7069,9 +7078,12 @@ describe('ReactDOMFizzServer', () => {
7078
});
7079
7080
assertConsoleErrorDev([
7072
- 'The render was aborted by the server without a reason.',
7073
- 'The render was aborted by the server without a reason.',
7074
- 'The render was aborted by the server without a reason.',
7081
+ 'Error: The render was aborted by the server without a reason.' +
7082
+ '\n in <stack>',
7083
+ 'Error: The render was aborted by the server without a reason.' +
7084
+ '\n in <stack>',
7085
+ 'Error: The render was aborted by the server without a reason.' +
7086
+ '\n in <stack>',
7087
]);
7088
7089
expect(finished).toBe(true);
@@ -9024,7 +9036,8 @@ describe('ReactDOMFizzServer', () => {
9036
pipe(writable);
9037
});
9038
assertConsoleErrorDev([
9027
- 'React encountered a style tag with `precedence` "default" and `nonce` "R4nd0mR4nd0m". When React manages style rules using `precedence` it will only include rules if the nonce matches the style nonce "R4nd0m" that was included with this render.',
9039
+ 'React encountered a style tag with `precedence` "default" and `nonce` "R4nd0mR4nd0m". When React manages style rules using `precedence` it will only include rules if the nonce matches the style nonce "R4nd0m" that was included with this render.' +
9040
+ '\n in style (at **)',
9041
]);
9042
expect(getVisibleChildren(document)).toEqual(
9043
<html>
@@ -9054,7 +9067,8 @@ describe('ReactDOMFizzServer', () => {
9067
pipe(writable);
9068
});
9069
assertConsoleErrorDev([
9057
- 'React encountered a style tag with `precedence` "default" and `nonce` "R4nd0m". When React manages style rules using `precedence` it will only include a nonce attributes if you also provide the same style nonce value as a render option.',
9070
+ 'React encountered a style tag with `precedence` "default" and `nonce` "R4nd0m". When React manages style rules using `precedence` it will only include a nonce attributes if you also provide the same style nonce value as a render option.' +
9071
+ '\n in style (at **)',
9072
]);
9073
expect(getVisibleChildren(document)).toEqual(
9074
<html>
@@ -9085,7 +9099,8 @@ describe('ReactDOMFizzServer', () => {
9099
pipe(writable);
9100
});
9101
assertConsoleErrorDev([
9088
- 'React encountered a style tag with `precedence` "default" and `nonce` "R4nd0m". When React manages style rules using `precedence` it will only include a nonce attributes if you also provide the same style nonce value as a render option.',
9102
+ 'React encountered a style tag with `precedence` "default" and `nonce` "R4nd0m". When React manages style rules using `precedence` it will only include a nonce attributes if you also provide the same style nonce value as a render option.' +
9103
+ '\n in style (at **)',
9104
]);
9105
expect(getVisibleChildren(document)).toEqual(
9106
<html>
packages/react-dom/src/__tests__/ReactDOMFloat-test.js
+18
-1
@@ -3628,7 +3628,24 @@ body {
3628
assertLog(['load stylesheet: foo']);
3629
await waitForAll([]);
3630
assertConsoleErrorDev([
3631
- "Hydration failed because the server rendered HTML didn't match the client.",
3631
+ "Error: Hydration failed because the server rendered HTML didn't match the client. " +
3632
+ 'As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:\n\n' +
3633
+ "- A server/client branch `if (typeof window !== 'undefined')`.\n" +
3634
+ "- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
3635
+ "- Date formatting in a user's locale which doesn't match the server.\n" +
3636
+ '- External changing data without sending a snapshot of it along with the HTML.\n' +
3637
+ '- Invalid HTML tag nesting.\n\n' +
3638
+ 'It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n\n' +
3639
+ 'https://react.dev/link/hydration-mismatch\n\n' +
3640
+ ' <html>\n' +
3641
+ ' <body>\n' +
3642
+ ' <div>\n' +
3643
+ ' <div>\n' +
3644
+ ' <Suspense fallback="loading 2...">\n' +
3645
+ ' <Component>\n' +
3646
+ ' <link>\n' +
3647
+ '+ <div>' +
3648
+ '\n in <stack>',
3649
]);
3650
jest.runAllTimers();
3651
packages/react-dom/src/__tests__/ReactDOMSrcObject-test.js
+6
-3
@@ -120,11 +120,14 @@ describe('ReactDOMSrcObject', () => {
120
121
assertConsoleErrorDev([
122
'Passing Blob, MediaSource or MediaStream to <source src> is not supported. ' +
123
- 'Pass it directly to <img src>, <video src> or <audio src> instead.',
123
+ 'Pass it directly to <img src>, <video src> or <audio src> instead.' +
124
+ '\n in source (at **)',
125
'Passing Blob, MediaSource or MediaStream to <source src> is not supported. ' +
125
- 'Pass it directly to <img src>, <video src> or <audio src> instead.',
126
+ 'Pass it directly to <img src>, <video src> or <audio src> instead.' +
127
+ '\n in source (at **)',
128
'Passing Blob, MediaSource or MediaStream to <source src> is not supported. ' +
127
- 'Pass it directly to <img src>, <video src> or <audio src> instead.',
129
+ 'Pass it directly to <img src>, <video src> or <audio src> instead.' +
130
+ '\n in source (at **)',
131
]);
132
expect(videoRef.current.firstChild.src).not.toMatch(/^blob:/);
133
expect(videoRef.current.firstChild.src).toContain('[object%20Blob]'); // toString:ed
packages/react-reconciler/src/__tests__/ReactFlushSync-test.js
+2
-1
@@ -110,7 +110,8 @@ describe('ReactFlushSync', () => {
110
assertConsoleErrorDev([
111
'flushSync was called from inside a lifecycle method. React ' +
112
'cannot flush when React is already rendering. Consider moving this ' +
113
- 'call to a scheduler task or micro task.',
113
+ 'call to a scheduler task or micro task.' +
114
+ '\n in App',
115
]);
116
117
await waitForPaint([]);
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js
+56
-29
@@ -2145,7 +2145,8 @@ describe('ReactFlightDOM', () => {
2145
pipe(flightWritable);
2146
});
2147
assertConsoleErrorDev([
2148
- 'The render was aborted by the server without a reason.',
2148
+ 'Error: The render was aborted by the server without a reason.' +
2149
+ '\n in <stack>',
2150
]);
2151
2152
const response =
@@ -2169,9 +2170,12 @@ describe('ReactFlightDOM', () => {
2170
).pipe(fizzWritable);
2171
});
2172
assertConsoleErrorDev([
2172
- 'The render was aborted by the server without a reason.',
2173
- 'The render was aborted by the server without a reason.',
2174
- 'The render was aborted by the server without a reason.',
2173
+ '[Server] Error: The render was aborted by the server without a reason.' +
2174
+ '\n in <stack>',
2175
+ '[Server] Error: The render was aborted by the server without a reason.' +
2176
+ '\n in <stack>',
2177
+ '[Server] Error: The render was aborted by the server without a reason.' +
2178
+ '\n in <stack>',
2179
]);
2180
2181
expect(shellErrors).toEqual([]);
@@ -2235,7 +2239,8 @@ describe('ReactFlightDOM', () => {
2239
});
2240
2241
assertConsoleErrorDev([
2238
- 'The render was aborted by the server without a reason.',
2242
+ 'Error: The render was aborted by the server without a reason.' +
2243
+ '\n in <stack>',
2244
]);
2245
2246
const response =
@@ -2260,9 +2265,12 @@ describe('ReactFlightDOM', () => {
2265
});
2266
2267
assertConsoleErrorDev([
2263
- 'The render was aborted by the server without a reason.',
2264
- 'The render was aborted by the server without a reason.',
2265
- 'The render was aborted by the server without a reason.',
2268
+ '[Server] Error: The render was aborted by the server without a reason.' +
2269
+ '\n in <stack>',
2270
+ '[Server] Error: The render was aborted by the server without a reason.' +
2271
+ '\n in <stack>',
2272
+ '[Server] Error: The render was aborted by the server without a reason.' +
2273
+ '\n in <stack>',
2274
]);
2275
2276
expect(shellErrors).toEqual([]);
@@ -2327,7 +2335,8 @@ describe('ReactFlightDOM', () => {
2335
pipe(flightWritable);
2336
});
2337
assertConsoleErrorDev([
2330
- 'The render was aborted by the server without a reason.',
2338
+ 'Error: The render was aborted by the server without a reason.' +
2339
+ '\n in <stack>',
2340
]);
2341
2342
const response =
@@ -2351,9 +2360,12 @@ describe('ReactFlightDOM', () => {
2360
).pipe(fizzWritable);
2361
});
2362
assertConsoleErrorDev([
2354
- 'The render was aborted by the server without a reason.',
2355
- 'The render was aborted by the server without a reason.',
2356
- 'The render was aborted by the server without a reason.',
2363
+ '[Server] Error: The render was aborted by the server without a reason.' +
2364
+ '\n in <stack>',
2365
+ '[Server] Error: The render was aborted by the server without a reason.' +
2366
+ '\n in <stack>',
2367
+ '[Server] Error: The render was aborted by the server without a reason.' +
2368
+ '\n in <stack>',
2369
]);
2370
2371
expect(shellErrors).toEqual([]);
@@ -2416,7 +2428,8 @@ describe('ReactFlightDOM', () => {
2428
pipe(flightWritable);
2429
});
2430
assertConsoleErrorDev([
2419
- 'The render was aborted by the server without a reason.',
2431
+ 'Error: The render was aborted by the server without a reason.' +
2432
+ '\n in <stack>',
2433
]);
2434
2435
const response =
@@ -2440,9 +2453,12 @@ describe('ReactFlightDOM', () => {
2453
).pipe(fizzWritable);
2454
});
2455
assertConsoleErrorDev([
2443
- 'The render was aborted by the server without a reason.',
2444
- 'The render was aborted by the server without a reason.',
2445
- 'The render was aborted by the server without a reason.',
2456
+ '[Server] Error: The render was aborted by the server without a reason.' +
2457
+ '\n in <stack>',
2458
+ '[Server] Error: The render was aborted by the server without a reason.' +
2459
+ '\n in <stack>',
2460
+ '[Server] Error: The render was aborted by the server without a reason.' +
2461
+ '\n in <stack>',
2462
]);
2463
2464
expect(shellErrors).toEqual([]);
@@ -2504,7 +2520,8 @@ describe('ReactFlightDOM', () => {
2520
});
2521
2522
assertConsoleErrorDev([
2507
- 'The render was aborted by the server without a reason.',
2523
+ 'Error: The render was aborted by the server without a reason.' +
2524
+ '\n in <stack>',
2525
]);
2526
2527
const response =
@@ -2528,9 +2545,12 @@ describe('ReactFlightDOM', () => {
2545
).pipe(fizzWritable);
2546
});
2547
assertConsoleErrorDev([
2531
- 'The render was aborted by the server without a reason.',
2532
- 'The render was aborted by the server without a reason.',
2533
- 'The render was aborted by the server without a reason.',
2548
+ '[Server] Error: The render was aborted by the server without a reason.' +
2549
+ '\n in <stack>',
2550
+ '[Server] Error: The render was aborted by the server without a reason.' +
2551
+ '\n in <stack>',
2552
+ '[Server] Error: The render was aborted by the server without a reason.' +
2553
+ '\n in <stack>',
2554
]);
2555
2556
expect(shellErrors).toEqual([]);
@@ -2596,7 +2616,8 @@ describe('ReactFlightDOM', () => {
2616
});
2617
2618
assertConsoleErrorDev([
2599
- 'The render was aborted by the server without a reason.',
2619
+ 'Error: The render was aborted by the server without a reason.' +
2620
+ '\n in <stack>',
2621
]);
2622
2623
const response =
@@ -2620,8 +2641,10 @@ describe('ReactFlightDOM', () => {
2641
).pipe(fizzWritable);
2642
});
2643
assertConsoleErrorDev([
2623
- 'The render was aborted by the server without a reason.',
2624
- 'The render was aborted by the server without a reason.',
2644
+ '[Server] Error: The render was aborted by the server without a reason.' +
2645
+ '\n in <stack>',
2646
+ '[Server] Error: The render was aborted by the server without a reason.' +
2647
+ '\n in <stack>',
2648
]);
2649
2650
expect(shellErrors).toEqual([]);
@@ -2668,7 +2691,8 @@ describe('ReactFlightDOM', () => {
2691
});
2692
2693
assertConsoleErrorDev([
2671
- 'The render was aborted by the server without a reason.',
2694
+ 'Error: The render was aborted by the server without a reason.' +
2695
+ '\n in <stack>',
2696
]);
2697
2698
const response =
@@ -2692,7 +2716,8 @@ describe('ReactFlightDOM', () => {
2716
).pipe(fizzWritable);
2717
});
2718
assertConsoleErrorDev([
2695
- 'The render was aborted by the server without a reason.',
2719
+ '[Server] Error: The render was aborted by the server without a reason.' +
2720
+ '\n in <stack>',
2721
]);
2722
2723
expect(shellErrors).toEqual([]);
@@ -2760,8 +2785,9 @@ describe('ReactFlightDOM', () => {
2785
});
2786
2787
assertConsoleErrorDev([
2763
- 'The render was aborted by the server without a reason.',
2764
- 'bam!',
2788
+ 'Error: The render was aborted by the server without a reason.' +
2789
+ '\n in <stack>',
2790
+ 'Error: bam!\n in <stack>',
2791
]);
2792
2793
const response =
@@ -2785,8 +2811,9 @@ describe('ReactFlightDOM', () => {
2811
).pipe(fizzWritable);
2812
});
2813
assertConsoleErrorDev([
2788
- 'The render was aborted by the server without a reason.',
2789
- 'bam!',
2814
+ '[Server] Error: The render was aborted by the server without a reason.' +
2815
+ '\n in <stack>',
2816
+ '[Server] Error: bam!\n in <stack>',
2817
]);
2818
2819
expect(shellErrors).toEqual([]);
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMForm-test.js
+4
-2
@@ -957,7 +957,8 @@ describe('ReactFlightDOMForm', () => {
957
'Failed to serialize an action for progressive enhancement:\n' +
958
'Error: React Element cannot be passed to Server Functions from the Client without a temporary reference set. Pass a TemporaryReferenceSet to the options.\n' +
959
' [<div/>]\n' +
960
- ' ^^^^^^',
960
+ ' ^^^^^^' +
961
+ '\n in <stack>',
962
]);
963
964
// The error message was returned as JSX.
@@ -1032,7 +1033,8 @@ describe('ReactFlightDOMForm', () => {
1033
await submitTheForm();
1034
assertConsoleErrorDev([
1035
'Failed to serialize an action for progressive enhancement:\n' +
1035
- 'Error: File/Blob fields are not yet supported in progressive forms. Will fallback to client hydration.',
1036
+ 'Error: File/Blob fields are not yet supported in progressive forms. Will fallback to client hydration.' +
1037
+ '\n in <stack>',
1038
]);
1039
1040
expect(blob instanceof Blob).toBe(true);
packages/react-server/src/__tests__/ReactFlightServer-test.js
+2
-1
@@ -196,7 +196,8 @@ describe('ReactFlight', () => {
196
"The props of this element may help locate this element: { children: 'Free!', [key]: [Getter] }",
197
{withoutStack: true},
198
],
199
- "TypeError: Cannot read properties of undefined (reading 'stack')",
199
+ "TypeError: Cannot read properties of undefined (reading 'stack')" +
200
+ '\n in <stack>',
201
]);
202
});
203
});
packages/react/src/__tests__/ReactChildren-test.js
+9
-7
@@ -864,6 +864,7 @@ describe('ReactChildren', () => {
864
});
865
866
it('warns for mapped list children without keys', async () => {
867
+ spyOnDev(console, 'error').mockImplementation(() => {});
868
function ComponentRenderingMappedChildren({children}) {
869
return (
870
<div>
@@ -883,13 +884,14 @@ describe('ReactChildren', () => {
884
</ComponentRenderingMappedChildren>,
885
);
886
});
886
- assertConsoleErrorDev([
887
- 'Each child in a list should have a unique "key" prop.\n\n' +
888
- 'Check the render method of `ComponentRenderingMappedChildren`.' +
889
- ' See https://react.dev/link/warning-keys for more information.\n' +
890
- ' in div (at **)\n' +
891
- ' in **/ReactChildren-test.js:**:** (at **)',
892
- ]);
887
+ if (__DEV__) {
888
+ const calls = console.error.mock.calls;
889
+ console.error.mockRestore();
890
+ expect(calls.length).toBe(1);
891
+ expect(calls[0][0]).toEqual(
892
+ 'Each child in a list should have a unique "key" prop.%s%s See https://react.dev/link/warning-keys for more information.',
893
+ );
894
+ }
895
});
896
897
it('does not warn for mapped static children without keys', async () => {
packages/react/src/__tests__/createReactClassIntegration-test.js
+5
-2
@@ -349,10 +349,13 @@ describe('create-react-class-integration', () => {
349
});
350
assertConsoleErrorDev([
351
[
352
- 'Component uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
352
+ 'Component uses the legacy childContextTypes API which will soon be removed. ' +
353
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)',
354
{withoutStack: true},
355
],
355
- 'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
356
+ 'Component uses the legacy contextTypes API which will soon be removed. ' +
357
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)' +
358
+ '\n in ReactClassComponent (at **)',
359
]);
360
expect(container.firstChild.className).toBe('foo');
361
});
packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js
+19
-4
@@ -641,11 +641,21 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
641
'The result of getSnapshot should be cached to avoid an infinite loop',
642
{withoutStack: true},
643
],
644
- 'Error: Maximum update depth exceeded',
645
- 'The above error occurred i',
644
+ [
645
+ 'Error: Maximum update depth exceeded. ' +
646
+ 'This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. ' +
647
+ 'React limits the number of nested updates to prevent infinite loops.' +
648
+ '\n in <stack>',
649
+ {withoutStack: true},
650
+ ],
651
+ 'The above error occurred in the <App> component:\n\n' +
652
+ ' in App (at **)\n\n' +
653
+ 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
654
+ 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.',
655
]
656
: [
648
- 'The result of getSnapshot should be cached to avoid an infinite loop',
657
+ 'The result of getSnapshot should be cached to avoid an infinite loop' +
658
+ '\n in App (at **)',
659
],
660
);
661
});
@@ -839,7 +849,12 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
849
await act(() => {
850
ReactDOM.hydrate(React.createElement(App, null), container);
851
});
842
- assertConsoleErrorDev(['Text content did not match']);
852
+ assertConsoleErrorDev([
853
+ 'Warning: Text content did not match. Server: "server" Client: "client"\n' +
854
+ ' in Text (at **)\n' +
855
+ ' in div (at **)\n' +
856
+ ' in App (at **)',
857
+ ]);
858
assertLog(['client', 'Passive effect: client']);
859
}
860
expect(container.textContent).toEqual('client');