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 | 7 // This file provides |
8 // |spellcheck_test(sample, tester, expectedText, opt_title)| asynchronous test | 8 // |spellcheck_test(sample, tester, expectedText, opt_title)| asynchronous test |
9 // to W3C test harness for easier writing of spellchecker test cases. | 9 // to W3C test harness for easier writing of spellchecker test cases. |
10 // | 10 // |
(...skipping 257 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
268 */ | 268 */ |
269 serializeInternal(node) { | 269 serializeInternal(node) { |
270 if (isElement(node)) | 270 if (isElement(node)) |
271 return this.handleElementNode(node); | 271 return this.handleElementNode(node); |
272 if (isCharacterData(node)) | 272 if (isCharacterData(node)) |
273 return this.handleCharacterData(node); | 273 return this.handleCharacterData(node); |
274 throw new Error(`Unexpected node ${node}`); | 274 throw new Error(`Unexpected node ${node}`); |
275 } | 275 } |
276 } | 276 } |
277 | 277 |
| 278 /** |
| 279 * @param {!Document} document |
| 280 * @return {boolean} |
| 281 */ |
| 282 function hasPendingSpellCheckRequest(document) { |
| 283 return internals.lastSpellCheckRequestSequence(document) !== |
| 284 internals.lastSpellCheckProcessedSequence(document); |
| 285 } |
| 286 |
278 /** @type {string} */ | 287 /** @type {string} */ |
279 const kTitle = 'title'; | 288 const kTitle = 'title'; |
280 /** @type {string} */ | 289 /** @type {string} */ |
281 const kCallback = 'callback'; | 290 const kCallback = 'callback'; |
282 /** @type {string} */ | 291 /** @type {string} */ |
283 const kIsSpellcheckTest = 'isSpellcheckTest'; | 292 const kIsSpellcheckTest = 'isSpellcheckTest'; |
284 | 293 |
285 /** | |
286 * @param {!Test} testObject | |
287 * @param {!Sample} sample | |
288 * @param {string} expectedText | |
289 * @param {number} remainingRetry | |
290 * @param {number} retryInterval | |
291 */ | |
292 function verifyMarkers( | |
293 testObject, sample, expectedText, remainingRetry, retryInterval) { | |
294 assert_not_equals( | |
295 window.internals, undefined, | |
296 'window.internals is required for running automated spellcheck tests.'); | |
297 | |
298 /** @type {!MarkerSerializer} */ | |
299 const serializer = new MarkerSerializer({ | |
300 spelling: '#', | |
301 grammar: '~'}); | |
302 | |
303 try { | |
304 assert_equals(serializer.serialize(sample.document), expectedText); | |
305 testObject.done(); | |
306 } catch (error) { | |
307 if (remainingRetry <= 0) | |
308 throw error; | |
309 | |
310 // Force invoking idle time spellchecker in case it has not been run yet. | |
311 if (window.testRunner) | |
312 window.testRunner.runIdleTasks(() => {}); | |
313 | |
314 // TODO(xiaochengh): We should make SpellCheckRequester::didCheck trigger | |
315 // something in JavaScript (e.g., a |Promise|), so that we can actively | |
316 // know the completion of spellchecking instead of passively waiting for | |
317 // markers to appear or disappear. | |
318 testObject.step_timeout( | |
319 () => verifyMarkers(testObject, sample, expectedText, | |
320 remainingRetry - 1, retryInterval), | |
321 retryInterval); | |
322 } | |
323 } | |
324 | |
325 // Spellchecker gets triggered not only by text and selection change, but also | 294 // Spellchecker gets triggered not only by text and selection change, but also |
326 // by focus change. For example, misspelling markers in <INPUT> disappear when | 295 // by focus change. For example, misspelling markers in <INPUT> disappear when |
327 // the window loses focus, even though the selection does not change. | 296 // the window loses focus, even though the selection does not change. |
328 // Therefore, we disallow spellcheck tests from running simultaneously to | 297 // Therefore, we disallow spellcheck tests from running simultaneously to |
329 // prevent interference among them. If we call spellcheck_test while another | 298 // prevent interference among them. If we call spellcheck_test while another |
330 // test is running, the new test will be added into testQueue waiting for the | 299 // test is running, the new test will be added into testQueue waiting for the |
331 // completion of the previous test. | 300 // completion of the previous test. |
332 | 301 |
333 /** @type {boolean} */ | 302 /** @type {boolean} */ |
334 var spellcheckTestRunning = false; | 303 var spellcheckTestRunning = false; |
335 /** @type {!Array<!Object>} */ | 304 /** @type {!Array<!Object>} */ |
336 const testQueue = []; | 305 const testQueue = []; |
337 | 306 |
| 307 /** @type {?Function} */ |
| 308 let verificationForCurrentTest = null; |
| 309 |
338 /** | 310 /** |
339 * @param {!Test} testObject | 311 * @param {!Test} testObject |
340 * @param {!Sample|string} input | 312 * @param {!Sample|string} input |
341 * @param {function(!Document)|string} tester | 313 * @param {function(!Document)|string} tester |
342 * @param {string} expectedText | 314 * @param {string} expectedText |
343 */ | 315 */ |
344 function invokeSpellcheckTest(testObject, input, tester, expectedText) { | 316 function invokeSpellcheckTest(testObject, input, tester, expectedText) { |
345 spellcheckTestRunning = true; | 317 spellcheckTestRunning = true; |
346 | 318 |
347 testObject.step(() => { | 319 testObject.step(() => { |
348 // TODO(xiaochengh): Merge the following part with |assert_selection|. | 320 // TODO(xiaochengh): Merge the following part with |assert_selection|. |
349 /** @type {!Sample} */ | 321 /** @type {!Sample} */ |
350 const sample = typeof(input) === 'string' ? new Sample(input) : input; | 322 const sample = typeof(input) === 'string' ? new Sample(input) : input; |
351 testObject.sample = sample; | 323 testObject.sample = sample; |
352 | 324 |
353 if (typeof(tester) === 'function') { | 325 if (typeof(tester) === 'function') { |
354 tester.call(window, sample.document); | 326 tester.call(window, sample.document); |
355 } else if (typeof(tester) === 'string') { | 327 } else if (typeof(tester) === 'string') { |
356 const strings = tester.split(/ (.+)/); | 328 const strings = tester.split(/ (.+)/); |
357 sample.document.execCommand(strings[0], false, strings[1]); | 329 sample.document.execCommand(strings[0], false, strings[1]); |
358 } else { | 330 } else { |
359 assert_unreached(`Invalid tester: ${tester}`); | 331 assert_unreached(`Invalid tester: ${tester}`); |
360 } | 332 } |
361 | 333 |
362 /** @type {number} */ | 334 assert_not_equals( |
363 const kMaxRetry = 10; | 335 window.testRunner, undefined, |
364 /** @type {number} */ | 336 'window.testRunner is required for automated spellcheck tests.'); |
365 const kRetryInterval = 50; | 337 assert_not_equals( |
| 338 window.internals, undefined, |
| 339 'window.internals is required for automated spellcheck tests.'); |
366 | 340 |
367 // TODO(xiaochengh): We should make SpellCheckRequester::didCheck trigger | 341 assert_equals( |
368 // something in JavaScript (e.g., a |Promise|), so that we can actively know | 342 verificationForCurrentTest, null, |
369 // the completion of spellchecking instead of passively waiting for markers | 343 'Internal error: previous test not verified yet'); |
370 // to appear or disappear. | 344 |
371 testObject.step_timeout( | 345 verificationForCurrentTest = () => { |
372 () => verifyMarkers(testObject, sample, expectedText, | 346 if (hasPendingSpellCheckRequest(sample.document)) |
373 kMaxRetry, kRetryInterval), | 347 return; |
374 kRetryInterval); | 348 |
| 349 testObject.step(() => { |
| 350 /** @type {!MarkerSerializer} */ |
| 351 const serializer = new MarkerSerializer({ |
| 352 spelling: '#', |
| 353 grammar: '~'}); |
| 354 |
| 355 assert_equals(serializer.serialize(sample.document), expectedText); |
| 356 testObject.done(); |
| 357 }); |
| 358 }; |
| 359 |
| 360 if (internals.idleTimeSpellCheckerState !== undefined && |
| 361 internals.idleTimeSpellCheckerState(sample.document) === 'HotModeRequest
ed') { |
| 362 internals.runIdleTimeSpellChecker(sample.document); |
| 363 } |
| 364 |
| 365 // For a test that does not create new spell check request, a synchronous |
| 366 // verification finishes everything. |
| 367 verificationForCurrentTest(); |
375 }); | 368 }); |
376 } | 369 } |
377 | 370 |
378 add_result_callback(testObj => { | 371 add_result_callback(testObj => { |
379 if (!testObj.properties[kIsSpellcheckTest]) | 372 if (!testObj.properties[kIsSpellcheckTest]) |
380 return; | 373 return; |
381 | 374 |
| 375 verificationForCurrentTest = null; |
| 376 |
382 /** @type {boolean} */ | 377 /** @type {boolean} */ |
383 var shouldRemoveSample = false; | 378 var shouldRemoveSample = false; |
384 if (testObj.status === testObj.PASS) { | 379 if (testObj.status === testObj.PASS) { |
385 if (testObj.properties[kCallback]) | 380 if (testObj.properties[kCallback]) |
386 testObj.properties[kCallback](testObj.sample); | 381 testObj.properties[kCallback](testObj.sample); |
387 else | 382 else |
388 shouldRemoveSample = true; | 383 shouldRemoveSample = true; |
389 } else { | 384 } else { |
390 if (window.testRunner) | 385 if (window.testRunner) |
391 shouldRemoveSample = true; | 386 shouldRemoveSample = true; |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
425 return args; | 420 return args; |
426 } | 421 } |
427 | 422 |
428 /** | 423 /** |
429 * @param {!Sample|string} input | 424 * @param {!Sample|string} input |
430 * @param {function(!Document)|string} tester | 425 * @param {function(!Document)|string} tester |
431 * @param {string} expectedText | 426 * @param {string} expectedText |
432 * @param {Object=} opt_args | 427 * @param {Object=} opt_args |
433 */ | 428 */ |
434 function spellcheckTest(input, tester, expectedText, opt_args) { | 429 function spellcheckTest(input, tester, expectedText, opt_args) { |
435 if (window.testRunner) | |
436 window.testRunner.setMockSpellCheckerEnabled(true); | |
437 | |
438 /** @type {!Object} */ | 430 /** @type {!Object} */ |
439 const args = getTestArguments(opt_args); | 431 const args = getTestArguments(opt_args); |
440 /** @type {!Test} */ | 432 /** @type {!Test} */ |
441 const testObject = async_test(args[kTitle], args); | 433 const testObject = async_test(args[kTitle], args); |
442 | 434 |
443 if (spellcheckTestRunning) { | 435 if (spellcheckTestRunning) { |
444 testQueue.push({ | 436 testQueue.push({ |
445 testObject: testObject, input: input, | 437 testObject: testObject, input: input, |
446 tester: tester, expectedText: expectedText}); | 438 tester: tester, expectedText: expectedText}); |
447 return; | 439 return; |
448 } | 440 } |
449 | 441 |
450 invokeSpellcheckTest(testObject, input, tester, expectedText); | 442 invokeSpellcheckTest(testObject, input, tester, expectedText); |
451 } | 443 } |
452 | 444 |
| 445 if (window.testRunner) { |
| 446 window.testRunner.setMockSpellCheckerEnabled(true); |
| 447 window.testRunner.setSpellCheckResolvedCallback(() => { |
| 448 if (verificationForCurrentTest) |
| 449 verificationForCurrentTest(); |
| 450 }); |
| 451 } |
| 452 |
453 // Export symbols | 453 // Export symbols |
454 window.spellcheck_test = spellcheckTest; | 454 window.spellcheck_test = spellcheckTest; |
455 })(); | 455 })(); |
OLD | NEW |