OLD | NEW |
---|---|
(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 // { | |
35 // location: 0, | |
36 // length: 8, | |
37 // type: 'spelling', | |
38 // description: 'welcome' | |
39 // }, | |
40 // 'Mark misspellings and give replacement suggestions after simple typing.' | |
41 // ); | |
42 | |
43 // spellcheck_test( | |
44 // '<div contentEditable>|</div>', | |
45 // 'insertText You has the right.', | |
46 // { | |
47 // location: 4, | |
48 // length: 3, | |
49 // type: 'grammar' | |
50 // }, | |
51 // 'Mark ungrammatical phrases after simple typing.' | |
52 // ); | |
53 // | |
54 | |
55 (function() { | |
56 const Sample = window.Sample; | |
57 | |
58 class Marker { | |
59 /** | |
60 * @public | |
61 * @param {number} location | |
62 * @param {number} length | |
63 * @param {string=} opt_type | |
64 * @param {string=} opt_description | |
65 */ | |
66 constructor(location, length, opt_type, opt_description) { | |
67 this.location_ = location; | |
68 this.length_ = length; | |
69 this.type_ = opt_type || 'spelling'; | |
70 this.description_ = opt_description; | |
71 } | |
72 | |
73 /** @return {!number} */ | |
74 get location() { return this.location_; } | |
75 | |
76 /** @return {!number} */ | |
77 get length() { return this.length_; } | |
78 | |
79 /** @return {!string} */ | |
80 get type() { return this.type_; } | |
81 | |
82 /** @return {string=} */ | |
83 get description() { return this.description_; } | |
84 | |
85 /** | |
86 * @public | |
87 */ | |
88 assertValid() { | |
89 // TODO(xiaochengh): Add proper assert descriptions when needed. | |
90 assert_true(Number.isInteger(this.location_)); | |
91 assert_greater_than_equal(this.location_, 0); | |
92 assert_true(Number.isInteger(this.length_)); | |
93 assert_greater_than(this.length_, 0); | |
94 assert_true(this.type_ === 'spelling' || this.type_ === 'grammar'); | |
95 if (this.description_ !== undefined) | |
yosin_UTC9
2016/10/24 04:40:08
nit: We prefer early-return style to reduce indent
Xiaocheng
2016/10/24 05:11:37
Done.
| |
96 assert_true(typeof this.description_ === 'string'); | |
97 } | |
98 | |
99 /** | |
100 * @public | |
101 * @param {Marker} expected | |
102 */ | |
103 assertMatch(expected) { | |
104 assert_equals(this.location, expected.location, | |
105 'Marker locations mismatch.'); | |
106 assert_equals(this.length, expected.length, 'Marker lengths mismatch.'); | |
107 assert_equals(this.type, expected.type, 'Marker types mismatch.'); | |
108 if (expected.description !== undefined) { | |
yosin_UTC9
2016/10/24 04:40:08
nit: We prefer early-return style.
Xiaocheng
2016/10/24 05:11:37
Done.
| |
109 assert_equals(this.description, expected.description, | |
110 'Marker descriptions mismatch'); | |
111 } | |
112 } | |
113 } | |
114 | |
115 /** | |
116 * @param {number} location | |
117 * @param {number} length | |
118 * @param {string=} opt_description | |
119 * @return {!Marker} | |
120 */ | |
121 function spellingMarker(location, length, opt_description) { | |
122 return new Marker(location, length, 'spelling', opt_description); | |
123 } | |
124 | |
125 /** | |
126 * @param {number} location | |
127 * @param {number} length | |
128 * @param {string=} opt_description | |
129 * @return {!Marker} | |
130 */ | |
131 function grammarMarker(location, length, opt_description) { | |
132 return new Marker(location, length, 'grammar', opt_description); | |
133 } | |
134 | |
135 /** | |
136 * @param {Marker[]} expectedMarkers | |
137 */ | |
138 function checkExpectedMarkers(expectedMarkers) { | |
139 expectedMarkers.forEach(marker => marker.assertValid()); | |
140 expectedMarkers.sort((a, b) => a.location - b.location); | |
141 for (var i = 1; i < expectedMarkers.length; ++i) { | |
yosin_UTC9
2016/10/24 04:40:08
Let's use Array#forEach. We can write:
expectedMa
Xiaocheng
2016/10/24 05:11:37
Didn't know |forEach| takes a second parameter.
D
| |
142 assert_less_than( | |
143 expectedMarkers[i - 1].location + expectedMarkers[i - 1].length, | |
144 expectedMarkers[i].location, | |
145 'Marker ranges should be disjoint.'); | |
146 } | |
147 } | |
148 | |
149 /** | |
150 * @param {Node} node | |
yosin_UTC9
2016/10/24 04:40:08
nit: s/Node/!Node/
It think passing |null| to |ex
Xiaocheng
2016/10/24 05:11:37
Done.
| |
151 * @param {string} type | |
152 * @param {Marker[]} markers | |
153 */ | |
154 function extractMarkersOfType(node, type, markers) { | |
155 /** @type {HTMLBodyElement} */ | |
156 const body = node.ownerDocument.body; | |
157 /** @type {number} */ | |
158 const markerCount = internals.markerCountForNode(node, type); | |
159 for (var i = 0; i < markerCount; ++i) { | |
160 /** @type {Range} */ | |
161 const markerRange = internals.markerRangeForNode(node, type, i); | |
162 /** @type {string} */ | |
163 const description = internals.markerDescriptionForNode(node, type, i); | |
164 /** @type {number} */ | |
165 const location = internals.locationFromRange(body, markerRange); | |
166 /** @type {number} */ | |
167 const length = internals.lengthFromRange(body, markerRange); | |
168 | |
169 markers.push(new Marker(location, length, type, description)); | |
170 } | |
171 } | |
172 | |
173 /** | |
174 * @param {Node} node | |
yosin_UTC9
2016/10/24 04:40:08
nit: s/Node/!Node/
Xiaocheng
2016/10/24 05:11:37
Done.
| |
175 * @param {Marker[]} markers | |
yosin_UTC9
2016/10/24 04:40:08
s/Marker[]/!Array<!Marker>/
Xiaocheng
2016/10/24 05:11:37
Done.
| |
176 */ | |
177 function extractAllMarkersRecursivelyTo(node, markers) { | |
178 extractMarkersOfType(node, 'spelling', markers); | |
179 extractMarkersOfType(node, 'grammar', markers); | |
180 node.childNodes.forEach( | |
181 child => extractAllMarkersRecursivelyTo(child, markers)); | |
182 } | |
183 | |
184 /** | |
185 * @param {Document} doc | |
yosin_UTC9
2016/10/24 04:40:08
nit: s/Document/!Document/
Xiaocheng
2016/10/24 05:11:37
Done.
| |
186 * @return {Marker[]} | |
187 */ | |
188 function extractAllMarkers(doc) { | |
189 /** @type {Marker[]} */ | |
190 const markers = []; | |
191 extractAllMarkersRecursivelyTo(doc.body, markers); | |
192 markers.sort((a, b) => a.location - b.location); | |
193 return markers; | |
194 } | |
195 | |
196 /** | |
197 * @param {Test} testObject | |
198 * @param {Sample} sample, | |
199 * @param {Marker[]} expectedMarkers | |
200 * @param {number} remainingRetry | |
201 * @param {number} retryInterval | |
202 */ | |
203 function verifyMarkers( | |
204 testObject, sample, expectedMarkers, remainingRetry, retryInterval) { | |
205 assert_not_equals( | |
206 window.internals, undefined, | |
207 'window.internals is required for running automated spellcheck tests.'); | |
208 | |
209 /** @type {Marker[]} */ | |
210 const actualMarkers = extractAllMarkers(sample.document); | |
211 try { | |
212 assert_equals( | |
213 actualMarkers.length, expectedMarkers.length, | |
214 'Number of markers mismatch.'); | |
215 for (var i = 0; i < expectedMarkers.length; ++i) | |
216 actualMarkers[i].assertMatch(expectedMarkers[i]); | |
217 | |
218 testObject.done(); | |
219 sample.remove(); | |
220 } catch (e) { | |
221 if (remainingRetry > 0) { | |
222 // Force invoking idle time spellchecker if it was not run yet. | |
223 if (window.testRunner) | |
224 testRunner.runIdleTasks(() => {}); | |
225 | |
226 // TODO(xiaochengh): We should make SpellCheckRequester::didCheck trigger | |
227 // something in JavaScript (e.g., a |Promise|), so that we can actively | |
228 // know the completion of spellchecking instead of passively waiting for | |
229 // markers to appear or disappear. | |
230 testObject.step_timeout( | |
231 () => verifyMarkers(testObject, sample, expectedMarkers, | |
232 remainingRetry - 1, retryInterval), | |
233 retryInterval); | |
234 } else { | |
235 throw e; | |
236 } | |
237 } | |
238 } | |
239 | |
240 /** | |
241 * @param {string} inputText | |
242 * @param {function(!Document)|string} | |
243 * @param {Marker|Marker[]} expectedMarkers | |
244 * @param {string=} opt_title | |
245 */ | |
246 function spellcheckTest(inputText, tester, expectedMarkers, opt_title) { | |
247 /** @type {Test} */ | |
248 const testObject = async_test(opt_title); | |
249 | |
250 if (!(expectedMarkers instanceof Array)) | |
251 expectedMarkers = [expectedMarkers] | |
252 testObject.step(() => checkExpectedMarkers(expectedMarkers)); | |
253 | |
254 if (window.testRunner) | |
255 testRunner.setMockSpellCheckerEnabled(true); | |
256 | |
257 /** @type {Sample} */ | |
258 const sample = new Sample(inputText); | |
259 if (typeof(tester) === 'function') { | |
260 tester.call(window, sample.document); | |
261 } else if (typeof(tester) === 'string') { | |
262 const strings = tester.split(/ (.+)/); | |
263 sample.document.execCommand(strings[0], false, strings[1]); | |
264 } else { | |
265 testObject.step(() => assert_unreached(`Invalid tester: ${tester}`)); | |
266 } | |
267 | |
268 /** @type {number} */ | |
269 const maxRetry = 10; | |
270 /** @type {number} */ | |
271 const retryInterval = 50; | |
272 | |
273 // TODO(xiaochengh): We should make SpellCheckRequester::didCheck trigger | |
274 // something in JavaScript (e.g., a |Promise|), so that we can actively know | |
275 // the completion of spellchecking instead of passively waiting for markers to | |
276 // appear or disappear. | |
277 testObject.step_timeout( | |
278 () => verifyMarkers(testObject, sample, expectedMarkers, | |
279 maxRetry, retryInterval), | |
280 retryInterval); | |
281 } | |
282 | |
283 // Export symbols | |
284 window.Marker = Marker; | |
285 window.spellingMarker = spellingMarker; | |
286 window.grammarMarker = grammarMarker; | |
287 window.spellcheck_test = spellcheckTest; | |
288 })(); | |
OLD | NEW |