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

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

Issue 2435273002: Introduce spellcheck_test (Closed)
Patch Set: Mon Oct 24 16:37:45 JST 2016 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 /** @type {string} */
47 const kSpelling = 'spelling';
48 /** @type {string} */
49 const kGrammar = 'grammar';
50
51 class Marker {
52 /**
53 * @public
54 * @param {number} location
55 * @param {number} length
56 * @param {string=} opt_type
57 * @param {string=} opt_description
58 */
59 constructor(location, length, opt_type, opt_description) {
60 /** @type {number} */
61 this.location_ = location;
62 /** @type {number} */
63 this.length_ = length;
64 /** @type {string} */
65 this.type_ = opt_type || 'spelling';
66 /** @type {boolean} */
67 this.ignoreDescription_ = opt_description === undefined;
68 /** @type {string} */
69 this.description_ = opt_description || '';
70 }
71
72 /** @return {number} */
73 get location() { return this.location_; }
74
75 /** @return {number} */
76 get length() { return this.length_; }
77
78 /** @return {string} */
79 get type() { return this.type_; }
80
81 /** @return {boolean} */
82 get ignoreDescription() { return this.ignoreDescription_; }
83
84 /** @return {string} */
85 get description() { return this.description_; }
86
87 /**
88 * @public
89 */
90 assertValid() {
91 // TODO(xiaochengh): Add proper assert descriptions when needed.
92 assert_true(Number.isInteger(this.location_));
93 assert_greater_than_equal(this.location_, 0);
94 assert_true(Number.isInteger(this.length_));
95 assert_greater_than(this.length_, 0);
96 assert_true(this.type_ === kSpelling || this.type_ === kGrammar);
97 assert_true(typeof this.description_ === 'string');
98 }
99
100 /**
101 * @public
102 * @param {!Marker} expected
103 */
104 assertMatch(expected) {
105 assert_equals(this.location, expected.location,
106 'Marker locations mismatch.');
107 assert_equals(this.length, expected.length, 'Marker lengths mismatch.');
108 assert_equals(this.type, expected.type, 'Marker types mismatch.');
109 if (expected.ignoreDescription)
110 return;
111 assert_equals(this.description, expected.description,
112 'Marker descriptions mismatch');
113 }
114 }
115
116 /**
117 * @param {number} location
118 * @param {number} length
119 * @param {string=} opt_description
120 * @return {!Marker}
121 */
122 function spellingMarker(location, length, opt_description) {
123 return new Marker(location, length, kSpelling, opt_description);
124 }
125
126 /**
127 * @param {number} location
128 * @param {number} length
129 * @param {string=} opt_description
130 * @return {!Marker}
131 */
132 function grammarMarker(location, length, opt_description) {
133 return new Marker(location, length, kGrammar, opt_description);
134 }
135
136 /**
137 * @param {!Marker} marker1
138 * @param {!Marker} marker2
139 * @return {number}
140 */
141 function markerComparison(marker1, marker2) {
142 return marker1.location - marker2.location;
143 }
144
145 /**
146 * @param {!Array<!Marker>} expectedMarkers
147 */
148 function checkExpectedMarkers(expectedMarkers) {
149 if (!expectedMarkers.length)
yosin_UTC9 2016/10/24 07:54:14 Better to write: |expectedMarkers.length === 0|
Xiaocheng 2016/10/24 08:29:23 Done.
150 return;
151 expectedMarkers.forEach(marker => marker.assertValid());
152 expectedMarkers.sort(markerComparison);
153 expectedMarkers.reduce((lastMarker, currentMarker) => {
154 assert_less_than(
155 lastMarker.location + lastMarker.length, currentMarker.location,
156 'Marker ranges should be disjoint.');
157 return currentMarker;
158 });
159 }
160
161 /**
162 * @param {!Node} node
163 * @param {string} type
164 * @param {!Array<!Marker>} markers
165 */
166 function extractMarkersOfType(node, type, markers) {
167 /** @type {!HTMLBodyElement} */
168 const body = node.ownerDocument.body;
169 /** @type {number} */
170 const markerCount = window.internals.markerCountForNode(node, type);
171 for (var i = 0; i < markerCount; ++i) {
yosin_UTC9 2016/10/24 07:54:14 nit: s/var/let/
Xiaocheng 2016/10/24 08:29:24 Done.
172 /** @type {!Range} */
173 const markerRange = window.internals.markerRangeForNode(node, type, i);
174 /** @type {string} */
175 const description = window.internals.markerDescriptionForNode(node, type, i) ;
176 /** @type {number} */
177 const location = window.internals.locationFromRange(body, markerRange);
178 /** @type {number} */
179 const length = window.internals.lengthFromRange(body, markerRange);
180
181 markers.push(new Marker(location, length, type, description));
182 }
183 }
184
185 /**
186 * @param {!Node} node
187 * @param {!Array<!Marker>} markers
188 */
189 function extractAllMarkersRecursivelyTo(node, markers) {
190 extractMarkersOfType(node, kSpelling, markers);
191 extractMarkersOfType(node, kGrammar, markers);
192 node.childNodes.forEach(
193 child => extractAllMarkersRecursivelyTo(child, markers));
194 }
195
196 /**
197 * @param {!Document} doc
198 * @return {!Array<!Marker>}
199 */
200 function extractAllMarkers(doc) {
201 /** @type {!Array<!Marker>} */
202 const markers = [];
203 extractAllMarkersRecursivelyTo(doc.body, markers);
204 markers.sort(markerComparison);
205 return markers;
206 }
207
208 /**
209 * @param {!Test} testObject
210 * @param {!Sample} sample,
211 * @param {!Array<!Marker>} expectedMarkers
212 * @param {number} remainingRetry
213 * @param {number} retryInterval
214 */
215 function verifyMarkers(
216 testObject, sample, expectedMarkers, remainingRetry, retryInterval) {
217 assert_not_equals(
218 window.internals, undefined,
219 'window.internals is required for running automated spellcheck tests.');
220
221 /** @type {!Array<!Marker>} */
222 const actualMarkers = extractAllMarkers(sample.document);
223 try {
224 assert_equals(actualMarkers.length, expectedMarkers.length,
225 'Number of markers mismatch.');
226 actualMarkers.forEach(
227 (marker, index) => marker.assertMatch(expectedMarkers[index]));
228 testObject.done();
229 sample.remove();
230 } catch (error) {
231 if (remainingRetry <= 0)
232 throw error;
233
234 // Force invoking idle time spellchecker in case it has not been run yet.
235 if (window.testRunner)
236 window.testRunner.runIdleTasks(() => {});
237
238 // TODO(xiaochengh): We should make SpellCheckRequester::didCheck trigger
239 // something in JavaScript (e.g., a |Promise|), so that we can actively
240 // know the completion of spellchecking instead of passively waiting for
241 // markers to appear or disappear.
242 testObject.step_timeout(
243 () => verifyMarkers(testObject, sample, expectedMarkers,
244 remainingRetry - 1, retryInterval),
245 retryInterval);
246 }
247 }
248
249 // Spellchecker gets triggered not only by text and selection change, but also
250 // by focus change. For example, misspelling markers in <INPUT> disappear when
251 // the window loses focus, even though the selection does not change.
252 // Therefore, we disallow spellcheck tests from running simultaneously to
253 // prevent interference among them. If we call spellcheck_test while another
254 // test is running, the new test will be added into testQueue waiting for the
255 // completion of the previous test.
256
257 /** @type {boolean} */
258 var spellcheckTestRunning = false;
259 /** @type {!Array<!Object>} */
260 const testQueue = [];
261
262 /**
263 * @param {string} inputText
264 * @param {function(!Document)|string} tester
265 * @param {!Marker|!Array<!Marker>} expectedMarkers
266 * @param {string=} opt_title
267 */
268 function invokeSpellcheckTest(inputText, tester, expectedMarkers, opt_title) {
269 spellcheckTestRunning = true;
270
271 /** @type {!Test} */
272 const testObject = async_test(opt_title, {isSpellcheckTest: true});
273
274 if (!(expectedMarkers instanceof Array))
275 expectedMarkers = [expectedMarkers]
276 testObject.step(() => checkExpectedMarkers(expectedMarkers));
277
278 if (window.testRunner)
279 window.testRunner.setMockSpellCheckerEnabled(true);
280
281 /** @type {!Sample} */
282 const sample = new Sample(inputText);
283 if (typeof(tester) === 'function') {
yosin_UTC9 2016/10/24 07:54:14 How about moving this if-else-if fragment to |Samp
Xiaocheng 2016/10/24 08:29:23 This part is slightly different from assert_select
284 tester.call(window, sample.document);
285 } else if (typeof(tester) === 'string') {
286 const strings = tester.split(/ (.+)/);
287 sample.document.execCommand(strings[0], false, strings[1]);
288 } else {
289 testObject.step(() => assert_unreached(`Invalid tester: ${tester}`));
290 }
291
292 /** @type {number} */
293 const kMaxRetry = 10;
294 /** @type {number} */
295 const kRetryInterval = 50;
296
297 // TODO(xiaochengh): We should make SpellCheckRequester::didCheck trigger
298 // something in JavaScript (e.g., a |Promise|), so that we can actively know
299 // the completion of spellchecking instead of passively waiting for markers to
300 // appear or disappear.
301 testObject.step_timeout(
302 () => verifyMarkers(testObject, sample, expectedMarkers,
303 kMaxRetry, kRetryInterval),
304 kRetryInterval);
305 }
306
307 add_result_callback(testObj => {
308 if (!testObj.properties.isSpellcheckTest)
309 return;
310 spellcheckTestRunning = false;
311 /** @type {Object} */
312 const args = testQueue.shift();
313 if (args === undefined)
314 return;
315 invokeSpellcheckTest(args.inputText, args.tester,
316 args.expectedMarkers, args.opt_title);
317 });
318
319 /**
320 * @param {string} inputText
321 * @param {function(!Document)|string} tester
322 * @param {!Marker|!Array<!Marker>} expectedMarkers
323 * @param {string=} opt_title
324 */
325 function spellcheckTest(inputText, tester, expectedMarkers, opt_title) {
326 if (spellcheckTestRunning) {
327 testQueue.push({
328 inputText: inputText, tester: tester,
329 expectedMarkers: expectedMarkers, opt_title: opt_title});
330 return;
331 }
332
333 invokeSpellcheckTest(inputText, tester, expectedMarkers, opt_title);
334 }
335
336 // Export symbols
337 window.Marker = Marker;
338 window.spellingMarker = spellingMarker;
339 window.grammarMarker = grammarMarker;
340 window.spellcheck_test = spellcheckTest;
341 })();
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