OLD | NEW |
| (Empty) |
1 <!doctype html> | |
2 <title>Range mutation tests</title> | |
3 <link rel="author" title="Aryeh Gregor" href=ayg@aryeh.name> | |
4 <meta name=timeout content=long> | |
5 | |
6 <div id=log></div> | |
7 <script src=/resources/testharness.js></script> | |
8 <script src=/resources/testharnessreport.js></script> | |
9 <script src=../common.js></script> | |
10 <script> | |
11 "use strict"; | |
12 | |
13 // These tests probably use too much abstraction and too little copy-paste. | |
14 // Reader beware. | |
15 // | |
16 // TODO: | |
17 // | |
18 // * Lots and lots and lots more different types of ranges | |
19 // * insertBefore() with DocumentFragments | |
20 // * Fill out other insert/remove tests | |
21 // * normalize() (https://www.w3.org/Bugs/Public/show_bug.cgi?id=13843) | |
22 | |
23 // Give a textual description of the range we're testing, for the test names. | |
24 function describeRange(startContainer, startOffset, endContainer, endOffset) { | |
25 if (startContainer == endContainer && startOffset == endOffset) { | |
26 return "range collapsed at (" + startContainer + ", " + startOffset + ")"; | |
27 } else if (startContainer == endContainer) { | |
28 return "range on " + startContainer + " from " + startOffset + " to " + endO
ffset; | |
29 } else { | |
30 return "range from (" + startContainer + ", " + startOffset + ") to (" + end
Container + ", " + endOffset + ")"; | |
31 } | |
32 } | |
33 | |
34 // Lists of the various types of nodes we'll want to use. We use strings that | |
35 // we can later eval(), so that we can produce legible test names. | |
36 var textNodes = [ | |
37 "paras[0].firstChild", | |
38 "paras[1].firstChild", | |
39 "foreignTextNode", | |
40 "xmlTextNode", | |
41 "detachedTextNode", | |
42 "detachedForeignTextNode", | |
43 "detachedXmlTextNode", | |
44 ]; | |
45 var commentNodes = [ | |
46 "comment", | |
47 "foreignComment", | |
48 "xmlComment", | |
49 "detachedComment", | |
50 "detachedForeignComment", | |
51 "detachedXmlComment", | |
52 ]; | |
53 var characterDataNodes = textNodes.concat(commentNodes); | |
54 | |
55 // This function is slightly scary, but it works well enough, so . . . | |
56 // sourceTests is an array of test data that will be altered in mysterious ways | |
57 // before being passed off to doTest, descFn is something that takes an element | |
58 // of sourceTests and produces the first part of a human-readable description | |
59 // of the test, testFn is the function that doTest will call to do the actual | |
60 // work and tell it what results to expect. | |
61 function doTests(sourceTests, descFn, testFn) { | |
62 var tests = []; | |
63 for (var i = 0; i < sourceTests.length; i++) { | |
64 var params = sourceTests[i]; | |
65 var len = params.length; | |
66 tests.push([ | |
67 descFn(params) + ", with unselected " + describeRange(params[len - 4], par
ams[len - 3], params[len - 2], params[len - 1]), | |
68 // The closure here ensures that the params that testFn get are the | |
69 // current version of params, not the version from the last | |
70 // iteration of this loop. We test that none of the parameters | |
71 // evaluate to undefined to catch bugs in our eval'ing, like | |
72 // mistyping a property name. | |
73 function(params) { return function() { | |
74 var evaledParams = params.map(eval); | |
75 for (var i = 0; i < evaledParams.length; i++) { | |
76 assert_true(typeof evaledParams[i] != "undefined", | |
77 "Test bug: " + params[i] + " is undefined"); | |
78 } | |
79 return testFn.apply(null, evaledParams); | |
80 } }(params), | |
81 false, | |
82 params[len - 4], | |
83 params[len - 3], | |
84 params[len - 2], | |
85 params[len - 1] | |
86 ]); | |
87 tests.push([ | |
88 descFn(params) + ", with selected " + describeRange(params[len - 4], param
s[len - 3], params[len - 2], params[len - 1]), | |
89 function(params) { return function(selectedRange) { | |
90 var evaledParams = params.slice(0, len - 4).map(eval); | |
91 for (var i = 0; i < evaledParams.length; i++) { | |
92 assert_true(typeof evaledParams[i] != "undefined", | |
93 "Test bug: " + params[i] + " is undefined"); | |
94 } | |
95 // Override input range with the one that was actually selected when com
puting the expected result. | |
96 evaledParams = evaledParams.concat([selectedRange.startContainer, select
edRange.startOffset, selectedRange.endContainer, selectedRange.endOffset]); | |
97 return testFn.apply(null, evaledParams); | |
98 } }(params), | |
99 true, | |
100 params[len - 4], | |
101 params[len - 3], | |
102 params[len - 2], | |
103 params[len - 1] | |
104 ]); | |
105 } | |
106 generate_tests(doTest, tests); | |
107 } | |
108 | |
109 // Set up the range, call the callback function to do the DOM modification and | |
110 // tell us what to expect. The callback function needs to return a | |
111 // four-element array with the expected start/end containers/offsets, and | |
112 // receives no arguments. useSelection tells us whether the Range should be | |
113 // added to a Selection and the Selection tested to ensure that the mutation | |
114 // affects user selections as well as other ranges; every test is run with this | |
115 // both false and true, because when it's set to true WebKit and Opera fail all | |
116 // tests' sanity checks, which is unhelpful. The last four parameters just | |
117 // tell us what range to build. | |
118 function doTest(callback, useSelection, startContainer, startOffset, endContaine
r, endOffset) { | |
119 // Recreate all the test nodes in case they were altered by the last test | |
120 // run. | |
121 setupRangeTests(); | |
122 startContainer = eval(startContainer); | |
123 startOffset = eval(startOffset); | |
124 endContainer = eval(endContainer); | |
125 endOffset = eval(endOffset); | |
126 | |
127 var ownerDoc = startContainer.nodeType == Node.DOCUMENT_NODE | |
128 ? startContainer | |
129 : startContainer.ownerDocument; | |
130 var range = ownerDoc.createRange(); | |
131 range.setStart(startContainer, startOffset); | |
132 range.setEnd(endContainer, endOffset); | |
133 | |
134 if (useSelection) { | |
135 getSelection().removeAllRanges(); | |
136 getSelection().addRange(range); | |
137 | |
138 // Some browsers refuse to add a range unless it results in an actual visibl
e selection. | |
139 if (!getSelection().rangeCount) | |
140 return; | |
141 | |
142 // Override range with the one that was actually selected as it differs in s
ome browsers. | |
143 range = getSelection().getRangeAt(0); | |
144 } | |
145 | |
146 var expected = callback(range); | |
147 | |
148 assert_equals(range.startContainer, expected[0], | |
149 "Wrong start container"); | |
150 assert_equals(range.startOffset, expected[1], | |
151 "Wrong start offset"); | |
152 assert_equals(range.endContainer, expected[2], | |
153 "Wrong end container"); | |
154 assert_equals(range.endOffset, expected[3], | |
155 "Wrong end offset"); | |
156 } | |
157 | |
158 | |
159 // Now we get to the specific tests. | |
160 | |
161 function testSplitText(oldNode, offset, startContainer, startOffset, endContaine
r, endOffset) { | |
162 // Save these for later | |
163 var originalStartOffset = startOffset; | |
164 var originalEndOffset = endOffset; | |
165 var originalLength = oldNode.length; | |
166 | |
167 var newNode; | |
168 try { | |
169 newNode = oldNode.splitText(offset); | |
170 } catch (e) { | |
171 // Should only happen if offset is negative | |
172 return [startContainer, startOffset, endContainer, endOffset]; | |
173 } | |
174 | |
175 // First we adjust for replacing data: | |
176 // | |
177 // "Replace data with offset offset, count count, and data the empty | |
178 // string." | |
179 // | |
180 // That translates to offset = offset, count = originalLength - offset, | |
181 // data = "". node is oldNode. | |
182 // | |
183 // "For every boundary point whose node is node, and whose offset is | |
184 // greater than offset but less than or equal to offset plus count, set its | |
185 // offset to offset." | |
186 if (startContainer == oldNode | |
187 && startOffset > offset | |
188 && startOffset <= originalLength) { | |
189 startOffset = offset; | |
190 } | |
191 | |
192 if (endContainer == oldNode | |
193 && endOffset > offset | |
194 && endOffset <= originalLength) { | |
195 endOffset = offset; | |
196 } | |
197 | |
198 // "For every boundary point whose node is node, and whose offset is | |
199 // greater than offset plus count, add the length of data to its offset, | |
200 // then subtract count from it." | |
201 // | |
202 // Can't happen: offset plus count is originalLength. | |
203 | |
204 // Now we insert a node, if oldNode's parent isn't null: "For each boundary | |
205 // point whose node is the new parent of the affected node and whose offset | |
206 // is greater than the new index of the affected node, add one to the | |
207 // boundary point's offset." | |
208 if (startContainer == oldNode.parentNode | |
209 && startOffset > 1 + indexOf(oldNode)) { | |
210 startOffset++; | |
211 } | |
212 | |
213 if (endContainer == oldNode.parentNode | |
214 && endOffset > 1 + indexOf(oldNode)) { | |
215 endOffset++; | |
216 } | |
217 | |
218 // Finally, the splitText stuff itself: | |
219 // | |
220 // "If parent is not null, run these substeps: | |
221 // | |
222 // * "For each range whose start node is node and start offset is greater | |
223 // than offset, set its start node to new node and decrease its start | |
224 // offset by offset. | |
225 // | |
226 // * "For each range whose end node is node and end offset is greater | |
227 // than offset, set its end node to new node and decrease its end offset | |
228 // by offset. | |
229 // | |
230 // * "For each range whose start node is parent and start offset is equal | |
231 // to the index of node + 1, increase its start offset by one. | |
232 // | |
233 // * "For each range whose end node is parent and end offset is equal to | |
234 // the index of node + 1, increase its end offset by one." | |
235 if (oldNode.parentNode) { | |
236 if (startContainer == oldNode && originalStartOffset > offset) { | |
237 startContainer = newNode; | |
238 startOffset = originalStartOffset - offset; | |
239 } | |
240 | |
241 if (endContainer == oldNode && originalEndOffset > offset) { | |
242 endContainer = newNode; | |
243 endOffset = originalEndOffset - offset; | |
244 } | |
245 | |
246 if (startContainer == oldNode.parentNode | |
247 && startOffset == 1 + indexOf(oldNode)) { | |
248 startOffset++; | |
249 } | |
250 | |
251 if (endContainer == oldNode.parentNode | |
252 && endOffset == 1 + indexOf(oldNode)) { | |
253 endOffset++; | |
254 } | |
255 } | |
256 | |
257 return [startContainer, startOffset, endContainer, endOffset]; | |
258 } | |
259 | |
260 // The offset argument is unsigned, so per WebIDL -1 should wrap to 4294967295, | |
261 // which is probably longer than the length, so it should throw an exception. | |
262 // This is no different from the other cases where the offset is longer than | |
263 // the length, and the wrapping complicates my testing slightly, so I won't | |
264 // bother testing negative values here or in other cases. | |
265 var splitTextTests = []; | |
266 for (var i = 0; i < textNodes.length; i++) { | |
267 var node = textNodes[i]; | |
268 splitTextTests.push([node, 376, node, 0, node, 1]); | |
269 splitTextTests.push([node, 0, node, 0, node, 0]); | |
270 splitTextTests.push([node, 1, node, 1, node, 1]); | |
271 splitTextTests.push([node, node + ".length", node, node + ".length", node, nod
e + ".length"]); | |
272 splitTextTests.push([node, 1, node, 1, node, 3]); | |
273 splitTextTests.push([node, 2, node, 1, node, 3]); | |
274 splitTextTests.push([node, 3, node, 1, node, 3]); | |
275 } | |
276 | |
277 splitTextTests.push( | |
278 ["paras[0].firstChild", 1, "paras[0]", 0, "paras[0]", 0], | |
279 ["paras[0].firstChild", 1, "paras[0]", 0, "paras[0]", 1], | |
280 ["paras[0].firstChild", 1, "paras[0]", 1, "paras[0]", 1], | |
281 ["paras[0].firstChild", 1, "paras[0].firstChild", 1, "paras[0]", 1], | |
282 ["paras[0].firstChild", 2, "paras[0].firstChild", 1, "paras[0]", 1], | |
283 ["paras[0].firstChild", 3, "paras[0].firstChild", 1, "paras[0]", 1], | |
284 ["paras[0].firstChild", 1, "paras[0]", 0, "paras[0].firstChild", 3], | |
285 ["paras[0].firstChild", 2, "paras[0]", 0, "paras[0].firstChild", 3], | |
286 ["paras[0].firstChild", 3, "paras[0]", 0, "paras[0].firstChild", 3] | |
287 ); | |
288 | |
289 | |
290 function testReplaceDataAlgorithm(node, offset, count, data, callback, startCont
ainer, startOffset, endContainer, endOffset) { | |
291 // Mutation works the same any time DOM Core's "replace data" algorithm is | |
292 // invoked. node, offset, count, data are as in that algorithm. The | |
293 // callback is what does the actual setting. Not to be confused with | |
294 // testReplaceData, which tests the replaceData() method. | |
295 | |
296 // Barring any provision to the contrary, the containers and offsets must | |
297 // not change. | |
298 var expectedStartContainer = startContainer; | |
299 var expectedStartOffset = startOffset; | |
300 var expectedEndContainer = endContainer; | |
301 var expectedEndOffset = endOffset; | |
302 | |
303 var originalParent = node.parentNode; | |
304 var originalData = node.data; | |
305 | |
306 var exceptionThrown = false; | |
307 try { | |
308 callback(); | |
309 } catch (e) { | |
310 // Should only happen if offset is greater than length | |
311 exceptionThrown = true; | |
312 } | |
313 | |
314 assert_equals(node.parentNode, originalParent, | |
315 "Sanity check failed: changing data changed the parent"); | |
316 | |
317 // "User agents must run the following steps whenever they replace data of | |
318 // a CharacterData node, as though they were written in the specification | |
319 // for that algorithm after all other steps. In particular, the steps must | |
320 // not be executed if the algorithm threw an exception." | |
321 if (exceptionThrown) { | |
322 assert_equals(node.data, originalData, | |
323 "Sanity check failed: exception thrown but data changed"); | |
324 } else { | |
325 assert_equals(node.data, | |
326 originalData.substr(0, offset) + data + originalData.substr(offset + count
), | |
327 "Sanity check failed: data not changed as expected"); | |
328 } | |
329 | |
330 // "For every boundary point whose node is node, and whose offset is | |
331 // greater than offset but less than or equal to offset plus count, set | |
332 // its offset to offset." | |
333 if (!exceptionThrown | |
334 && startContainer == node | |
335 && startOffset > offset | |
336 && startOffset <= offset + count) { | |
337 expectedStartOffset = offset; | |
338 } | |
339 | |
340 if (!exceptionThrown | |
341 && endContainer == node | |
342 && endOffset > offset | |
343 && endOffset <= offset + count) { | |
344 expectedEndOffset = offset; | |
345 } | |
346 | |
347 // "For every boundary point whose node is node, and whose offset is | |
348 // greater than offset plus count, add the length of data to its offset, | |
349 // then subtract count from it." | |
350 if (!exceptionThrown | |
351 && startContainer == node | |
352 && startOffset > offset + count) { | |
353 expectedStartOffset += data.length - count; | |
354 } | |
355 | |
356 if (!exceptionThrown | |
357 && endContainer == node | |
358 && endOffset > offset + count) { | |
359 expectedEndOffset += data.length - count; | |
360 } | |
361 | |
362 return [expectedStartContainer, expectedStartOffset, expectedEndContainer, exp
ectedEndOffset]; | |
363 } | |
364 | |
365 function testInsertData(node, offset, data, startContainer, startOffset, endCont
ainer, endOffset) { | |
366 return testReplaceDataAlgorithm(node, offset, 0, data, | |
367 function() { node.insertData(offset, data) }, | |
368 startContainer, startOffset, endContainer, endOffset); | |
369 } | |
370 | |
371 var insertDataTests = []; | |
372 for (var i = 0; i < characterDataNodes.length; i++) { | |
373 var node = characterDataNodes[i]; | |
374 insertDataTests.push([node, 376, '"foo"', node, 0, node, 1]); | |
375 insertDataTests.push([node, 0, '"foo"', node, 0, node, 0]); | |
376 insertDataTests.push([node, 1, '"foo"', node, 1, node, 1]); | |
377 insertDataTests.push([node, node + ".length", '"foo"', node, node + ".length",
node, node + ".length"]); | |
378 insertDataTests.push([node, 1, '"foo"', node, 1, node, 3]); | |
379 insertDataTests.push([node, 2, '"foo"', node, 1, node, 3]); | |
380 insertDataTests.push([node, 3, '"foo"', node, 1, node, 3]); | |
381 | |
382 insertDataTests.push([node, 376, '""', node, 0, node, 1]); | |
383 insertDataTests.push([node, 0, '""', node, 0, node, 0]); | |
384 insertDataTests.push([node, 1, '""', node, 1, node, 1]); | |
385 insertDataTests.push([node, node + ".length", '""', node, node + ".length", no
de, node + ".length"]); | |
386 insertDataTests.push([node, 1, '""', node, 1, node, 3]); | |
387 insertDataTests.push([node, 2, '""', node, 1, node, 3]); | |
388 insertDataTests.push([node, 3, '""', node, 1, node, 3]); | |
389 } | |
390 | |
391 insertDataTests.push( | |
392 ["paras[0].firstChild", 1, '"foo"', "paras[0]", 0, "paras[0]", 0], | |
393 ["paras[0].firstChild", 1, '"foo"', "paras[0]", 0, "paras[0]", 1], | |
394 ["paras[0].firstChild", 1, '"foo"', "paras[0]", 1, "paras[0]", 1], | |
395 ["paras[0].firstChild", 1, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1], | |
396 ["paras[0].firstChild", 2, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1], | |
397 ["paras[0].firstChild", 3, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1], | |
398 ["paras[0].firstChild", 1, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3], | |
399 ["paras[0].firstChild", 2, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3], | |
400 ["paras[0].firstChild", 3, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3] | |
401 ); | |
402 | |
403 | |
404 function testAppendData(node, data, startContainer, startOffset, endContainer, e
ndOffset) { | |
405 return testReplaceDataAlgorithm(node, node.length, 0, data, | |
406 function() { node.appendData(data) }, | |
407 startContainer, startOffset, endContainer, endOffset); | |
408 } | |
409 | |
410 var appendDataTests = []; | |
411 for (var i = 0; i < characterDataNodes.length; i++) { | |
412 var node = characterDataNodes[i]; | |
413 appendDataTests.push([node, '"foo"', node, 0, node, 1]); | |
414 appendDataTests.push([node, '"foo"', node, 0, node, 0]); | |
415 appendDataTests.push([node, '"foo"', node, 1, node, 1]); | |
416 appendDataTests.push([node, '"foo"', node, 0, node, node + ".length"]); | |
417 appendDataTests.push([node, '"foo"', node, 1, node, node + ".length"]); | |
418 appendDataTests.push([node, '"foo"', node, node + ".length", node, node + ".le
ngth"]); | |
419 appendDataTests.push([node, '"foo"', node, 1, node, 3]); | |
420 | |
421 appendDataTests.push([node, '""', node, 0, node, 1]); | |
422 appendDataTests.push([node, '""', node, 0, node, 0]); | |
423 appendDataTests.push([node, '""', node, 1, node, 1]); | |
424 appendDataTests.push([node, '""', node, 0, node, node + ".length"]); | |
425 appendDataTests.push([node, '""', node, 1, node, node + ".length"]); | |
426 appendDataTests.push([node, '""', node, node + ".length", node, node + ".lengt
h"]); | |
427 appendDataTests.push([node, '""', node, 1, node, 3]); | |
428 } | |
429 | |
430 appendDataTests.push( | |
431 ["paras[0].firstChild", '""', "paras[0]", 0, "paras[0]", 0], | |
432 ["paras[0].firstChild", '""', "paras[0]", 0, "paras[0]", 1], | |
433 ["paras[0].firstChild", '""', "paras[0]", 1, "paras[0]", 1], | |
434 ["paras[0].firstChild", '""', "paras[0].firstChild", 1, "paras[0]", 1], | |
435 ["paras[0].firstChild", '""', "paras[0]", 0, "paras[0].firstChild", 3], | |
436 | |
437 ["paras[0].firstChild", '"foo"', "paras[0]", 0, "paras[0]", 0], | |
438 ["paras[0].firstChild", '"foo"', "paras[0]", 0, "paras[0]", 1], | |
439 ["paras[0].firstChild", '"foo"', "paras[0]", 1, "paras[0]", 1], | |
440 ["paras[0].firstChild", '"foo"', "paras[0].firstChild", 1, "paras[0]", 1], | |
441 ["paras[0].firstChild", '"foo"', "paras[0]", 0, "paras[0].firstChild", 3] | |
442 ); | |
443 | |
444 | |
445 function testDeleteData(node, offset, count, startContainer, startOffset, endCon
tainer, endOffset) { | |
446 return testReplaceDataAlgorithm(node, offset, count, "", | |
447 function() { node.deleteData(offset, count) }, | |
448 startContainer, startOffset, endContainer, endOffset); | |
449 } | |
450 | |
451 var deleteDataTests = []; | |
452 for (var i = 0; i < characterDataNodes.length; i++) { | |
453 var node = characterDataNodes[i]; | |
454 deleteDataTests.push([node, 376, 2, node, 0, node, 1]); | |
455 deleteDataTests.push([node, 0, 2, node, 0, node, 0]); | |
456 deleteDataTests.push([node, 1, 2, node, 1, node, 1]); | |
457 deleteDataTests.push([node, node + ".length", 2, node, node + ".length", node,
node + ".length"]); | |
458 deleteDataTests.push([node, 1, 2, node, 1, node, 3]); | |
459 deleteDataTests.push([node, 2, 2, node, 1, node, 3]); | |
460 deleteDataTests.push([node, 3, 2, node, 1, node, 3]); | |
461 | |
462 deleteDataTests.push([node, 376, 0, node, 0, node, 1]); | |
463 deleteDataTests.push([node, 0, 0, node, 0, node, 0]); | |
464 deleteDataTests.push([node, 1, 0, node, 1, node, 1]); | |
465 deleteDataTests.push([node, node + ".length", 0, node, node + ".length", node,
node + ".length"]); | |
466 deleteDataTests.push([node, 1, 0, node, 1, node, 3]); | |
467 deleteDataTests.push([node, 2, 0, node, 1, node, 3]); | |
468 deleteDataTests.push([node, 3, 0, node, 1, node, 3]); | |
469 | |
470 deleteDataTests.push([node, 376, 631, node, 0, node, 1]); | |
471 deleteDataTests.push([node, 0, 631, node, 0, node, 0]); | |
472 deleteDataTests.push([node, 1, 631, node, 1, node, 1]); | |
473 deleteDataTests.push([node, node + ".length", 631, node, node + ".length", nod
e, node + ".length"]); | |
474 deleteDataTests.push([node, 1, 631, node, 1, node, 3]); | |
475 deleteDataTests.push([node, 2, 631, node, 1, node, 3]); | |
476 deleteDataTests.push([node, 3, 631, node, 1, node, 3]); | |
477 } | |
478 | |
479 deleteDataTests.push( | |
480 ["paras[0].firstChild", 1, 2, "paras[0]", 0, "paras[0]", 0], | |
481 ["paras[0].firstChild", 1, 2, "paras[0]", 0, "paras[0]", 1], | |
482 ["paras[0].firstChild", 1, 2, "paras[0]", 1, "paras[0]", 1], | |
483 ["paras[0].firstChild", 1, 2, "paras[0].firstChild", 1, "paras[0]", 1], | |
484 ["paras[0].firstChild", 2, 2, "paras[0].firstChild", 1, "paras[0]", 1], | |
485 ["paras[0].firstChild", 3, 2, "paras[0].firstChild", 1, "paras[0]", 1], | |
486 ["paras[0].firstChild", 1, 2, "paras[0]", 0, "paras[0].firstChild", 3], | |
487 ["paras[0].firstChild", 2, 2, "paras[0]", 0, "paras[0].firstChild", 3], | |
488 ["paras[0].firstChild", 3, 2, "paras[0]", 0, "paras[0].firstChild", 3] | |
489 ); | |
490 | |
491 | |
492 function testReplaceData(node, offset, count, data, startContainer, startOffset,
endContainer, endOffset) { | |
493 return testReplaceDataAlgorithm(node, offset, count, data, | |
494 function() { node.replaceData(offset, count, data) }, | |
495 startContainer, startOffset, endContainer, endOffset); | |
496 } | |
497 | |
498 var replaceDataTests = []; | |
499 for (var i = 0; i < characterDataNodes.length; i++) { | |
500 var node = characterDataNodes[i]; | |
501 replaceDataTests.push([node, 376, 0, '"foo"', node, 0, node, 1]); | |
502 replaceDataTests.push([node, 0, 0, '"foo"', node, 0, node, 0]); | |
503 replaceDataTests.push([node, 1, 0, '"foo"', node, 1, node, 1]); | |
504 replaceDataTests.push([node, node + ".length", 0, '"foo"', node, node + ".leng
th", node, node + ".length"]); | |
505 replaceDataTests.push([node, 1, 0, '"foo"', node, 1, node, 3]); | |
506 replaceDataTests.push([node, 2, 0, '"foo"', node, 1, node, 3]); | |
507 replaceDataTests.push([node, 3, 0, '"foo"', node, 1, node, 3]); | |
508 | |
509 replaceDataTests.push([node, 376, 0, '""', node, 0, node, 1]); | |
510 replaceDataTests.push([node, 0, 0, '""', node, 0, node, 0]); | |
511 replaceDataTests.push([node, 1, 0, '""', node, 1, node, 1]); | |
512 replaceDataTests.push([node, node + ".length", 0, '""', node, node + ".length"
, node, node + ".length"]); | |
513 replaceDataTests.push([node, 1, 0, '""', node, 1, node, 3]); | |
514 replaceDataTests.push([node, 2, 0, '""', node, 1, node, 3]); | |
515 replaceDataTests.push([node, 3, 0, '""', node, 1, node, 3]); | |
516 | |
517 replaceDataTests.push([node, 376, 1, '"foo"', node, 0, node, 1]); | |
518 replaceDataTests.push([node, 0, 1, '"foo"', node, 0, node, 0]); | |
519 replaceDataTests.push([node, 1, 1, '"foo"', node, 1, node, 1]); | |
520 replaceDataTests.push([node, node + ".length", 1, '"foo"', node, node + ".leng
th", node, node + ".length"]); | |
521 replaceDataTests.push([node, 1, 1, '"foo"', node, 1, node, 3]); | |
522 replaceDataTests.push([node, 2, 1, '"foo"', node, 1, node, 3]); | |
523 replaceDataTests.push([node, 3, 1, '"foo"', node, 1, node, 3]); | |
524 | |
525 replaceDataTests.push([node, 376, 1, '""', node, 0, node, 1]); | |
526 replaceDataTests.push([node, 0, 1, '""', node, 0, node, 0]); | |
527 replaceDataTests.push([node, 1, 1, '""', node, 1, node, 1]); | |
528 replaceDataTests.push([node, node + ".length", 1, '""', node, node + ".length"
, node, node + ".length"]); | |
529 replaceDataTests.push([node, 1, 1, '""', node, 1, node, 3]); | |
530 replaceDataTests.push([node, 2, 1, '""', node, 1, node, 3]); | |
531 replaceDataTests.push([node, 3, 1, '""', node, 1, node, 3]); | |
532 | |
533 replaceDataTests.push([node, 376, 47, '"foo"', node, 0, node, 1]); | |
534 replaceDataTests.push([node, 0, 47, '"foo"', node, 0, node, 0]); | |
535 replaceDataTests.push([node, 1, 47, '"foo"', node, 1, node, 1]); | |
536 replaceDataTests.push([node, node + ".length", 47, '"foo"', node, node + ".len
gth", node, node + ".length"]); | |
537 replaceDataTests.push([node, 1, 47, '"foo"', node, 1, node, 3]); | |
538 replaceDataTests.push([node, 2, 47, '"foo"', node, 1, node, 3]); | |
539 replaceDataTests.push([node, 3, 47, '"foo"', node, 1, node, 3]); | |
540 | |
541 replaceDataTests.push([node, 376, 47, '""', node, 0, node, 1]); | |
542 replaceDataTests.push([node, 0, 47, '""', node, 0, node, 0]); | |
543 replaceDataTests.push([node, 1, 47, '""', node, 1, node, 1]); | |
544 replaceDataTests.push([node, node + ".length", 47, '""', node, node + ".length
", node, node + ".length"]); | |
545 replaceDataTests.push([node, 1, 47, '""', node, 1, node, 3]); | |
546 replaceDataTests.push([node, 2, 47, '""', node, 1, node, 3]); | |
547 replaceDataTests.push([node, 3, 47, '""', node, 1, node, 3]); | |
548 } | |
549 | |
550 replaceDataTests.push( | |
551 ["paras[0].firstChild", 1, 0, '"foo"', "paras[0]", 0, "paras[0]", 0], | |
552 ["paras[0].firstChild", 1, 0, '"foo"', "paras[0]", 0, "paras[0]", 1], | |
553 ["paras[0].firstChild", 1, 0, '"foo"', "paras[0]", 1, "paras[0]", 1], | |
554 ["paras[0].firstChild", 1, 0, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1
], | |
555 ["paras[0].firstChild", 2, 0, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1
], | |
556 ["paras[0].firstChild", 3, 0, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1
], | |
557 ["paras[0].firstChild", 1, 0, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3
], | |
558 ["paras[0].firstChild", 2, 0, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3
], | |
559 ["paras[0].firstChild", 3, 0, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3
], | |
560 | |
561 ["paras[0].firstChild", 1, 1, '"foo"', "paras[0]", 0, "paras[0]", 0], | |
562 ["paras[0].firstChild", 1, 1, '"foo"', "paras[0]", 0, "paras[0]", 1], | |
563 ["paras[0].firstChild", 1, 1, '"foo"', "paras[0]", 1, "paras[0]", 1], | |
564 ["paras[0].firstChild", 1, 1, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1
], | |
565 ["paras[0].firstChild", 2, 1, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1
], | |
566 ["paras[0].firstChild", 3, 1, '"foo"', "paras[0].firstChild", 1, "paras[0]", 1
], | |
567 ["paras[0].firstChild", 1, 1, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3
], | |
568 ["paras[0].firstChild", 2, 1, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3
], | |
569 ["paras[0].firstChild", 3, 1, '"foo"', "paras[0]", 0, "paras[0].firstChild", 3
], | |
570 | |
571 ["paras[0].firstChild", 1, 47, '"foo"', "paras[0]", 0, "paras[0]", 0], | |
572 ["paras[0].firstChild", 1, 47, '"foo"', "paras[0]", 0, "paras[0]", 1], | |
573 ["paras[0].firstChild", 1, 47, '"foo"', "paras[0]", 1, "paras[0]", 1], | |
574 ["paras[0].firstChild", 1, 47, '"foo"', "paras[0].firstChild", 1, "paras[0]",
1], | |
575 ["paras[0].firstChild", 2, 47, '"foo"', "paras[0].firstChild", 1, "paras[0]",
1], | |
576 ["paras[0].firstChild", 3, 47, '"foo"', "paras[0].firstChild", 1, "paras[0]",
1], | |
577 ["paras[0].firstChild", 1, 47, '"foo"', "paras[0]", 0, "paras[0].firstChild",
3], | |
578 ["paras[0].firstChild", 2, 47, '"foo"', "paras[0]", 0, "paras[0].firstChild",
3], | |
579 ["paras[0].firstChild", 3, 47, '"foo"', "paras[0]", 0, "paras[0].firstChild",
3] | |
580 ); | |
581 | |
582 | |
583 // There are lots of ways to set data, so we pass a callback that does the | |
584 // actual setting. | |
585 function testDataChange(node, attr, op, rval, startContainer, startOffset, endCo
ntainer, endOffset) { | |
586 return testReplaceDataAlgorithm(node, 0, node.length, op == "=" ? rval : node[
attr] + rval, | |
587 function() { | |
588 if (op == "=") { | |
589 node[attr] = rval; | |
590 } else if (op == "+=") { | |
591 node[attr] += rval; | |
592 } else { | |
593 throw "Unknown op " + op; | |
594 } | |
595 }, | |
596 startContainer, startOffset, endContainer, endOffset); | |
597 } | |
598 | |
599 var dataChangeTests = []; | |
600 var dataChangeTestAttrs = ["data", "textContent", "nodeValue"]; | |
601 for (var i = 0; i < characterDataNodes.length; i++) { | |
602 var node = characterDataNodes[i]; | |
603 var dataChangeTestRanges = [ | |
604 [node, 0, node, 0], | |
605 [node, 0, node, 1], | |
606 [node, 1, node, 1], | |
607 [node, 0, node, node + ".length"], | |
608 [node, 1, node, node + ".length"], | |
609 [node, node + ".length", node, node + ".length"], | |
610 ]; | |
611 | |
612 for (var j = 0; j < dataChangeTestRanges.length; j++) { | |
613 for (var k = 0; k < dataChangeTestAttrs.length; k++) { | |
614 dataChangeTests.push([ | |
615 node, | |
616 '"' + dataChangeTestAttrs[k] + '"', | |
617 '"="', | |
618 '""', | |
619 ].concat(dataChangeTestRanges[j])); | |
620 | |
621 dataChangeTests.push([ | |
622 node, | |
623 '"' + dataChangeTestAttrs[k] + '"', | |
624 '"="', | |
625 '"foo"', | |
626 ].concat(dataChangeTestRanges[j])); | |
627 | |
628 dataChangeTests.push([ | |
629 node, | |
630 '"' + dataChangeTestAttrs[k] + '"', | |
631 '"="', | |
632 node + "." + dataChangeTestAttrs[k], | |
633 ].concat(dataChangeTestRanges[j])); | |
634 | |
635 dataChangeTests.push([ | |
636 node, | |
637 '"' + dataChangeTestAttrs[k] + '"', | |
638 '"+="', | |
639 '""', | |
640 ].concat(dataChangeTestRanges[j])); | |
641 | |
642 dataChangeTests.push([ | |
643 node, | |
644 '"' + dataChangeTestAttrs[k] + '"', | |
645 '"+="', | |
646 '"foo"', | |
647 ].concat(dataChangeTestRanges[j])); | |
648 | |
649 dataChangeTests.push([ | |
650 node, | |
651 '"' + dataChangeTestAttrs[k] + '"', | |
652 '"+="', | |
653 node + "." + dataChangeTestAttrs[k] | |
654 ].concat(dataChangeTestRanges[j])); | |
655 } | |
656 } | |
657 } | |
658 | |
659 | |
660 // Now we test node insertions and deletions, as opposed to just data changes. | |
661 // To avoid loads of repetition, we define modifyForRemove() and | |
662 // modifyForInsert(). | |
663 | |
664 // If we were to remove removedNode from its parent, what would the boundary | |
665 // point [node, offset] become? Returns [new node, new offset]. Must be | |
666 // called BEFORE the node is actually removed, so its parent is not null. (If | |
667 // the parent is null, it will do nothing.) | |
668 function modifyForRemove(removedNode, point) { | |
669 var oldParent = removedNode.parentNode; | |
670 var oldIndex = indexOf(removedNode); | |
671 if (!oldParent) { | |
672 return point; | |
673 } | |
674 | |
675 // "For each boundary point whose node is removed node or a descendant of | |
676 // it, set the boundary point to (old parent, old index)." | |
677 if (point[0] == removedNode || isDescendant(point[0], removedNode)) { | |
678 return [oldParent, oldIndex]; | |
679 } | |
680 | |
681 // "For each boundary point whose node is old parent and whose offset is | |
682 // greater than old index, subtract one from its offset." | |
683 if (point[0] == oldParent && point[1] > oldIndex) { | |
684 return [point[0], point[1] - 1]; | |
685 } | |
686 | |
687 return point; | |
688 } | |
689 | |
690 // Update the given boundary point [node, offset] to account for the fact that | |
691 // insertedNode was just inserted into its current position. This must be | |
692 // called AFTER insertedNode was already inserted. | |
693 function modifyForInsert(insertedNode, point) { | |
694 // "For each boundary point whose node is the new parent of the affected | |
695 // node and whose offset is greater than the new index of the affected | |
696 // node, add one to the boundary point's offset." | |
697 if (point[0] == insertedNode.parentNode && point[1] > indexOf(insertedNode)) { | |
698 return [point[0], point[1] + 1]; | |
699 } | |
700 | |
701 return point; | |
702 } | |
703 | |
704 | |
705 function testInsertBefore(newParent, affectedNode, refNode, startContainer, star
tOffset, endContainer, endOffset) { | |
706 var expectedStart = [startContainer, startOffset]; | |
707 var expectedEnd = [endContainer, endOffset]; | |
708 | |
709 expectedStart = modifyForRemove(affectedNode, expectedStart); | |
710 expectedEnd = modifyForRemove(affectedNode, expectedEnd); | |
711 | |
712 try { | |
713 newParent.insertBefore(affectedNode, refNode); | |
714 } catch (e) { | |
715 // For our purposes, assume that DOM Core is true -- i.e., ignore | |
716 // mutation events and similar. | |
717 return [startContainer, startOffset, endContainer, endOffset]; | |
718 } | |
719 | |
720 expectedStart = modifyForInsert(affectedNode, expectedStart); | |
721 expectedEnd = modifyForInsert(affectedNode, expectedEnd); | |
722 | |
723 return expectedStart.concat(expectedEnd); | |
724 } | |
725 | |
726 var insertBeforeTests = [ | |
727 // Moving a node to its current position | |
728 ["testDiv", "paras[0]", "paras[1]", "paras[0]", 0, "paras[0]", 0], | |
729 ["testDiv", "paras[0]", "paras[1]", "paras[0]", 0, "paras[0]", 1], | |
730 ["testDiv", "paras[0]", "paras[1]", "paras[0]", 1, "paras[0]", 1], | |
731 ["testDiv", "paras[0]", "paras[1]", "testDiv", 0, "testDiv", 2], | |
732 ["testDiv", "paras[0]", "paras[1]", "testDiv", 1, "testDiv", 1], | |
733 ["testDiv", "paras[0]", "paras[1]", "testDiv", 1, "testDiv", 2], | |
734 ["testDiv", "paras[0]", "paras[1]", "testDiv", 2, "testDiv", 2], | |
735 | |
736 // Stuff that actually moves something. Note that paras[0] and paras[1] | |
737 // are both children of testDiv. | |
738 ["paras[0]", "paras[1]", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 0], | |
739 ["paras[0]", "paras[1]", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1], | |
740 ["paras[0]", "paras[1]", "paras[0].firstChild", "paras[0]", 1, "paras[0]", 1], | |
741 ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 0, "testDiv", 1], | |
742 ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 0, "testDiv", 2], | |
743 ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 1, "testDiv", 1], | |
744 ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 1, "testDiv", 2], | |
745 ["paras[0]", "paras[1]", "null", "paras[0]", 0, "paras[0]", 0], | |
746 ["paras[0]", "paras[1]", "null", "paras[0]", 0, "paras[0]", 1], | |
747 ["paras[0]", "paras[1]", "null", "paras[0]", 1, "paras[0]", 1], | |
748 ["paras[0]", "paras[1]", "null", "testDiv", 0, "testDiv", 1], | |
749 ["paras[0]", "paras[1]", "null", "testDiv", 0, "testDiv", 2], | |
750 ["paras[0]", "paras[1]", "null", "testDiv", 1, "testDiv", 1], | |
751 ["paras[0]", "paras[1]", "null", "testDiv", 1, "testDiv", 2], | |
752 ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc",
0, "foreignDoc", 0], | |
753 ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc",
0, "foreignDoc", 1], | |
754 ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc",
0, "foreignDoc", 2], | |
755 ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc",
1, "foreignDoc", 1], | |
756 ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 0, "fore
ignDoc", 0], | |
757 ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 0, "fore
ignDoc", 1], | |
758 ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 0, "fore
ignDoc", 2], | |
759 ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 1, "fore
ignDoc", 1], | |
760 ["foreignDoc", "detachedComment", "null", "foreignDoc", 0, "foreignDoc", 1], | |
761 ["paras[0]", "xmlTextNode", "paras[0].firstChild", "paras[0]", 0, "paras[0]",
0], | |
762 ["paras[0]", "xmlTextNode", "paras[0].firstChild", "paras[0]", 0, "paras[0]",
1], | |
763 ["paras[0]", "xmlTextNode", "paras[0].firstChild", "paras[0]", 1, "paras[0]",
1], | |
764 | |
765 // Stuff that throws exceptions | |
766 ["paras[0]", "paras[0]", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1], | |
767 ["paras[0]", "testDiv", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1], | |
768 ["paras[0]", "document", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1], | |
769 ["paras[0]", "foreignDoc", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1
], | |
770 ["paras[0]", "document.doctype", "paras[0].firstChild", "paras[0]", 0, "paras[
0]", 1], | |
771 ]; | |
772 | |
773 | |
774 function testReplaceChild(newParent, newChild, oldChild, startContainer, startOf
fset, endContainer, endOffset) { | |
775 var expectedStart = [startContainer, startOffset]; | |
776 var expectedEnd = [endContainer, endOffset]; | |
777 | |
778 expectedStart = modifyForRemove(oldChild, expectedStart); | |
779 expectedEnd = modifyForRemove(oldChild, expectedEnd); | |
780 | |
781 if (newChild != oldChild) { | |
782 // Don't do this twice, if they're the same! | |
783 expectedStart = modifyForRemove(newChild, expectedStart); | |
784 expectedEnd = modifyForRemove(newChild, expectedEnd); | |
785 } | |
786 | |
787 try { | |
788 newParent.replaceChild(newChild, oldChild); | |
789 } catch (e) { | |
790 return [startContainer, startOffset, endContainer, endOffset]; | |
791 } | |
792 | |
793 expectedStart = modifyForInsert(newChild, expectedStart); | |
794 expectedEnd = modifyForInsert(newChild, expectedEnd); | |
795 | |
796 return expectedStart.concat(expectedEnd); | |
797 } | |
798 | |
799 var replaceChildTests = [ | |
800 // Moving a node to its current position. Doesn't match most browsers' | |
801 // behavior, but we probably want to keep the spec the same anyway: | |
802 // https://bugzilla.mozilla.org/show_bug.cgi?id=647603 | |
803 ["testDiv", "paras[0]", "paras[0]", "paras[0]", 0, "paras[0]", 0], | |
804 ["testDiv", "paras[0]", "paras[0]", "paras[0]", 0, "paras[0]", 1], | |
805 ["testDiv", "paras[0]", "paras[0]", "paras[0]", 1, "paras[0]", 1], | |
806 ["testDiv", "paras[0]", "paras[0]", "testDiv", 0, "testDiv", 2], | |
807 ["testDiv", "paras[0]", "paras[0]", "testDiv", 1, "testDiv", 1], | |
808 ["testDiv", "paras[0]", "paras[0]", "testDiv", 1, "testDiv", 2], | |
809 ["testDiv", "paras[0]", "paras[0]", "testDiv", 2, "testDiv", 2], | |
810 | |
811 // Stuff that actually moves something. | |
812 ["paras[0]", "paras[1]", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 0], | |
813 ["paras[0]", "paras[1]", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1], | |
814 ["paras[0]", "paras[1]", "paras[0].firstChild", "paras[0]", 1, "paras[0]", 1], | |
815 ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 0, "testDiv", 1], | |
816 ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 0, "testDiv", 2], | |
817 ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 1, "testDiv", 1], | |
818 ["paras[0]", "paras[1]", "paras[0].firstChild", "testDiv", 1, "testDiv", 2], | |
819 ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc",
0, "foreignDoc", 0], | |
820 ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc",
0, "foreignDoc", 1], | |
821 ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc",
0, "foreignDoc", 2], | |
822 ["foreignDoc", "detachedComment", "foreignDoc.documentElement", "foreignDoc",
1, "foreignDoc", 1], | |
823 ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 0, "fore
ignDoc", 0], | |
824 ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 0, "fore
ignDoc", 1], | |
825 ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 0, "fore
ignDoc", 2], | |
826 ["foreignDoc", "detachedComment", "foreignDoc.doctype", "foreignDoc", 1, "fore
ignDoc", 1], | |
827 ["paras[0]", "xmlTextNode", "paras[0].firstChild", "paras[0]", 0, "paras[0]",
0], | |
828 ["paras[0]", "xmlTextNode", "paras[0].firstChild", "paras[0]", 0, "paras[0]",
1], | |
829 ["paras[0]", "xmlTextNode", "paras[0].firstChild", "paras[0]", 1, "paras[0]",
1], | |
830 | |
831 // Stuff that throws exceptions | |
832 ["paras[0]", "paras[0]", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1], | |
833 ["paras[0]", "testDiv", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1], | |
834 ["paras[0]", "document", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1], | |
835 ["paras[0]", "foreignDoc", "paras[0].firstChild", "paras[0]", 0, "paras[0]", 1
], | |
836 ["paras[0]", "document.doctype", "paras[0].firstChild", "paras[0]", 0, "paras[
0]", 1], | |
837 ]; | |
838 | |
839 | |
840 function testAppendChild(newParent, affectedNode, startContainer, startOffset, e
ndContainer, endOffset) { | |
841 var expectedStart = [startContainer, startOffset]; | |
842 var expectedEnd = [endContainer, endOffset]; | |
843 | |
844 expectedStart = modifyForRemove(affectedNode, expectedStart); | |
845 expectedEnd = modifyForRemove(affectedNode, expectedEnd); | |
846 | |
847 try { | |
848 newParent.appendChild(affectedNode); | |
849 } catch (e) { | |
850 return [startContainer, startOffset, endContainer, endOffset]; | |
851 } | |
852 | |
853 // These two lines will actually never do anything, if you think about it, | |
854 // but let's leave them in so correctness is more obvious. | |
855 expectedStart = modifyForInsert(affectedNode, expectedStart); | |
856 expectedEnd = modifyForInsert(affectedNode, expectedEnd); | |
857 | |
858 return expectedStart.concat(expectedEnd); | |
859 } | |
860 | |
861 var appendChildTests = [ | |
862 // Moving a node to its current position | |
863 ["testDiv", "testDiv.lastChild", "testDiv.lastChild", 0, "testDiv.lastChild",
0], | |
864 ["testDiv", "testDiv.lastChild", "testDiv.lastChild", 0, "testDiv.lastChild",
1], | |
865 ["testDiv", "testDiv.lastChild", "testDiv.lastChild", 1, "testDiv.lastChild",
1], | |
866 ["testDiv", "testDiv.lastChild", "testDiv", "testDiv.childNodes.length - 2", "
testDiv", "testDiv.childNodes.length"], | |
867 ["testDiv", "testDiv.lastChild", "testDiv", "testDiv.childNodes.length - 2", "
testDiv", "testDiv.childNodes.length - 1"], | |
868 ["testDiv", "testDiv.lastChild", "testDiv", "testDiv.childNodes.length - 1", "
testDiv", "testDiv.childNodes.length"], | |
869 ["testDiv", "testDiv.lastChild", "testDiv", "testDiv.childNodes.length - 1", "
testDiv", "testDiv.childNodes.length - 1"], | |
870 ["testDiv", "testDiv.lastChild", "testDiv", "testDiv.childNodes.length", "test
Div", "testDiv.childNodes.length"], | |
871 ["detachedDiv", "detachedDiv.lastChild", "detachedDiv.lastChild", 0, "detached
Div.lastChild", 0], | |
872 ["detachedDiv", "detachedDiv.lastChild", "detachedDiv.lastChild", 0, "detached
Div.lastChild", 1], | |
873 ["detachedDiv", "detachedDiv.lastChild", "detachedDiv.lastChild", 1, "detached
Div.lastChild", 1], | |
874 ["detachedDiv", "detachedDiv.lastChild", "detachedDiv", "detachedDiv.childNode
s.length - 2", "detachedDiv", "detachedDiv.childNodes.length"], | |
875 ["detachedDiv", "detachedDiv.lastChild", "detachedDiv", "detachedDiv.childNode
s.length - 2", "detachedDiv", "detachedDiv.childNodes.length - 1"], | |
876 ["detachedDiv", "detachedDiv.lastChild", "detachedDiv", "detachedDiv.childNode
s.length - 1", "detachedDiv", "detachedDiv.childNodes.length"], | |
877 ["detachedDiv", "detachedDiv.lastChild", "detachedDiv", "detachedDiv.childNode
s.length - 1", "detachedDiv", "detachedDiv.childNodes.length - 1"], | |
878 ["detachedDiv", "detachedDiv.lastChild", "detachedDiv", "detachedDiv.childNode
s.length", "detachedDiv", "detachedDiv.childNodes.length"], | |
879 | |
880 // Stuff that actually moves something | |
881 ["paras[0]", "paras[1]", "paras[0]", 0, "paras[0]", 0], | |
882 ["paras[0]", "paras[1]", "paras[0]", 0, "paras[0]", 1], | |
883 ["paras[0]", "paras[1]", "paras[0]", 1, "paras[0]", 1], | |
884 ["paras[0]", "paras[1]", "testDiv", 0, "testDiv", 1], | |
885 ["paras[0]", "paras[1]", "testDiv", 0, "testDiv", 2], | |
886 ["paras[0]", "paras[1]", "testDiv", 1, "testDiv", 1], | |
887 ["paras[0]", "paras[1]", "testDiv", 1, "testDiv", 2], | |
888 ["foreignDoc", "detachedComment", "foreignDoc", "foreignDoc.childNodes.length
- 1", "foreignDoc", "foreignDoc.childNodes.length"], | |
889 ["foreignDoc", "detachedComment", "foreignDoc", "foreignDoc.childNodes.length
- 1", "foreignDoc", "foreignDoc.childNodes.length - 1"], | |
890 ["foreignDoc", "detachedComment", "foreignDoc", "foreignDoc.childNodes.length"
, "foreignDoc", "foreignDoc.childNodes.length"], | |
891 ["foreignDoc", "detachedComment", "detachedComment", 0, "detachedComment", 5], | |
892 ["paras[0]", "xmlTextNode", "paras[0]", 0, "paras[0]", 0], | |
893 ["paras[0]", "xmlTextNode", "paras[0]", 0, "paras[0]", 1], | |
894 ["paras[0]", "xmlTextNode", "paras[0]", 1, "paras[0]", 1], | |
895 | |
896 // Stuff that throws exceptions | |
897 ["paras[0]", "paras[0]", "paras[0]", 0, "paras[0]", 1], | |
898 ["paras[0]", "testDiv", "paras[0]", 0, "paras[0]", 1], | |
899 ["paras[0]", "document", "paras[0]", 0, "paras[0]", 1], | |
900 ["paras[0]", "foreignDoc", "paras[0]", 0, "paras[0]", 1], | |
901 ["paras[0]", "document.doctype", "paras[0]", 0, "paras[0]", 1], | |
902 ]; | |
903 | |
904 | |
905 function testRemoveChild(affectedNode, startContainer, startOffset, endContainer
, endOffset) { | |
906 var expectedStart = [startContainer, startOffset]; | |
907 var expectedEnd = [endContainer, endOffset]; | |
908 | |
909 expectedStart = modifyForRemove(affectedNode, expectedStart); | |
910 expectedEnd = modifyForRemove(affectedNode, expectedEnd); | |
911 | |
912 // We don't test cases where the parent is wrong, so this should never | |
913 // throw an exception. | |
914 affectedNode.parentNode.removeChild(affectedNode); | |
915 | |
916 return expectedStart.concat(expectedEnd); | |
917 } | |
918 | |
919 var removeChildTests = [ | |
920 ["paras[0]", "paras[0]", 0, "paras[0]", 0], | |
921 ["paras[0]", "paras[0]", 0, "paras[0]", 1], | |
922 ["paras[0]", "paras[0]", 1, "paras[0]", 1], | |
923 ["paras[0]", "testDiv", 0, "testDiv", 0], | |
924 ["paras[0]", "testDiv", 0, "testDiv", 1], | |
925 ["paras[0]", "testDiv", 1, "testDiv", 1], | |
926 ["paras[0]", "testDiv", 0, "testDiv", 2], | |
927 ["paras[0]", "testDiv", 1, "testDiv", 2], | |
928 ["paras[0]", "testDiv", 2, "testDiv", 2], | |
929 | |
930 ["foreignDoc.documentElement", "foreignDoc", 0, "foreignDoc", "foreignDoc.chil
dNodes.length"], | |
931 ]; | |
932 | |
933 | |
934 // Finally run everything. All grouped together at the end so that I can | |
935 // easily comment out some of them, so I don't have to wait for all test types | |
936 // to debug only some of them. | |
937 doTests(splitTextTests, function(params) { return params[0] + ".splitText(" + pa
rams[1] + ")" }, testSplitText); | |
938 doTests(insertDataTests, function(params) { return params[0] + ".insertData(" +
params[1] + ", " + params[2] + ")" }, testInsertData); | |
939 doTests(appendDataTests, function(params) { return params[0] + ".appendData(" +
params[1] + ")" }, testAppendData); | |
940 doTests(deleteDataTests, function(params) { return params[0] + ".deleteData(" +
params[1] + ", " + params[2] + ")" }, testDeleteData); | |
941 doTests(replaceDataTests, function(params) { return params[0] + ".replaceData("
+ params[1] + ", " + params[2] + ", " + params[3] + ")" }, testReplaceData); | |
942 doTests(dataChangeTests, function(params) { return params[0] + "." + eval(params
[1]) + " " + eval(params[2]) + ' ' + params[3] }, testDataChange); | |
943 doTests(insertBeforeTests, function(params) { return params[0] + ".insertBefore(
" + params[1] + ", " + params[2] + ")" }, testInsertBefore); | |
944 doTests(replaceChildTests, function(params) { return params[0] + ".replaceChild(
" + params[1] + ", " + params[2] + ")" }, testReplaceChild); | |
945 doTests(appendChildTests, function(params) { return params[0] + ".appendChild("
+ params[1] + ")" }, testAppendChild); | |
946 doTests(removeChildTests, function(params) { return params[0] + ".parentNode.rem
oveChild(" + params[0] + ")" }, testRemoveChild); | |
947 | |
948 | |
949 testDiv.style.display = "none"; | |
950 </script> | |
OLD | NEW |