Chromium Code Reviews| Index: third_party/WebKit/LayoutTests/editing/spelling/spellcheck_test.js |
| diff --git a/third_party/WebKit/LayoutTests/editing/spelling/spellcheck_test.js b/third_party/WebKit/LayoutTests/editing/spelling/spellcheck_test.js |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..f65672c4e476d3e49c6921e0cb1a8562473bd354 |
| --- /dev/null |
| +++ b/third_party/WebKit/LayoutTests/editing/spelling/spellcheck_test.js |
| @@ -0,0 +1,292 @@ |
| +// Copyright 2016 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +'use strict'; |
| + |
| +// This file provides |
| +// |spellcheck_test(sample, tester, expectedMarkers, opt_title)| asynchronous |
| +// test to W3C test harness for easier writing of editing test cases. |
| +// |
| +// |sample| is an HTML fragment text which is inserted as |innerHTML|. It should |
| +// have at least one focus boundary point marker "|" and at most one anchor |
| +// boundary point marker "^" indicating the initial selection. |
| +// |
| +// |tester| is either name with parameter of execCommand or function taking |
| +// one parameter |Document|. |
| +// |
| +// |expectedMarkers| is either a |Marker| or a |Marker| array, where each |
| +// |Marker| is an |Object| with the following properties: |
| +// - |location| and |length| are integers indicating the range of the marked |
| +// text. It must hold that |location >= 0| and |length > 0|. |
| +// - |type| is an optional string indicating the marker type. When present, it |
| +// must be equal to either "spelling" or "grammer". When absent, it is |
| +// regarded as "spelling". |
| +// - |description| is an optional string indicating the description of a marker. |
| +// |
| +// |opt_title| is an optional string giving the title of the test case. |
| +// |
| +// Examples: |
| +// |
| +// spellcheck_test( |
| +// '<div contentEditable>|</div>', |
| +// 'insertText wellcome.', |
| +// { |
|
yosin_UTC9
2016/10/24 06:17:00
Let's use |spellingMarker()| and |grammarMarker()|
Xiaocheng
2016/10/24 06:37:57
Whoops. Forgot to change this part.
|
| +// location: 0, |
| +// length: 8, |
| +// type: 'spelling', |
| +// description: 'welcome' |
| +// }, |
| +// 'Mark misspellings and give replacement suggestions after typing.'); |
| + |
| +// spellcheck_test( |
| +// '<div contentEditable>|</div>', |
| +// 'insertText You has the right.', |
| +// { |
| +// location: 4, |
| +// length: 3, |
| +// type: 'grammar' |
| +// }, |
| +// 'Mark ungrammatical phrases after typing.'); |
| +// |
| + |
| +(function() { |
| +const Sample = window.Sample; |
| + |
| +class Marker { |
| + /** |
| + * @public |
| + * @param {number} location |
| + * @param {number} length |
| + * @param {string=} opt_type |
| + * @param {string=} opt_description |
| + */ |
| + constructor(location, length, opt_type, opt_description) { |
| + /** @type {number} */ |
| + this.location_ = location; |
| + /** @type {number} */ |
| + this.length_ = length; |
| + /** @type {string} */ |
| + this.type_ = opt_type || 'spelling'; |
| + /** @type {string|null} */ |
| + this.description_ = opt_description ? opt_description : null; |
| + } |
| + |
| + /** @return {number} */ |
| + get location() { return this.location_; } |
| + |
| + /** @return {number} */ |
| + get length() { return this.length_; } |
| + |
| + /** @return {string} */ |
| + get type() { return this.type_; } |
| + |
| + /** @return {string|null} */ |
| + get description() { return this.description_; } |
| + |
| + /** |
| + * @public |
| + */ |
| + assertValid() { |
| + // TODO(xiaochengh): Add proper assert descriptions when needed. |
| + assert_true(Number.isInteger(this.location_)); |
| + assert_greater_than_equal(this.location_, 0); |
| + assert_true(Number.isInteger(this.length_)); |
| + assert_greater_than(this.length_, 0); |
| + assert_true(this.type_ === 'spelling' || this.type_ === 'grammar'); |
| + if (this.description_ === null) |
| + return; |
| + assert_true(typeof this.description_ === 'string'); |
| + } |
| + |
| + /** |
| + * @public |
| + * @param {!Marker} expected |
| + */ |
| + assertMatch(expected) { |
| + assert_equals(this.location, expected.location, |
| + 'Marker locations mismatch.'); |
| + assert_equals(this.length, expected.length, 'Marker lengths mismatch.'); |
| + assert_equals(this.type, expected.type, 'Marker types mismatch.'); |
| + if (expected.description === null) |
| + return; |
| + assert_equals(this.description, expected.description, |
| + 'Marker descriptions mismatch'); |
| + } |
| +} |
| + |
| +/** |
| + * @param {number} location |
| + * @param {number} length |
| + * @param {string=} opt_description |
| + * @return {!Marker} |
| + */ |
| +function spellingMarker(location, length, opt_description) { |
| + return new Marker(location, length, 'spelling', opt_description); |
| +} |
| + |
| +/** |
| + * @param {number} location |
| + * @param {number} length |
| + * @param {string=} opt_description |
| + * @return {!Marker} |
| + */ |
| +function grammarMarker(location, length, opt_description) { |
| + return new Marker(location, length, 'grammar', opt_description); |
| +} |
| + |
| +/** |
| + * @param {!Array<Marker>} expectedMarkers |
| + */ |
| +function checkExpectedMarkers(expectedMarkers) { |
| + expectedMarkers.forEach(marker => marker.assertValid()); |
| + expectedMarkers.sort((a, b) => a.location - b.location); |
| + expectedMarkers.forEach((currentMarker, index) => { |
|
yosin_UTC9
2016/10/24 06:17:00
How about Array#reduce()
expectedMarkers.reduce((
Xiaocheng
2016/10/24 06:37:57
Didn't know that. Done.
|
| + if (index == 0) |
| + return; |
| + /** @type {!Marker} */ |
| + const lastMarker = expectedMarkers[index - 1]; |
| + assert_less_than( |
| + lastMarker.location + lastMarker.length, currentMarker.location, |
| + 'Marker ranges should be disjoint.'); |
| + }); |
| +} |
| + |
| +/** |
| + * @param {!Node} node |
| + * @param {string} type |
| + * @param {!Array<Marker>} markers |
|
yosin_UTC9
2016/10/24 06:17:00
s/!Array<Marker>/!Array<!Marker>/
Xiaocheng
2016/10/24 06:37:57
Done.
|
| + */ |
| +function extractMarkersOfType(node, type, markers) { |
| + /** @type {!HTMLBodyElement} */ |
| + const body = node.ownerDocument.body; |
| + /** @type {number} */ |
| + const markerCount = window.internals.markerCountForNode(node, type); |
| + for (var i = 0; i < markerCount; ++i) { |
| + /** @type {!Range} */ |
| + const markerRange = window.internals.markerRangeForNode(node, type, i); |
| + /** @type {string} */ |
| + const description = window.internals.markerDescriptionForNode(node, type, i); |
| + /** @type {number} */ |
| + const location = window.internals.locationFromRange(body, markerRange); |
| + /** @type {number} */ |
| + const length = window.internals.lengthFromRange(body, markerRange); |
| + |
| + markers.push(new Marker(location, length, type, description)); |
| + } |
| +} |
| + |
| +/** |
| + * @param {!Node} node |
| + * @param {!Array<Marker>} markers |
| + */ |
| +function extractAllMarkersRecursivelyTo(node, markers) { |
| + extractMarkersOfType(node, 'spelling', markers); |
| + extractMarkersOfType(node, 'grammar', markers); |
| + node.childNodes.forEach( |
| + child => extractAllMarkersRecursivelyTo(child, markers)); |
| +} |
| + |
| +/** |
| + * @param {!Document} doc |
| + * @return {!Array<Marker>} |
| + */ |
| +function extractAllMarkers(doc) { |
| + /** @type {!Array<Marker>} */ |
| + const markers = []; |
| + extractAllMarkersRecursivelyTo(doc.body, markers); |
| + markers.sort((a, b) => a.location - b.location); |
| + return markers; |
| +} |
| + |
| +/** |
| + * @param {!Test} testObject |
| + * @param {!Sample} sample, |
| + * @param {!Array<Marker>} expectedMarkers |
| + * @param {number} remainingRetry |
| + * @param {number} retryInterval |
| + */ |
| +function verifyMarkers( |
| + testObject, sample, expectedMarkers, remainingRetry, retryInterval) { |
| + assert_not_equals( |
| + window.internals, undefined, |
| + 'window.internals is required for running automated spellcheck tests.'); |
| + |
| + /** @type {!Array<Marker>} */ |
| + const actualMarkers = extractAllMarkers(sample.document); |
| + try { |
| + assert_equals(actualMarkers.length, expectedMarkers.length, |
| + 'Number of markers mismatch.'); |
| + actualMarkers.forEach( |
| + (marker, index) => marker.assertMatch(expectedMarkers[index])); |
| + testObject.done(); |
| + sample.remove(); |
| + } catch (e) { |
| + if (remainingRetry > 0) { |
| + // Force invoking idle time spellchecker if it was not run yet. |
| + if (window.testRunner) |
| + window.testRunner.runIdleTasks(() => {}); |
| + |
| + // TODO(xiaochengh): We should make SpellCheckRequester::didCheck trigger |
| + // something in JavaScript (e.g., a |Promise|), so that we can actively |
| + // know the completion of spellchecking instead of passively waiting for |
| + // markers to appear or disappear. |
| + testObject.step_timeout( |
| + () => verifyMarkers(testObject, sample, expectedMarkers, |
| + remainingRetry - 1, retryInterval), |
| + retryInterval); |
| + } else { |
| + throw e; |
| + } |
| + } |
| +} |
| + |
| +/** |
| + * @param {string} inputText |
| + * @param {function(!Document)|string} tester |
| + * @param {!Marker|!Array<Marker>} expectedMarkers |
| + * @param {string=} opt_title |
| + */ |
| +function spellcheckTest(inputText, tester, expectedMarkers, opt_title) { |
| + /** @type {!Test} */ |
| + const testObject = async_test(opt_title); |
| + |
| + if (!(expectedMarkers instanceof Array)) |
| + expectedMarkers = [expectedMarkers] |
| + testObject.step(() => checkExpectedMarkers(expectedMarkers)); |
| + |
| + if (window.testRunner) |
| + window.testRunner.setMockSpellCheckerEnabled(true); |
| + |
| + /** @type {!Sample} */ |
| + const sample = new Sample(inputText); |
| + if (typeof(tester) === 'function') { |
| + tester.call(window, sample.document); |
| + } else if (typeof(tester) === 'string') { |
| + const strings = tester.split(/ (.+)/); |
| + sample.document.execCommand(strings[0], false, strings[1]); |
| + } else { |
| + testObject.step(() => assert_unreached(`Invalid tester: ${tester}`)); |
| + } |
| + |
| + /** @type {number} */ |
| + const maxRetry = 10; |
| + /** @type {number} */ |
| + const retryInterval = 50; |
| + |
| + // TODO(xiaochengh): We should make SpellCheckRequester::didCheck trigger |
| + // something in JavaScript (e.g., a |Promise|), so that we can actively know |
| + // the completion of spellchecking instead of passively waiting for markers to |
| + // appear or disappear. |
| + testObject.step_timeout( |
| + () => verifyMarkers(testObject, sample, expectedMarkers, |
| + maxRetry, retryInterval), |
| + retryInterval); |
| +} |
| + |
| +// Export symbols |
| +window.Marker = Marker; |
| +window.spellingMarker = spellingMarker; |
| +window.grammarMarker = grammarMarker; |
| +window.spellcheck_test = spellcheckTest; |
| +})(); |