Chromium Code Reviews| 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 // We need to ensure correct usage of testRunner.runIdleTasks() that: | |
| 308 // 1. We don't call runIdleTasks if another runIdleTasks is called but the | |
| 309 // callback has not been invoked yet; Otherwise, the current call is ignored. | |
| 310 // 2. When the callback is invoked, we only verify test cases whose testers | |
| 311 // finished before calling runIdleTasks(); Otherwise, the idle time spell | |
| 312 // check may have not been invoked at the verification time. | |
| 313 | |
| 314 /** @type {boolean} */ | |
| 315 var runIdleTasksRequested = false; | |
| 316 /** @type {!Array<!Function>} Verification functions of tests cases whose idle | |
|
tkent
2016/12/21 07:42:55
nit: For a multiline annotation, please fold after
Xiaocheng
2016/12/21 07:52:00
Done.
| |
| 317 * idle spell checkers are requested before the current call of runIdleTasks. | |
| 318 */ | |
| 319 var idleVerificationReadyQueue = []; | |
| 320 /** @type {!Array<!Function>} Verification functions of tests cases whose idle | |
|
tkent
2016/12/21 07:42:55
Ditto.
Xiaocheng
2016/12/21 07:52:00
Done.
| |
| 321 * idle spell checkers are requested after the current call of runIdleTasks. | |
| 322 */ | |
| 323 var idleVerificationWaitingQueue = []; | |
| 324 | |
| 325 function batchIdleVerification() { | |
| 326 runIdleTasksRequested = false; | |
| 327 idleVerificationReadyQueue.forEach(func => func()); | |
| 328 idleVerificationReadyQueue = idleVerificationWaitingQueue; | |
| 329 idleVerificationWaitingQueue = []; | |
| 330 if (idleVerificationReadyQueue.length) { | |
| 331 runIdleTasksRequested = true; | |
| 332 testRunner.runIdleTasks(batchIdleVerification); | |
| 333 } | |
| 334 } | |
| 335 | |
| 338 /** | 336 /** |
| 339 * @param {!Test} testObject | 337 * @param {!Test} testObject |
| 340 * @param {!Sample|string} input | 338 * @param {!Sample|string} input |
| 341 * @param {function(!Document)|string} tester | 339 * @param {function(!Document)|string} tester |
| 342 * @param {string} expectedText | 340 * @param {string} expectedText |
| 343 */ | 341 */ |
| 344 function invokeSpellcheckTest(testObject, input, tester, expectedText) { | 342 function invokeSpellcheckTest(testObject, input, tester, expectedText) { |
| 345 spellcheckTestRunning = true; | 343 spellcheckTestRunning = true; |
| 346 | 344 |
| 347 testObject.step(() => { | 345 testObject.step(() => { |
| 348 // TODO(xiaochengh): Merge the following part with |assert_selection|. | 346 // TODO(xiaochengh): Merge the following part with |assert_selection|. |
| 349 /** @type {!Sample} */ | 347 /** @type {!Sample} */ |
| 350 const sample = typeof(input) === 'string' ? new Sample(input) : input; | 348 const sample = typeof(input) === 'string' ? new Sample(input) : input; |
| 351 testObject.sample = sample; | 349 testObject.sample = sample; |
| 352 | 350 |
| 353 if (typeof(tester) === 'function') { | 351 if (typeof(tester) === 'function') { |
| 354 tester.call(window, sample.document); | 352 tester.call(window, sample.document); |
| 355 } else if (typeof(tester) === 'string') { | 353 } else if (typeof(tester) === 'string') { |
| 356 const strings = tester.split(/ (.+)/); | 354 const strings = tester.split(/ (.+)/); |
| 357 sample.document.execCommand(strings[0], false, strings[1]); | 355 sample.document.execCommand(strings[0], false, strings[1]); |
| 358 } else { | 356 } else { |
| 359 assert_unreached(`Invalid tester: ${tester}`); | 357 assert_unreached(`Invalid tester: ${tester}`); |
| 360 } | 358 } |
| 361 | 359 |
| 362 /** @type {number} */ | 360 assert_not_equals( |
| 363 const kMaxRetry = 10; | 361 window.testRunner, undefined, |
| 364 /** @type {number} */ | 362 'window.testRunner is required for automated spellcheck tests.'); |
| 365 const kRetryInterval = 50; | 363 assert_not_equals( |
| 364 window.internals, undefined, | |
| 365 'window.internals is required for automated spellcheck tests.'); | |
| 366 | 366 |
| 367 // TODO(xiaochengh): We should make SpellCheckRequester::didCheck trigger | 367 /** @type {!Function} */ |
| 368 // something in JavaScript (e.g., a |Promise|), so that we can actively know | 368 const verification = () => { |
| 369 // the completion of spellchecking instead of passively waiting for markers | 369 testObject.step(() => { |
| 370 // to appear or disappear. | 370 if (hasPendingSpellCheckRequest(sample.document)) |
| 371 testObject.step_timeout( | 371 return; |
| 372 () => verifyMarkers(testObject, sample, expectedText, | 372 |
| 373 kMaxRetry, kRetryInterval), | 373 /** @type {!MarkerSerializer} */ |
| 374 kRetryInterval); | 374 const serializer = new MarkerSerializer({ |
| 375 spelling: '#', | |
| 376 grammar: '~'}); | |
| 377 | |
| 378 assert_equals(serializer.serialize(sample.document), expectedText); | |
| 379 testObject.done(); | |
| 380 }); | |
| 381 } | |
| 382 | |
| 383 // Verify when all spell check requests are resolved. | |
| 384 testRunner.setSpellCheckResolvedCallback(verification); | |
| 385 | |
| 386 // For tests that do not expect new markers, verify with runIdleTasks. | |
| 387 if (runIdleTasksRequested) { | |
| 388 idleVerificationWaitingQueue.push(verification); | |
| 389 return; | |
| 390 } | |
| 391 | |
| 392 idleVerificationReadyQueue.push(verification); | |
| 393 runIdleTasksRequested = true; | |
| 394 testRunner.runIdleTasks(batchIdleVerification); | |
| 375 }); | 395 }); |
| 376 } | 396 } |
| 377 | 397 |
| 378 add_result_callback(testObj => { | 398 add_result_callback(testObj => { |
| 379 if (!testObj.properties[kIsSpellcheckTest]) | 399 if (!testObj.properties[kIsSpellcheckTest]) |
| 380 return; | 400 return; |
| 381 | 401 |
| 382 /** @type {boolean} */ | 402 /** @type {boolean} */ |
| 383 var shouldRemoveSample = false; | 403 var shouldRemoveSample = false; |
| 384 if (testObj.status === testObj.PASS) { | 404 if (testObj.status === testObj.PASS) { |
| 385 if (testObj.properties[kCallback]) | 405 if (testObj.properties[kCallback]) |
| 386 testObj.properties[kCallback](testObj.sample); | 406 testObj.properties[kCallback](testObj.sample); |
| 387 else | 407 else |
| 388 shouldRemoveSample = true; | 408 shouldRemoveSample = true; |
| 389 } else { | 409 } else { |
| 390 if (window.testRunner) | 410 if (window.testRunner) |
| 391 shouldRemoveSample = true; | 411 shouldRemoveSample = true; |
| 392 } | 412 } |
| 393 | 413 |
| 394 if (shouldRemoveSample) | 414 if (shouldRemoveSample) |
| 395 testObj.sample.remove(); | 415 testObj.sample.remove(); |
| 396 | 416 |
| 397 // This is the earliest timing when a new spellcheck_test can be started. | 417 // We may be in a spellCheckResolvedCallback here, so removing the callback |
| 398 spellcheckTestRunning = false; | 418 // (and hence, all remaining tasks) must be done asynchronously. |
| 419 setTimeout(() => { | |
| 420 if (window.testRunner) | |
| 421 testRunner.removeSpellCheckResolvedCallback(); | |
| 399 | 422 |
| 400 /** @type {Object} */ | 423 // This is the earliest timing when a new spellcheck_test can be started. |
| 401 const next = testQueue.shift(); | 424 spellcheckTestRunning = false; |
| 402 if (next === undefined) | 425 |
| 403 return; | 426 /** @type {Object} */ |
| 404 invokeSpellcheckTest(next.testObject, next.input, | 427 const next = testQueue.shift(); |
| 405 next.tester, next.expectedText); | 428 if (next === undefined) |
| 429 return; | |
| 430 invokeSpellcheckTest(next.testObject, next.input, | |
| 431 next.tester, next.expectedText); | |
| 432 }, 0); | |
| 406 }); | 433 }); |
| 407 | 434 |
| 408 /** | 435 /** |
| 409 * @param {Object=} passedArgs | 436 * @param {Object=} passedArgs |
| 410 * @return {!Object} | 437 * @return {!Object} |
| 411 */ | 438 */ |
| 412 function getTestArguments(passedArgs) { | 439 function getTestArguments(passedArgs) { |
| 413 const args = {}; | 440 const args = {}; |
| 414 args[kIsSpellcheckTest] = true; | 441 args[kIsSpellcheckTest] = true; |
| 415 [kTitle, kCallback].forEach(key => args[key] = undefined); | 442 [kTitle, kCallback].forEach(key => args[key] = undefined); |
| (...skipping 30 matching lines...) Expand all Loading... | |
| 446 tester: tester, expectedText: expectedText}); | 473 tester: tester, expectedText: expectedText}); |
| 447 return; | 474 return; |
| 448 } | 475 } |
| 449 | 476 |
| 450 invokeSpellcheckTest(testObject, input, tester, expectedText); | 477 invokeSpellcheckTest(testObject, input, tester, expectedText); |
| 451 } | 478 } |
| 452 | 479 |
| 453 // Export symbols | 480 // Export symbols |
| 454 window.spellcheck_test = spellcheckTest; | 481 window.spellcheck_test = spellcheckTest; |
| 455 })(); | 482 })(); |
| OLD | NEW |