main
js 207 lines 5.33 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @emails react-core
8 */
9
10 'use strict';
11
12 let React;
13 let ReactDOMClient;
14 let ReactDOMSelection;
15 let act;
16
17 let getModernOffsetsFromPoints;
18
19 describe('ReactDOMSelection', () => {
20 beforeEach(() => {
21 React = require('react');
22 ReactDOMClient = require('react-dom/client');
23 ReactDOMSelection = require('react-dom-bindings/src/client/ReactDOMSelection');
24 act = require('internal-test-utils').act;
25
26 ({getModernOffsetsFromPoints} = ReactDOMSelection);
27 });
28
29 // Simple implementation to compare correctness. React's old implementation of
30 // this logic used DOM Range objects and is available for manual testing at
31 // https://gist.github.com/sophiebits/2e6d571f4f10f33b62ea138a6e9c265c.
32 function simpleModernOffsetsFromPoints(
33 outerNode,
34 anchorNode,
35 anchorOffset,
36 focusNode,
37 focusOffset,
38 ) {
39 let start;
40 let end;
41 let length = 0;
42
43 function traverse(node) {
44 if (node.nodeType === Node.TEXT_NODE) {
45 if (node === anchorNode) {
46 start = length + anchorOffset;
47 }
48 if (node === focusNode) {
49 end = length + focusOffset;
50 }
51 length += node.nodeValue.length;
52 return;
53 }
54
55 for (let i = 0; true; i++) {
56 if (node === anchorNode && i === anchorOffset) {
57 start = length;
58 }
59 if (node === focusNode && i === focusOffset) {
60 end = length;
61 }
62 if (i === node.childNodes.length) {
63 break;
64 }
65 const n = node.childNodes[i];
66 traverse(n);
67 }
68 }
69 traverse(outerNode);
70
71 if (start === null || end === null) {
72 throw new Error('Provided anchor/focus nodes were outside of root.');
73 }
74 return {start, end};
75 }
76
77 // Complicated example derived from a real-world DOM tree. Has a bit of
78 // everything.
79 async function getFixture() {
80 const container = document.createElement('div');
81 const root = ReactDOMClient.createRoot(container);
82 await act(() => {
83 root.render(
84 <div>
85 <div>
86 <div>
87 <div>xxxxxxxxxxxxxxxxxxxx</div>
88 </div>
89 x
90 <div>
91 <div>
92 x
93 <div>
94 <div>
95 <div>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</div>
96 <div />
97 <div />
98 <div>xxxxxxxxxxxxxxxxxx</div>
99 </div>
100 </div>
101 </div>
102 </div>
103 <div />
104 </div>
105 <div>
106 <div>
107 <div>
108 <div>xxxx</div>
109 <div>xxxxxxxxxxxxxxxxxxx</div>
110 </div>
111 </div>
112 <div>xxx</div>
113 <div>xxxxx</div>
114 <div>xxx</div>
115 <div>
116 <div>
117 <div>
118 <div>{['x', 'x', 'xxx']}</div>
119 </div>
120 </div>
121 </div>
122 </div>
123 <div>
124 <div>xxxxxx</div>
125 </div>
126 </div>,
127 );
128 });
129 return container.firstChild;
130 }
131
132 it('returns correctly for base case', () => {
133 const node = document.createElement('div');
134 expect(getModernOffsetsFromPoints(node, node, 0, node, 0)).toEqual({
135 start: 0,
136 end: 0,
137 });
138 expect(simpleModernOffsetsFromPoints(node, node, 0, node, 0)).toEqual({
139 start: 0,
140 end: 0,
141 });
142 });
143
144 it('returns correctly for fuzz test', async () => {
145 const fixtureRoot = await getFixture();
146 const allNodes = [fixtureRoot].concat(
147 Array.from(fixtureRoot.querySelectorAll('*')),
148 );
149 expect(allNodes.length).toBe(27);
150 allNodes.slice().forEach(element => {
151 // Add text nodes.
152 allNodes.push(
153 ...Array.from(element.childNodes).filter(n => n.nodeType === 3),
154 );
155 });
156 expect(allNodes.length).toBe(41);
157
158 function randomNode() {
159 return allNodes[(Math.random() * allNodes.length) | 0];
160 }
161 function randomOffset(node) {
162 return (
163 (Math.random() *
164 (1 +
165 (node.nodeType === 3 ? node.nodeValue : node.childNodes).length)) |
166 0
167 );
168 }
169
170 for (let i = 0; i < 2000; i++) {
171 const anchorNode = randomNode();
172 const anchorOffset = randomOffset(anchorNode);
173 const focusNode = randomNode();
174 const focusOffset = randomOffset(focusNode);
175
176 const offsets1 = getModernOffsetsFromPoints(
177 fixtureRoot,
178 anchorNode,
179 anchorOffset,
180 focusNode,
181 focusOffset,
182 );
183 const offsets2 = simpleModernOffsetsFromPoints(
184 fixtureRoot,
185 anchorNode,
186 anchorOffset,
187 focusNode,
188 focusOffset,
189 );
190 if (JSON.stringify(offsets1) !== JSON.stringify(offsets2)) {
191 throw new Error(
192 JSON.stringify(offsets1) +
193 ' does not match ' +
194 JSON.stringify(offsets2) +
195 ' for anchorNode=allNodes[' +
196 allNodes.indexOf(anchorNode) +
197 '], anchorOffset=' +
198 anchorOffset +
199 ', focusNode=allNodes[' +
200 allNodes.indexOf(focusNode) +
201 '], focusOffset=' +
202 focusOffset,
203 );
204 }
205 }
206 });
207 });