11
12
let React;
13
let ReactDOMClient;
14
+let ReactDOM;
15
+let createPortal;
16
let act;
17
let container;
18
let Fragment;
33
Fragment = React.Fragment;
34
Activity = React.unstable_Activity;
35
ReactDOMClient = require('react-dom/client');
36
+ ReactDOM = require('react-dom');
37
+ createPortal = ReactDOM.createPortal;
38
act = require('internal-test-utils').act;
39
const IntersectionMocks = require('./utils/IntersectionMocks');
40
mockIntersectionObserver = IntersectionMocks.mockIntersectionObserver;
44
require('internal-test-utils').assertConsoleErrorDev;
45
46
container = document.createElement('div');
47
+ document.body.innerHTML = '';
48
document.body.appendChild(container);
49
});
50
616
expect(logs).toEqual([]);
617
});
618
619
+ // @gate enableFragmentRefs
620
+ it('applies event listeners to portaled children', async () => {
621
+ const fragmentRef = React.createRef();
622
+ const childARef = React.createRef();
623
+ const childBRef = React.createRef();
624
+ const root = ReactDOMClient.createRoot(container);
625
+
626
+ function Test() {
627
+ return (
628
+ <Fragment ref={fragmentRef}>
629
+ <div id="child-a" ref={childARef} />
630
+ {createPortal(<div id="child-b" ref={childBRef} />, document.body)}
631
+ </Fragment>
632
+ );
633
+ }
634
+
635
+ await act(() => {
636
+ root.render(<Test />);
637
+ });
638
+
639
+ const logs = [];
640
+ fragmentRef.current.addEventListener('click', e => {
641
+ logs.push(e.target.id);
642
+ });
643
+
644
+ childARef.current.click();
645
+ expect(logs).toEqual(['child-a']);
646
+
647
+ logs.length = 0;
648
+ childBRef.current.click();
649
+ expect(logs).toEqual(['child-b']);
650
+ });
651
+
652
describe('with activity', () => {
653
// @gate enableFragmentRefs && enableActivity
654
it('does not apply event listeners to hidden trees', async () => {
1004
expect(fragmentHandle.getRootNode()).toBe(fragmentHandle);
1005
});
1006
});
1007
+
1008
+ describe('compareDocumentPosition', () => {
1009
+ function expectPosition(position, spec) {
1010
+ const positionResult = {
1011
+ following: (position & Node.DOCUMENT_POSITION_FOLLOWING) !== 0,
1012
+ preceding: (position & Node.DOCUMENT_POSITION_PRECEDING) !== 0,
1013
+ contains: (position & Node.DOCUMENT_POSITION_CONTAINS) !== 0,
1014
+ containedBy: (position & Node.DOCUMENT_POSITION_CONTAINED_BY) !== 0,
1015
+ disconnected: (position & Node.DOCUMENT_POSITION_DISCONNECTED) !== 0,
1016
+ implementationSpecific:
1017
+ (position & Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC) !== 0,
1018
+ };
1019
+ expect(positionResult).toEqual(spec);
1020
+ }
1021
+ // @gate enableFragmentRefs
1022
+ it('returns the relationship between the fragment instance and a given node', async () => {
1023
+ const fragmentRef = React.createRef();
1024
+ const beforeRef = React.createRef();
1025
+ const afterRef = React.createRef();
1026
+ const middleChildRef = React.createRef();
1027
+ const firstChildRef = React.createRef();
1028
+ const lastChildRef = React.createRef();
1029
+ const containerRef = React.createRef();
1030
+ const disconnectedElement = document.createElement('div');
1031
+ const root = ReactDOMClient.createRoot(container);
1032
+
1033
+ function Test() {
1034
+ return (
1035
+ <div ref={containerRef}>
1036
+ <div ref={beforeRef} />
1037
+ <React.Fragment ref={fragmentRef}>
1038
+ <div ref={firstChildRef} />
1039
+ <div ref={middleChildRef} />
1040
+ <div ref={lastChildRef} />
1041
+ </React.Fragment>
1042
+ <div ref={afterRef} />
1043
+ </div>
1044
+ );
1045
+ }
1046
+
1047
+ await act(() => root.render(<Test />));
1048
+
1049
+ // document.body is preceding and contains the fragment
1050
+ expectPosition(
1051
+ fragmentRef.current.compareDocumentPosition(document.body),
1052
+ {
1053
+ preceding: true,
1054
+ following: false,
1055
+ contains: true,
1056
+ containedBy: false,
1057
+ disconnected: false,
1058
+ implementationSpecific: false,
1059
+ },
1060
+ );
1061
+
1062
+ // beforeRef is preceding the fragment
1063
+ expectPosition(
1064
+ fragmentRef.current.compareDocumentPosition(beforeRef.current),
1065
+ {
1066
+ preceding: true,
1067
+ following: false,
1068
+ contains: false,
1069
+ containedBy: false,
1070
+ disconnected: false,
1071
+ implementationSpecific: false,
1072
+ },
1073
+ );
1074
+
1075
+ // afterRef is following the fragment
1076
+ expectPosition(
1077
+ fragmentRef.current.compareDocumentPosition(afterRef.current),
1078
+ {
1079
+ preceding: false,
1080
+ following: true,
1081
+ contains: false,
1082
+ containedBy: false,
1083
+ disconnected: false,
1084
+ implementationSpecific: false,
1085
+ },
1086
+ );
1087
+
1088
+ // firstChildRef is contained by the fragment
1089
+ expectPosition(
1090
+ fragmentRef.current.compareDocumentPosition(firstChildRef.current),
1091
+ {
1092
+ preceding: false,
1093
+ following: false,
1094
+ contains: false,
1095
+ containedBy: true,
1096
+ disconnected: false,
1097
+ implementationSpecific: false,
1098
+ },
1099
+ );
1100
+
1101
+ // middleChildRef is contained by the fragment
1102
+ expectPosition(
1103
+ fragmentRef.current.compareDocumentPosition(middleChildRef.current),
1104
+ {
1105
+ preceding: false,
1106
+ following: false,
1107
+ contains: false,
1108
+ containedBy: true,
1109
+ disconnected: false,
1110
+ implementationSpecific: false,
1111
+ },
1112
+ );
1113
+
1114
+ // lastChildRef is contained by the fragment
1115
+ expectPosition(
1116
+ fragmentRef.current.compareDocumentPosition(lastChildRef.current),
1117
+ {
1118
+ preceding: false,
1119
+ following: false,
1120
+ contains: false,
1121
+ containedBy: true,
1122
+ disconnected: false,
1123
+ implementationSpecific: false,
1124
+ },
1125
+ );
1126
+
1127
+ // containerRef preceds and contains the fragment
1128
+ expectPosition(
1129
+ fragmentRef.current.compareDocumentPosition(containerRef.current),
1130
+ {
1131
+ preceding: true,
1132
+ following: false,
1133
+ contains: true,
1134
+ containedBy: false,
1135
+ disconnected: false,
1136
+ implementationSpecific: false,
1137
+ },
1138
+ );
1139
+
1140
+ expectPosition(
1141
+ fragmentRef.current.compareDocumentPosition(disconnectedElement),
1142
+ {
1143
+ preceding: false,
1144
+ following: true,
1145
+ contains: false,
1146
+ containedBy: false,
1147
+ disconnected: true,
1148
+ implementationSpecific: true,
1149
+ },
1150
+ );
1151
+ });
1152
+
1153
+ // @gate enableFragmentRefs
1154
+ it('handles fragment instances with one child', async () => {
1155
+ const fragmentRef = React.createRef();
1156
+ const beforeRef = React.createRef();
1157
+ const afterRef = React.createRef();
1158
+ const containerRef = React.createRef();
1159
+ const onlyChildRef = React.createRef();
1160
+ const disconnectedElement = document.createElement('div');
1161
+ const root = ReactDOMClient.createRoot(container);
1162
+
1163
+ function Test() {
1164
+ return (
1165
+ <div id="container" ref={containerRef}>
1166
+ <div>
1167
+ <div ref={beforeRef} id="before" />
1168
+ <React.Fragment ref={fragmentRef}>
1169
+ <div ref={onlyChildRef} id="within" />
1170
+ </React.Fragment>
1171
+ <div id="after" ref={afterRef} />
1172
+ </div>
1173
+ </div>
1174
+ );
1175
+ }
1176
+
1177
+ await act(() => root.render(<Test />));
1178
+ expectPosition(
1179
+ fragmentRef.current.compareDocumentPosition(beforeRef.current),
1180
+ {
1181
+ preceding: true,
1182
+ following: false,
1183
+ contains: false,
1184
+ containedBy: false,
1185
+ disconnected: false,
1186
+ implementationSpecific: false,
1187
+ },
1188
+ );
1189
+ expectPosition(
1190
+ fragmentRef.current.compareDocumentPosition(afterRef.current),
1191
+ {
1192
+ preceding: false,
1193
+ following: true,
1194
+ contains: false,
1195
+ containedBy: false,
1196
+ disconnected: false,
1197
+ implementationSpecific: false,
1198
+ },
1199
+ );
1200
+ expectPosition(
1201
+ fragmentRef.current.compareDocumentPosition(onlyChildRef.current),
1202
+ {
1203
+ preceding: false,
1204
+ following: false,
1205
+ contains: false,
1206
+ containedBy: true,
1207
+ disconnected: false,
1208
+ implementationSpecific: false,
1209
+ },
1210
+ );
1211
+ expectPosition(
1212
+ fragmentRef.current.compareDocumentPosition(containerRef.current),
1213
+ {
1214
+ preceding: true,
1215
+ following: false,
1216
+ contains: true,
1217
+ containedBy: false,
1218
+ disconnected: false,
1219
+ implementationSpecific: false,
1220
+ },
1221
+ );
1222
+ expectPosition(
1223
+ fragmentRef.current.compareDocumentPosition(disconnectedElement),
1224
+ {
1225
+ preceding: false,
1226
+ following: true,
1227
+ contains: false,
1228
+ containedBy: false,
1229
+ disconnected: true,
1230
+ implementationSpecific: true,
1231
+ },
1232
+ );
1233
+ });
1234
+
1235
+ // @gate enableFragmentRefs
1236
+ it('handles empty fragment instances', async () => {
1237
+ const fragmentRef = React.createRef();
1238
+ const beforeParentRef = React.createRef();
1239
+ const beforeRef = React.createRef();
1240
+ const afterRef = React.createRef();
1241
+ const afterParentRef = React.createRef();
1242
+ const containerRef = React.createRef();
1243
+ const root = ReactDOMClient.createRoot(container);
1244
+
1245
+ function Test() {
1246
+ return (
1247
+ <>
1248
+ <div id="before-container" ref={beforeParentRef} />
1249
+ <div id="container" ref={containerRef}>
1250
+ <div id="before" ref={beforeRef} />
1251
+ <React.Fragment ref={fragmentRef} />
1252
+ <div id="after" ref={afterRef} />
1253
+ </div>
1254
+ <div id="after-container" ref={afterParentRef} />
1255
+ </>
1256
+ );
1257
+ }
1258
+
1259
+ await act(() => root.render(<Test />));
1260
+
1261
+ expectPosition(
1262
+ fragmentRef.current.compareDocumentPosition(document.body),
1263
+ {
1264
+ preceding: true,
1265
+ following: false,
1266
+ contains: true,
1267
+ containedBy: false,
1268
+ disconnected: false,
1269
+ implementationSpecific: true,
1270
+ },
1271
+ );
1272
+ expectPosition(
1273
+ fragmentRef.current.compareDocumentPosition(beforeRef.current),
1274
+ {
1275
+ preceding: true,
1276
+ following: false,
1277
+ contains: false,
1278
+ containedBy: false,
1279
+ disconnected: false,
1280
+ implementationSpecific: true,
1281
+ },
1282
+ );
1283
+ expectPosition(
1284
+ fragmentRef.current.compareDocumentPosition(beforeParentRef.current),
1285
+ {
1286
+ preceding: true,
1287
+ following: false,
1288
+ contains: false,
1289
+ containedBy: false,
1290
+ disconnected: false,
1291
+ implementationSpecific: true,
1292
+ },
1293
+ );
1294
+ expectPosition(
1295
+ fragmentRef.current.compareDocumentPosition(afterRef.current),
1296
+ {
1297
+ preceding: false,
1298
+ following: true,
1299
+ contains: false,
1300
+ containedBy: false,
1301
+ disconnected: false,
1302
+ implementationSpecific: true,
1303
+ },
1304
+ );
1305
+ expectPosition(
1306
+ fragmentRef.current.compareDocumentPosition(afterParentRef.current),
1307
+ {
1308
+ preceding: false,
1309
+ following: true,
1310
+ contains: false,
1311
+ containedBy: false,
1312
+ disconnected: false,
1313
+ implementationSpecific: true,
1314
+ },
1315
+ );
1316
+ expectPosition(
1317
+ fragmentRef.current.compareDocumentPosition(containerRef.current),
1318
+ {
1319
+ preceding: false,
1320
+ following: false,
1321
+ contains: true,
1322
+ containedBy: false,
1323
+ disconnected: false,
1324
+ implementationSpecific: true,
1325
+ },
1326
+ );
1327
+ });
1328
+
1329
+ // @gate enableFragmentRefs
1330
+ it('returns disconnected for comparison with an unmounted fragment instance', async () => {
1331
+ const fragmentRef = React.createRef();
1332
+ const containerRef = React.createRef();
1333
+ const root = ReactDOMClient.createRoot(container);
1334
+
1335
+ function Test({mount}) {
1336
+ return (
1337
+ <div ref={containerRef}>
1338
+ {mount && (
1339
+ <Fragment ref={fragmentRef}>
1340
+ <div />
1341
+ </Fragment>
1342
+ )}
1343
+ </div>
1344
+ );
1345
+ }
1346
+
1347
+ await act(() => root.render(<Test mount={true} />));
1348
+
1349
+ const fragmentHandle = fragmentRef.current;
1350
+
1351
+ expectPosition(
1352
+ fragmentHandle.compareDocumentPosition(containerRef.current),
1353
+ {
1354
+ preceding: true,
1355
+ following: false,
1356
+ contains: true,
1357
+ containedBy: false,
1358
+ disconnected: false,
1359
+ implementationSpecific: false,
1360
+ },
1361
+ );
1362
+
1363
+ await act(() => {
1364
+ root.render(<Test mount={false} />);
1365
+ });
1366
+
1367
+ expectPosition(
1368
+ fragmentHandle.compareDocumentPosition(containerRef.current),
1369
+ {
1370
+ preceding: false,
1371
+ following: false,
1372
+ contains: false,
1373
+ containedBy: false,
1374
+ disconnected: true,
1375
+ implementationSpecific: false,
1376
+ },
1377
+ );
1378
+ });
1379
+
1380
+ describe('with portals', () => {
1381
+ // @gate enableFragmentRefs
1382
+ it('handles portaled elements', async () => {
1383
+ const fragmentRef = React.createRef();
1384
+ const portaledSiblingRef = React.createRef();
1385
+ const portaledChildRef = React.createRef();
1386
+
1387
+ function Test() {
1388
+ return (
1389
+ <div>
1390
+ {createPortal(<div ref={portaledSiblingRef} />, document.body)}
1391
+ <Fragment ref={fragmentRef}>
1392
+ {createPortal(<div ref={portaledChildRef} />, document.body)}
1393
+ <div />
1394
+ </Fragment>
1395
+ </div>
1396
+ );
1397
+ }
1398
+
1399
+ const root = ReactDOMClient.createRoot(container);
1400
+ await act(() => root.render(<Test />));
1401
+
1402
+ // The sibling is preceding in both the DOM and the React tree
1403
+ expectPosition(
1404
+ fragmentRef.current.compareDocumentPosition(
1405
+ portaledSiblingRef.current,
1406
+ ),
1407
+ {
1408
+ preceding: true,
1409
+ following: false,
1410
+ contains: false,
1411
+ containedBy: false,
1412
+ disconnected: false,
1413
+ implementationSpecific: false,
1414
+ },
1415
+ );
1416
+
1417
+ // The child is contained by in the React tree but not in the DOM
1418
+ expectPosition(
1419
+ fragmentRef.current.compareDocumentPosition(portaledChildRef.current),
1420
+ {
1421
+ preceding: false,
1422
+ following: false,
1423
+ contains: false,
1424
+ containedBy: false,
1425
+ disconnected: false,
1426
+ implementationSpecific: true,
1427
+ },
1428
+ );
1429
+ });
1430
+
1431
+ // @gate enableFragmentRefs
1432
+ it('handles multiple portals to the same element', async () => {
1433
+ const root = ReactDOMClient.createRoot(container);
1434
+ const fragmentRef = React.createRef();
1435
+ const childARef = React.createRef();
1436
+ const childBRef = React.createRef();
1437
+ const childCRef = React.createRef();
1438
+
1439
+ function Test() {
1440
+ const [c, setC] = React.useState(false);
1441
+ React.useEffect(() => {
1442
+ setC(true);
1443
+ });
1444
+
1445
+ return (
1446
+ <>
1447
+ {createPortal(
1448
+ <Fragment ref={fragmentRef}>
1449
+ <div id="A" ref={childARef} />
1450
+ {c ? <div id="C" ref={childCRef} /> : null}
1451
+ </Fragment>,
1452
+ document.body,
1453
+ )}
1454
+ {createPortal(<p id="B" ref={childBRef} />, document.body)}
1455
+ </>
1456
+ );
1457
+ }
1458
+
1459
+ await act(() => root.render(<Test />));
1460
+
1461
+ // Due to effect, order is A->B->C
1462
+ expect(document.body.innerHTML).toBe(
1463
+ '<div></div>' +
1464
+ '<div id="A"></div>' +
1465
+ '<p id="B"></p>' +
1466
+ '<div id="C"></div>',
1467
+ );
1468
+
1469
+ expectPosition(
1470
+ fragmentRef.current.compareDocumentPosition(document.body),
1471
+ {
1472
+ preceding: true,
1473
+ following: false,
1474
+ contains: true,
1475
+ containedBy: false,
1476
+ disconnected: false,
1477
+ implementationSpecific: false,
1478
+ },
1479
+ );
1480
+
1481
+ expectPosition(
1482
+ fragmentRef.current.compareDocumentPosition(childARef.current),
1483
+ {
1484
+ preceding: false,
1485
+ following: false,
1486
+ contains: false,
1487
+ containedBy: true,
1488
+ disconnected: false,
1489
+ implementationSpecific: false,
1490
+ },
1491
+ );
1492
+ expectPosition(
1493
+ fragmentRef.current.compareDocumentPosition(childBRef.current),
1494
+ {
1495
+ preceding: false,
1496
+ following: false,
1497
+ contains: false,
1498
+ containedBy: false,
1499
+ disconnected: false,
1500
+ implementationSpecific: true,
1501
+ },
1502
+ );
1503
+ expectPosition(
1504
+ fragmentRef.current.compareDocumentPosition(childCRef.current),
1505
+ {
1506
+ preceding: false,
1507
+ following: false,
1508
+ contains: false,
1509
+ containedBy: true,
1510
+ disconnected: false,
1511
+ implementationSpecific: false,
1512
+ },
1513
+ );
1514
+ });
1515
+
1516
+ // @gate enableFragmentRefs
1517
+ it('handles empty fragments', async () => {
1518
+ const fragmentRef = React.createRef();
1519
+ const childARef = React.createRef();
1520
+ const childBRef = React.createRef();
1521
+
1522
+ function Test() {
1523
+ return (
1524
+ <>
1525
+ <div id="A" ref={childARef} />
1526
+ {createPortal(<Fragment ref={fragmentRef} />, document.body)}
1527
+ <div id="B" ref={childBRef} />
1528
+ </>
1529
+ );
1530
+ }
1531
+
1532
+ const root = ReactDOMClient.createRoot(container);
1533
+ await act(() => root.render(<Test />));
1534
+
1535
+ expectPosition(
1536
+ fragmentRef.current.compareDocumentPosition(document.body),
1537
+ {
1538
+ preceding: true,
1539
+ following: false,
1540
+ contains: true,
1541
+ containedBy: false,
1542
+ disconnected: false,
1543
+ implementationSpecific: true,
1544
+ },
1545
+ );
1546
+ expectPosition(
1547
+ fragmentRef.current.compareDocumentPosition(childARef.current),
1548
+ {
1549
+ preceding: true,
1550
+ following: false,
1551
+ contains: false,
1552
+ containedBy: false,
1553
+ disconnected: false,
1554
+ implementationSpecific: true,
1555
+ },
1556
+ );
1557
+ expectPosition(
1558
+ fragmentRef.current.compareDocumentPosition(childBRef.current),
1559
+ {
1560
+ preceding: true,
1561
+ following: false,
1562
+ contains: false,
1563
+ containedBy: false,
1564
+ disconnected: false,
1565
+ implementationSpecific: true,
1566
+ },
1567
+ );
1568
+ });
1569
+ });
1570
+ });
1571
});