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 |