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