Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(110)

Side by Side Diff: third_party/WebKit/LayoutTests/editing/spelling/spellcheck_test.js

Issue 2435273002: Introduce spellcheck_test (Closed)
Patch Set: Address comments and disallow simultaneous running Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « third_party/WebKit/LayoutTests/editing/spelling/spellcheck_test.html ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 'use strict';
6
7 // This file provides
8 // |spellcheck_test(sample, tester, expectedMarkers, opt_title)| asynchronous
9 // test to W3C test harness for easier writing of editing test cases.
10 //
11 // |sample| is an HTML fragment text which is inserted as |innerHTML|. It should
12 // have at least one focus boundary point marker "|" and at most one anchor
13 // boundary point marker "^" indicating the initial selection.
14 //
15 // |tester| is either name with parameter of execCommand or function taking
16 // one parameter |Document|.
17 //
18 // |expectedMarkers| is either a |Marker| or a |Marker| array, where each
19 // |Marker| is an |Object| with the following properties:
20 // - |location| and |length| are integers indicating the range of the marked
21 // text. It must hold that |location >= 0| and |length > 0|.
22 // - |type| is an optional string indicating the marker type. When present, it
23 // must be equal to either "spelling" or "grammer". When absent, it is
24 // regarded as "spelling".
25 // - |description| is an optional string indicating the description of a marker.
26 //
27 // |opt_title| is an optional string giving the title of the test case.
28 //
29 // Examples:
30 //
31 // spellcheck_test(
32 // '<div contentEditable>|</div>',
33 // 'insertText wellcome.',
34 // spellingMarker(0, 8, 'welcome'), // 'wellcome'
35 // 'Mark misspellings and give replacement suggestions after typing.');
36 //
37 // spellcheck_test(
38 // '<div contentEditable>|</div>',
39 // 'insertText You has the right.',
40 // grammarMarker(4, 3), // 'has'
41 // 'Mark ungrammatical phrases after typing.');
42
43 (function() {
44 const Sample = window.Sample;
45
46 class Marker {
47 /**
48 * @public
49 * @param {number} location
50 * @param {number} length
51 * @param {string=} opt_type
52 * @param {string=} opt_description
53 */
54 constructor(location, length, opt_type, opt_description) {
55 /** @type {number} */
56 this.location_ = location;
57 /** @type {number} */
58 this.length_ = length;
59 /** @type {string} */
60 this.type_ = opt_type || 'spelling';
61 /** @type {string|null} */
yosin_UTC9 2016/10/24 06:58:32 s/string|null/?string/
Xiaocheng 2016/10/24 07:18:56 Done.
62 this.description_ = opt_description ? opt_description : null;
yosin_UTC9 2016/10/24 06:58:32 How about using empty string as placeholder? If so
Xiaocheng 2016/10/24 07:18:56 Then we need empty string check instead of null ch
63 }
64
65 /** @return {number} */
66 get location() { return this.location_; }
67
68 /** @return {number} */
69 get length() { return this.length_; }
70
71 /** @return {string} */
72 get type() { return this.type_; }
73
74 /** @return {string|null} */
yosin_UTC9 2016/10/24 06:58:32 s/string|null/?string/
Xiaocheng 2016/10/24 07:18:56 Done.
75 get description() { return this.description_; }
76
77 /**
78 * @public
79 */
80 assertValid() {
81 // TODO(xiaochengh): Add proper assert descriptions when needed.
82 assert_true(Number.isInteger(this.location_));
83 assert_greater_than_equal(this.location_, 0);
84 assert_true(Number.isInteger(this.length_));
85 assert_greater_than(this.length_, 0);
86 assert_true(this.type_ === 'spelling' || this.type_ === 'grammar');
yosin_UTC9 2016/10/24 06:58:32 Let's have const kSpelling = 'spelling' and const
Xiaocheng 2016/10/24 07:18:56 Done.
87 if (this.description_ === null)
88 return;
89 assert_true(typeof this.description_ === 'string');
90 }
91
92 /**
93 * @public
94 * @param {!Marker} expected
95 */
96 assertMatch(expected) {
97 assert_equals(this.location, expected.location,
98 'Marker locations mismatch.');
99 assert_equals(this.length, expected.length, 'Marker lengths mismatch.');
100 assert_equals(this.type, expected.type, 'Marker types mismatch.');
101 if (expected.description === null)
102 return;
103 assert_equals(this.description, expected.description,
104 'Marker descriptions mismatch');
105 }
106 }
107
108 /**
109 * @param {number} location
110 * @param {number} length
111 * @param {string=} opt_description
112 * @return {!Marker}
113 */
114 function spellingMarker(location, length, opt_description) {
115 return new Marker(location, length, 'spelling', opt_description);
116 }
117
118 /**
119 * @param {number} location
120 * @param {number} length
121 * @param {string=} opt_description
122 * @return {!Marker}
123 */
124 function grammarMarker(location, length, opt_description) {
125 return new Marker(location, length, 'grammar', opt_description);
126 }
127
128 /**
129 * @param {!Array<!Marker>} expectedMarkers
130 */
131 function checkExpectedMarkers(expectedMarkers) {
132 expectedMarkers.forEach(marker => marker.assertValid());
133 expectedMarkers.sort((a, b) => a.location - b.location);
134 expectedMarkers.reduce((lastMarker, currentMarker) => {
135 assert_less_than(
136 lastMarker.location + lastMarker.length, currentMarker.location,
137 'Marker ranges should be disjoint.');
138 return currentMarker;
139 });
140 }
141
142 /**
143 * @param {!Node} node
144 * @param {string} type
145 * @param {!Array<!Marker>} markers
146 */
147 function extractMarkersOfType(node, type, markers) {
148 /** @type {!HTMLBodyElement} */
149 const body = node.ownerDocument.body;
150 /** @type {number} */
151 const markerCount = window.internals.markerCountForNode(node, type);
152 for (var i = 0; i < markerCount; ++i) {
153 /** @type {!Range} */
154 const markerRange = window.internals.markerRangeForNode(node, type, i);
155 /** @type {string} */
156 const description = window.internals.markerDescriptionForNode(node, type, i) ;
157 /** @type {number} */
158 const location = window.internals.locationFromRange(body, markerRange);
159 /** @type {number} */
160 const length = window.internals.lengthFromRange(body, markerRange);
161
162 markers.push(new Marker(location, length, type, description));
163 }
164 }
165
166 /**
167 * @param {!Node} node
168 * @param {!Array<!Marker>} markers
169 */
170 function extractAllMarkersRecursivelyTo(node, markers) {
171 extractMarkersOfType(node, 'spelling', markers);
172 extractMarkersOfType(node, 'grammar', markers);
173 node.childNodes.forEach(
174 child => extractAllMarkersRecursivelyTo(child, markers));
175 }
176
177 /**
178 * @param {!Document} doc
179 * @return {!Array<!Marker>}
180 */
181 function extractAllMarkers(doc) {
182 /** @type {!Array<!Marker>} */
183 const markers = [];
184 extractAllMarkersRecursivelyTo(doc.body, markers);
185 markers.sort((a, b) => a.location - b.location);
yosin_UTC9 2016/10/24 06:58:32 Please avoid using one letter variable name.
Xiaocheng 2016/10/24 07:18:56 The comparison function is isolated out as |functi
186 return markers;
187 }
188
189 /**
190 * @param {!Test} testObject
191 * @param {!Sample} sample,
192 * @param {!Array<!Marker>} expectedMarkers
193 * @param {number} remainingRetry
194 * @param {number} retryInterval
195 */
196 function verifyMarkers(
197 testObject, sample, expectedMarkers, remainingRetry, retryInterval) {
198 assert_not_equals(
199 window.internals, undefined,
200 'window.internals is required for running automated spellcheck tests.');
201
202 /** @type {!Array<!Marker>} */
203 const actualMarkers = extractAllMarkers(sample.document);
204 try {
205 assert_equals(actualMarkers.length, expectedMarkers.length,
206 'Number of markers mismatch.');
207 actualMarkers.forEach(
208 (marker, index) => marker.assertMatch(expectedMarkers[index]));
209 testObject.done();
210 sample.remove();
211 } catch (e) {
yosin_UTC9 2016/10/24 06:58:32 Please avoid using one letter variable name.
Xiaocheng 2016/10/24 07:18:56 Done.
212 if (remainingRetry > 0) {
213 // Force invoking idle time spellchecker if it was not run yet.
214 if (window.testRunner)
215 window.testRunner.runIdleTasks(() => {});
216
217 // TODO(xiaochengh): We should make SpellCheckRequester::didCheck trigger
218 // something in JavaScript (e.g., a |Promise|), so that we can actively
219 // know the completion of spellchecking instead of passively waiting for
220 // markers to appear or disappear.
221 testObject.step_timeout(
222 () => verifyMarkers(testObject, sample, expectedMarkers,
223 remainingRetry - 1, retryInterval),
224 retryInterval);
225 } else {
226 throw e;
yosin_UTC9 2016/10/24 06:58:32 We prefer early-return style. e.g. if (remainingR
Xiaocheng 2016/10/24 07:18:56 Done.
227 }
228 }
229 }
230
231 // Spellchecker gets triggered not only by text and selection change, but also
232 // by focus change. For example, misspelling markers in <INPUT> disappear when
233 // the window loses focus, even though the selection does not change.
234 // Therefore, we disallow spellcheck tests from running simultaneously to
235 // prevent interference among them. If we call spellcheck_test while another
236 // test is running, the new test will be added into testQueue waiting for the
237 // completion of the previous test.
238
239 /** @type {boolean} */
240 var spellcheckTestRunning = false;
241 /** @type {!Array<!Object>} */
242 const testQueue = [];
243
244 /**
245 * @param {string} inputText
246 * @param {function(!Document)|string} tester
247 * @param {!Marker|!Array<!Marker>} expectedMarkers
248 * @param {string=} opt_title
249 */
250 function invokeSpellcheckTest(inputText, tester, expectedMarkers, opt_title) {
251 spellcheckTestRunning = true;
252
253 /** @type {!Test} */
254 const testObject = async_test(opt_title, {isSpellcheckTest: true});
255
256 if (!(expectedMarkers instanceof Array))
257 expectedMarkers = [expectedMarkers]
258 testObject.step(() => checkExpectedMarkers(expectedMarkers));
259
260 if (window.testRunner)
261 window.testRunner.setMockSpellCheckerEnabled(true);
262
263 /** @type {!Sample} */
264 const sample = new Sample(inputText);
265 if (typeof(tester) === 'function') {
266 tester.call(window, sample.document);
267 } else if (typeof(tester) === 'string') {
268 const strings = tester.split(/ (.+)/);
269 sample.document.execCommand(strings[0], false, strings[1]);
270 } else {
271 testObject.step(() => assert_unreached(`Invalid tester: ${tester}`));
272 }
273
274 /** @type {number} */
275 const maxRetry = 10;
yosin_UTC9 2016/10/24 06:58:32 s/maxRetry/kMaxRetry/
Xiaocheng 2016/10/24 07:18:56 Done.
276 /** @type {number} */
277 const retryInterval = 50;
yosin_UTC9 2016/10/24 06:58:32 s/retryInterval/kRetryInterval/
Xiaocheng 2016/10/24 07:18:56 Done.
278
279 // TODO(xiaochengh): We should make SpellCheckRequester::didCheck trigger
280 // something in JavaScript (e.g., a |Promise|), so that we can actively know
281 // the completion of spellchecking instead of passively waiting for markers to
282 // appear or disappear.
283 testObject.step_timeout(
284 () => verifyMarkers(testObject, sample, expectedMarkers,
285 maxRetry, retryInterval),
286 retryInterval);
287 }
288
289 add_result_callback(testObj => {
290 if (testObj.properties.isSpellcheckTest !== true)
yosin_UTC9 2016/10/24 06:58:32 Can we write |!testObj.properties.isSpellCheckTest
Xiaocheng 2016/10/24 07:18:56 Done.
291 return;
292 spellcheckTestRunning = false;
293 /** @type {Object} */
294 const args = testQueue.shift();
295 if (args === undefined)
296 return;
297 invokeSpellcheckTest(args.inputText, args.tester,
298 args.expectedMarkers, args.opt_title);
299 });
300
301 /**
302 * @param {string} inputText
303 * @param {function(!Document)|string} tester
304 * @param {!Marker|!Array<!Marker>} expectedMarkers
305 * @param {string=} opt_title
306 */
307 function spellcheckTest(inputText, tester, expectedMarkers, opt_title) {
308 if (spellcheckTestRunning) {
309 testQueue.push({
310 inputText: inputText, tester: tester,
311 expectedMarkers: expectedMarkers, opt_title: opt_title});
312 return;
313 }
314
315 invokeSpellcheckTest(inputText, tester, expectedMarkers, opt_title);
316 }
317
318 // Export symbols
319 window.Marker = Marker;
320 window.spellingMarker = spellingMarker;
321 window.grammarMarker = grammarMarker;
322 window.spellcheck_test = spellcheckTest;
323 })();
OLDNEW
« no previous file with comments | « third_party/WebKit/LayoutTests/editing/spelling/spellcheck_test.html ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698