Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 'use strict'; | 5 'use strict'; |
| 6 | 6 |
| 7 // This file provides |assert_selection(sample, tester, expectedText)| assertion | 7 // This file provides |assert_selection(sample, tester, expectedText)| assertion |
| 8 // to W3C test harness to write editing test cases easier. | 8 // to W3C test harness to write editing test cases easier. |
| 9 // | 9 // |
| 10 // |sample| is an HTML fragment text which is inserted as |innerHTML|. It should | 10 // |sample| is an HTML fragment text which is inserted as |innerHTML|. It should |
| (...skipping 328 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 339 // import "imported/wpt/html/resources/common.js", since |HTML5_VOID_ELEMENTS| | 339 // import "imported/wpt/html/resources/common.js", since |HTML5_VOID_ELEMENTS| |
| 340 // is defined in there. | 340 // is defined in there. |
| 341 /** | 341 /** |
| 342 * @const @type {!Set<string>} | 342 * @const @type {!Set<string>} |
| 343 * only void (without end tag) HTML5 elements | 343 * only void (without end tag) HTML5 elements |
| 344 */ | 344 */ |
| 345 const HTML5_VOID_ELEMENTS = new Set([ | 345 const HTML5_VOID_ELEMENTS = new Set([ |
| 346 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', | 346 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', |
| 347 'keygen', 'link', 'meta', 'param', 'source','track', 'wbr' ]); | 347 'keygen', 'link', 'meta', 'param', 'source','track', 'wbr' ]); |
| 348 | 348 |
| 349 class Serializer { | 349 class Serializer { |
|
yoichio
2016/05/30 02:08:36
Add comment describing simple example of
sample =
| |
| 350 /** | 350 /** |
| 351 * @public | 351 * @public |
| 352 * @param {!SampleSelection} selection | 352 * @param {!SampleSelection} selection |
| 353 */ | 353 */ |
| 354 constructor(selection) { | 354 constructor(selection) { |
| 355 /** @type {!SampleSelection} */ | 355 /** @type {!SampleSelection} */ |
| 356 this.selection_ = selection; | 356 this.selection_ = selection; |
| 357 /** @type {!Array<strings>} */ | 357 /** @type {!Array<strings>} */ |
| 358 this.strings_ = []; | 358 this.strings_ = []; |
| 359 } | 359 } |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 412 } | 412 } |
| 413 this.emit(text); | 413 this.emit(text); |
| 414 } | 414 } |
| 415 | 415 |
| 416 /** | 416 /** |
| 417 * @private | 417 * @private |
| 418 * @param {!HTMLElement} element | 418 * @param {!HTMLElement} element |
| 419 * @param {number} nodeIndex | 419 * @param {number} nodeIndex |
| 420 */ | 420 */ |
| 421 handleElementNode(element, nodeIndex) { | 421 handleElementNode(element, nodeIndex) { |
| 422 if (element.parenNode === this.selection_.focusNode && | 422 if (element.parentNode === this.selection_.focusNode && |
|
yoichio
2016/05/30 02:08:36
The strict mode can't check this? Sad,,,
| |
| 423 nodeIndex === this.selection_.focusOffset) { | 423 nodeIndex === this.selection_.focusOffset) { |
| 424 this.emit('|'); | 424 this.emit('|'); |
| 425 } else if ( | 425 } else if (element.parentNode === this.selection_.anchorNode && |
| 426 element === this.selection_.anchorNode && | 426 nodeIndex === this.selection_.anchorOffset) { |
| 427 nodeIndex === this.selection_.anchorOffset) { | |
| 428 this.emit('^'); | 427 this.emit('^'); |
| 429 } | 428 } |
| 430 /** @type {string} */ | 429 /** @type {string} */ |
| 431 const tagName = element.tagName.toLowerCase(); | 430 const tagName = element.tagName.toLowerCase(); |
| 432 this.emit(`<${tagName}`); | 431 this.emit(`<${tagName}`); |
| 433 Array.from(element.attributes) | 432 Array.from(element.attributes) |
| 434 .sort((attr1, attr2) => attr1.name.localeCompare(attr2.name)) | 433 .sort((attr1, attr2) => attr1.name.localeCompare(attr2.name)) |
| 435 .forEach(attr => { | 434 .forEach(attr => { |
| 436 if (attr.value === '') | 435 if (attr.value === '') |
| 437 return this.emit(` ${attr.name}`); | 436 return this.emit(` ${attr.name}`); |
| 438 const value = attr.value.replace(/&/g, '&') | 437 const value = attr.value.replace(/&/g, '&') |
| 439 .replace(/\u0022/g, '"') | 438 .replace(/\u0022/g, '"') |
| 440 .replace(/\u0027/g, '''); | 439 .replace(/\u0027/g, '''); |
| 441 this.emit(` ${attr.name}="${value}"`); | 440 this.emit(` ${attr.name}="${value}"`); |
| 442 }); | 441 }); |
| 443 this.emit('>'); | 442 this.emit('>'); |
| 444 if (element.childNodes.length === 0 && | 443 if (element.childNodes.length === 0 && |
| 445 HTML5_VOID_ELEMENTS.has(tagName)) { | 444 HTML5_VOID_ELEMENTS.has(tagName)) { |
| 446 return; | 445 return; |
| 447 } | 446 } |
| 448 /** @type {number} */ | 447 /** @type {number} */ |
| 449 let childIndex = 0; | 448 let childIndex = 0; |
| 450 for (const child of Array.from(element.childNodes)) { | 449 for (const child of Array.from(element.childNodes)) { |
| 451 this.serializeInternal(child, childIndex); | 450 this.serializeInternal(child, childIndex); |
| 452 ++childIndex; | 451 ++childIndex; |
| 453 } | 452 } |
| 453 if (element === this.selection_.focusNode && | |
| 454 childIndex === this.selection_.focusOffset) { | |
| 455 this.emit('|'); | |
| 456 } else if (element === this.selection_.anchorNode && | |
| 457 childIndex === this.selection_.anchorOffset) { | |
| 458 this.emit('^'); | |
| 459 } | |
| 454 this.emit(`</${tagName}>`); | 460 this.emit(`</${tagName}>`); |
| 455 } | 461 } |
| 456 | 462 |
| 457 /** | 463 /** |
| 458 * @public | 464 * @public |
| 459 * @param {!HTMLElement} element | 465 * @param {!HTMLElement} element |
| 460 */ | 466 */ |
| 461 serialize(element) { | 467 serialize(element) { |
| 462 if (this.selection_.isNone) | 468 if (this.selection_.isNone) |
| 463 return node.outerHTML; | 469 return node.outerHTML; |
| 464 this.serializeInternal(element, 0); | 470 this.serializeInternal(element, 0); |
| 465 return this.strings_.join(''); | 471 return this.strings_.join(''); |
| 466 } | 472 } |
| 467 | 473 |
| 468 /** | 474 /** |
| 469 * @private | 475 * @private |
| 470 * @param {!Node} node | 476 * @param {!Node} node |
| 471 * @param {number} nodeIndex | 477 * @param {number} nodeIndex |
| 472 */ | 478 */ |
| 473 serializeInternal(node, nodeIndex) { | 479 serializeInternal(node, nodeIndex) { |
| 474 if (isElement(node)) | 480 if (isElement(node)) |
| 475 return this.handleElementNode(node, nodeIndex); | 481 return this.handleElementNode(node, nodeIndex); |
| 476 if (isCharacterData(node)) | 482 if (isCharacterData(node)) |
| 477 return this.handleCharacterData(node); | 483 return this.handleCharacterData(node); |
| 478 throw new Error(`Unexpected node ${node}`); | 484 throw new Error(`Unexpected node ${node}`); |
| 479 } | 485 } |
| 480 } | 486 } |
| 481 | 487 |
| 482 class Sample { | 488 class Sample { |
|
yoichio
2016/05/30 02:08:36
Add comment describing simple example of
sample =
| |
| 483 /** | 489 /** |
| 484 * @public | 490 * @public |
| 485 * @param {string} sampleText | 491 * @param {string} sampleText |
| 486 */ | 492 */ |
| 487 constructor(sampleText) { | 493 constructor(sampleText) { |
| 488 /** @const @type {!HTMLIFame} */ | 494 /** @const @type {!HTMLIFame} */ |
| 489 this.iframe_ = document.createElement('iframe'); | 495 this.iframe_ = document.createElement('iframe'); |
| 490 document.body.appendChild(this.iframe_); | 496 document.body.appendChild(this.iframe_); |
| 491 /** @const @type {!HTMLDocument} */ | 497 /** @const @type {!HTMLDocument} */ |
| 492 this.document_ = this.iframe_.contentDocument; | 498 this.document_ = this.iframe_.contentDocument; |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 563 for (const line of getStack()) { | 569 for (const line of getStack()) { |
| 564 const match = RE_TEST_FUNCTION.exec(line); | 570 const match = RE_TEST_FUNCTION.exec(line); |
| 565 if (!match) | 571 if (!match) |
| 566 continue; | 572 continue; |
| 567 return `${match[1]}(${match[2]})`; | 573 return `${match[1]}(${match[2]})`; |
| 568 } | 574 } |
| 569 return ''; | 575 return ''; |
| 570 } | 576 } |
| 571 | 577 |
| 572 /** | 578 /** |
| 579 * @param {string} expectedText | |
| 580 */ | |
| 581 function checkExpectedText(expectedText) { | |
|
yoichio
2016/05/30 02:08:36
Could you extract sanity check functions from this
| |
| 582 /** @type {number} */ | |
| 583 const anchorOffset = expectedText.indexOf('^'); | |
| 584 /** @type {number} */ | |
| 585 const focusOffset = expectedText.indexOf('|'); | |
| 586 if (anchorOffset != expectedText.lastIndexOf('^')) { | |
| 587 throw new Error( | |
| 588 `You should have at most one anchor marker "^" in "${expectedText}".`); | |
| 589 } | |
| 590 if (focusOffset != expectedText.lastIndexOf('|')) { | |
| 591 throw new Error( | |
| 592 `You should have at most one focus marker "|" in "${expectedText}".`); | |
| 593 } | |
| 594 if (anchorOffset >= 0 && focusOffset < 0) { | |
| 595 throw new Error( | |
| 596 `You should have both anchor marker "^" and focus marker "|" in ${expect edText}`); | |
| 597 } | |
| 598 } | |
| 599 | |
| 600 /** | |
| 573 * @param {string} inputText | 601 * @param {string} inputText |
| 574 * @param {function(!Selection)|string} | 602 * @param {function(!Selection)|string} |
| 575 * @param {string} expectedText | 603 * @param {string} expectedText |
| 576 * @param {string=} opt_description | 604 * @param {string=} opt_description |
| 577 */ | 605 */ |
| 578 function assertSelection( | 606 function assertSelection( |
| 579 inputText, tester, expectedText, opt_description = '') { | 607 inputText, tester, expectedText, opt_description = '') { |
| 580 /** @type {string} */ | 608 /** @type {string} */ |
| 581 const description = | 609 const description = |
| 582 opt_description === '' ? assembleDescription() : opt_description; | 610 opt_description === '' ? assembleDescription() : opt_description; |
| 583 if (expectedText.indexOf('^') != expectedText.lastIndexOf('^')) { | 611 checkExpectedText(expectedText); |
| 584 throw new Error( | |
| 585 `You should have at most one anchor marker "^" in "${expectedText}".`) ; | |
| 586 } | |
| 587 if (expectedText.indexOf('|') != expectedText.lastIndexOf('|')) { | |
| 588 throw new Error( | |
| 589 `You should have at most one focus marker "|" in "${expectedText}".`); | |
| 590 } | |
| 591 const sample = new Sample(inputText); | 612 const sample = new Sample(inputText); |
| 592 if (typeof(tester) === 'function') { | 613 if (typeof(tester) === 'function') { |
| 593 tester.call(window, sample.selection); | 614 tester.call(window, sample.selection); |
| 594 } else if (typeof(tester) === 'string') { | 615 } else if (typeof(tester) === 'string') { |
| 595 const strings = tester.split(' '); | 616 const strings = tester.split(' '); |
| 596 sample.document.execCommand(strings[0], false, strings[1]); | 617 sample.document.execCommand(strings[0], false, strings[1]); |
| 597 } else { | 618 } else { |
| 598 throw new Error(`Invalid tester: ${tester}`); | 619 throw new Error(`Invalid tester: ${tester}`); |
| 599 } | 620 } |
| 600 /** @type {string} */ | 621 /** @type {string} */ |
| 601 const actualText = sample.serialize(); | 622 const actualText = sample.serialize(); |
| 602 // We keep sample HTML when assertion is false for ease of debugging test | 623 // We keep sample HTML when assertion is false for ease of debugging test |
| 603 // case. | 624 // case. |
| 604 if (actualText == expectedText) | 625 if (actualText == expectedText) |
| 605 sample.remove(); | 626 sample.remove(); |
| 606 assert_equals(actualText, expectedText, description); | 627 assert_equals(actualText, expectedText, description); |
| 607 } | 628 } |
| 608 | 629 |
| 609 // Export symbols | 630 // Export symbols |
| 610 window.Sample = Sample; | 631 window.Sample = Sample; |
| 611 window.assert_selection = assertSelection; | 632 window.assert_selection = assertSelection; |
| 612 })(); | 633 })(); |
| OLD | NEW |