368 lines
12 KiB
JavaScript
368 lines
12 KiB
JavaScript
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS-IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
goog.provide('goog.ui.RichTextSpellCheckerTest');
|
|
goog.setTestOnly('goog.ui.RichTextSpellCheckerTest');
|
|
|
|
goog.require('goog.dom.Range');
|
|
goog.require('goog.dom.classlist');
|
|
goog.require('goog.events.KeyCodes');
|
|
goog.require('goog.object');
|
|
goog.require('goog.spell.SpellCheck');
|
|
goog.require('goog.testing.MockClock');
|
|
goog.require('goog.testing.events');
|
|
goog.require('goog.testing.jsunit');
|
|
goog.require('goog.ui.RichTextSpellChecker');
|
|
|
|
var VOCABULARY = ['test', 'words', 'a', 'few'];
|
|
var SUGGESTIONS = ['foo', 'bar'];
|
|
var EXCLUDED_DATA = ['DIV.goog-quote', 'goog-comment', 'SPAN.goog-note'];
|
|
|
|
|
|
/**
|
|
* Delay in ms needed for the spell check word lookup to finish. Finishing the
|
|
* lookup also finishes the spell checking.
|
|
* @see goog.spell.SpellCheck.LOOKUP_DELAY_
|
|
*/
|
|
var SPELL_CHECK_LOOKUP_DELAY = 100;
|
|
|
|
var TEST_TEXT1 = 'this test is longer than a few words now';
|
|
var TEST_TEXT2 = 'test another simple text with misspelled words';
|
|
var TEST_TEXT3 = 'test another simple text with misspelled words' +
|
|
'<b class="goog-quote">test another simple text with misspelled words<u> ' +
|
|
'test another simple text with misspelled words<del class="goog-quote"> ' +
|
|
'test another simple text with misspelled words<i>this test is longer ' +
|
|
'than a few words now</i>test another simple text with misspelled words ' +
|
|
'<i>this test is longer than a few words now</i></del>test another ' +
|
|
'simple text with misspelled words<del class="goog-quote">test another ' +
|
|
'simple text with misspelled words<i>this test is longer than a few ' +
|
|
'words now</i>test another simple text with misspelled words<i>this test ' +
|
|
'is longer than a few words now</i></del></u>test another simple text ' +
|
|
'with misspelled words<u>test another simple text with misspelled words' +
|
|
'<del class="goog-quote">test another simple text with misspelled words' +
|
|
'<i> thistest is longer than a few words now</i>test another simple text ' +
|
|
'with misspelled words<i>this test is longer than a few words ' +
|
|
'now</i></del>test another simple text with misspelled words' +
|
|
'<del class="goog-quote">test another simple text with misspelled words' +
|
|
'<i>this test is longer than a few words now</i>test another simple text ' +
|
|
'with misspelled words<i>this test is longer than a few words ' +
|
|
'now</i></del></u></b>';
|
|
|
|
var spellChecker;
|
|
var handler;
|
|
var mockClock;
|
|
|
|
function setUp() {
|
|
mockClock = new goog.testing.MockClock(true /* install */);
|
|
handler = new goog.spell.SpellCheck(localSpellCheckingFunction);
|
|
spellChecker = new goog.ui.RichTextSpellChecker(handler);
|
|
}
|
|
|
|
function tearDown() {
|
|
spellChecker.dispose();
|
|
handler.dispose();
|
|
mockClock.dispose();
|
|
}
|
|
|
|
function waitForSpellCheckToFinish() {
|
|
mockClock.tick(SPELL_CHECK_LOOKUP_DELAY);
|
|
}
|
|
|
|
|
|
/**
|
|
* @typedef {!Array<string><string>>}
|
|
* @suppress {missingProvide}
|
|
*/
|
|
var lookupWordEntry;
|
|
|
|
|
|
/**
|
|
* Function to use for word lookup by the spell check handler. This function is
|
|
* supplied as a constructor parameter for the spell check handler.
|
|
* @param {!Array<string>} words Unknown words that need to be looked up.
|
|
* @param {!goog.spell.SpellCheck} spellChecker The spell check handler.
|
|
* @param {function(!Array.)} callback The lookup callback
|
|
* function.
|
|
*/
|
|
function localSpellCheckingFunction(words, spellChecker, callback) {
|
|
var len = words.length;
|
|
var results = [];
|
|
for (var i = 0; i < len; i++) {
|
|
var word = words[i];
|
|
var found = false;
|
|
for (var j = 0; j < VOCABULARY.length; ++j) {
|
|
if (VOCABULARY[j] == word) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (found) {
|
|
results.push([word, goog.spell.SpellCheck.WordStatus.VALID]);
|
|
} else {
|
|
results.push([word, goog.spell.SpellCheck.WordStatus.INVALID,
|
|
SUGGESTIONS]);
|
|
}
|
|
}
|
|
callback.call(spellChecker, results);
|
|
}
|
|
|
|
function testDocumentIntegrity() {
|
|
var el = document.getElementById('test1');
|
|
spellChecker.decorate(el);
|
|
el.appendChild(document.createTextNode(TEST_TEXT3));
|
|
var el2 = el.cloneNode(true);
|
|
|
|
spellChecker.setExcludeMarker('goog-quote');
|
|
spellChecker.check();
|
|
waitForSpellCheckToFinish();
|
|
spellChecker.ignoreWord('iggnore');
|
|
waitForSpellCheckToFinish();
|
|
spellChecker.check();
|
|
waitForSpellCheckToFinish();
|
|
spellChecker.resume();
|
|
waitForSpellCheckToFinish();
|
|
|
|
assertEquals('Spell checker run should not change the underlying element.',
|
|
el2.innerHTML, el.innerHTML);
|
|
}
|
|
|
|
function testExcludeMarkers() {
|
|
var el = document.getElementById('test1');
|
|
spellChecker.decorate(el);
|
|
spellChecker.setExcludeMarker(
|
|
['DIV.goog-quote', 'goog-comment', 'SPAN.goog-note']);
|
|
assertArrayEquals(['goog-quote', 'goog-comment', 'goog-note'],
|
|
spellChecker.excludeMarker);
|
|
assertArrayEquals(['DIV', undefined, 'SPAN'],
|
|
spellChecker.excludeTags);
|
|
el.innerHTML = '<div class="goog-quote">misspelling</div>' +
|
|
'<div class="goog-yes">misspelling</div>' +
|
|
'<div class="goog-note">misspelling</div>' +
|
|
'<div class="goog-comment">misspelling</div>' +
|
|
'<span>misspelling<span>';
|
|
|
|
spellChecker.check();
|
|
waitForSpellCheckToFinish();
|
|
assertEquals(3, spellChecker.getLastIndex());
|
|
}
|
|
|
|
function testBiggerDocument() {
|
|
var el = document.getElementById('test2');
|
|
spellChecker.decorate(el);
|
|
el.appendChild(document.createTextNode(TEST_TEXT3));
|
|
var el2 = el.cloneNode(true);
|
|
|
|
spellChecker.check();
|
|
waitForSpellCheckToFinish();
|
|
spellChecker.resume();
|
|
waitForSpellCheckToFinish();
|
|
|
|
assertEquals('Spell checker run should not change the underlying element.',
|
|
el2.innerHTML, el.innerHTML);
|
|
}
|
|
|
|
function testElementOverflow() {
|
|
var el = document.getElementById('test3');
|
|
spellChecker.decorate(el);
|
|
el.appendChild(document.createTextNode(TEST_TEXT3));
|
|
|
|
var el2 = el.cloneNode(true);
|
|
|
|
spellChecker.check();
|
|
waitForSpellCheckToFinish();
|
|
spellChecker.check();
|
|
waitForSpellCheckToFinish();
|
|
spellChecker.resume();
|
|
waitForSpellCheckToFinish();
|
|
|
|
assertEquals('Spell checker run should not change the underlying element.',
|
|
el2.innerHTML, el.innerHTML);
|
|
}
|
|
|
|
function testKeyboardNavigateNext() {
|
|
var el = document.getElementById('test4');
|
|
spellChecker.decorate(el);
|
|
var text = 'a unit test for keyboard test';
|
|
el.appendChild(document.createTextNode(text));
|
|
var keyEventProperties =
|
|
goog.object.create('ctrlKey', true, 'shiftKey', false);
|
|
|
|
spellChecker.check();
|
|
waitForSpellCheckToFinish();
|
|
|
|
// First call just moves focus to first misspelled word.
|
|
goog.testing.events.fireKeySequence(el, goog.events.KeyCodes.RIGHT,
|
|
keyEventProperties);
|
|
|
|
// Test moving from first to second mispelled word.
|
|
var defaultExecuted = goog.testing.events.fireKeySequence(el,
|
|
goog.events.KeyCodes.RIGHT, keyEventProperties);
|
|
|
|
assertFalse('The default action should be prevented for the key event',
|
|
defaultExecuted);
|
|
assertCursorAtElement(spellChecker.makeElementId(2));
|
|
|
|
spellChecker.resume();
|
|
}
|
|
|
|
function testKeyboardNavigateNextOnLastWord() {
|
|
var el = document.getElementById('test5');
|
|
spellChecker.decorate(el);
|
|
var text = 'a unit test for keyboard test';
|
|
el.appendChild(document.createTextNode(text));
|
|
var keyEventProperties =
|
|
goog.object.create('ctrlKey', true, 'shiftKey', false);
|
|
|
|
spellChecker.check();
|
|
waitForSpellCheckToFinish();
|
|
|
|
// Move to the last invalid word.
|
|
goog.testing.events.fireKeySequence(el, goog.events.KeyCodes.RIGHT,
|
|
keyEventProperties);
|
|
goog.testing.events.fireKeySequence(el, goog.events.KeyCodes.RIGHT,
|
|
keyEventProperties);
|
|
goog.testing.events.fireKeySequence(el, goog.events.KeyCodes.RIGHT,
|
|
keyEventProperties);
|
|
|
|
// Test moving to the next invalid word. Should have no effect.
|
|
var defaultExecuted = goog.testing.events.fireKeySequence(el,
|
|
goog.events.KeyCodes.RIGHT, keyEventProperties);
|
|
|
|
assertFalse('The default action should be prevented for the key event',
|
|
defaultExecuted);
|
|
assertCursorAtElement(spellChecker.makeElementId(3));
|
|
|
|
spellChecker.resume();
|
|
}
|
|
|
|
function testKeyboardNavigateOpenSuggestions() {
|
|
var el = document.getElementById('test6');
|
|
spellChecker.decorate(el);
|
|
var text = 'unit';
|
|
el.appendChild(document.createTextNode(text));
|
|
var keyEventProperties =
|
|
goog.object.create('ctrlKey', true, 'shiftKey', false);
|
|
|
|
spellChecker.check();
|
|
waitForSpellCheckToFinish();
|
|
|
|
var suggestionMenu = spellChecker.getMenu();
|
|
|
|
goog.testing.events.fireKeySequence(el, goog.events.KeyCodes.RIGHT,
|
|
keyEventProperties);
|
|
|
|
assertFalse('The suggestion menu should not be visible yet.',
|
|
suggestionMenu.isVisible());
|
|
|
|
keyEventProperties.ctrlKey = false;
|
|
var defaultExecuted = goog.testing.events.fireKeySequence(el,
|
|
goog.events.KeyCodes.DOWN, keyEventProperties);
|
|
|
|
assertFalse('The default action should be prevented for the key event',
|
|
defaultExecuted);
|
|
assertTrue('The suggestion menu should be visible after the key event.',
|
|
suggestionMenu.isVisible());
|
|
|
|
spellChecker.resume();
|
|
}
|
|
|
|
function testKeyboardNavigatePrevious() {
|
|
var el = document.getElementById('test7');
|
|
spellChecker.decorate(el);
|
|
var text = 'a unit test for keyboard test';
|
|
el.appendChild(document.createTextNode(text));
|
|
var keyEventProperties =
|
|
goog.object.create('ctrlKey', true, 'shiftKey', false);
|
|
|
|
spellChecker.check();
|
|
waitForSpellCheckToFinish();
|
|
|
|
// Move to the third element, so we can test the move back to the second.
|
|
goog.testing.events.fireKeySequence(el, goog.events.KeyCodes.RIGHT,
|
|
keyEventProperties);
|
|
goog.testing.events.fireKeySequence(el, goog.events.KeyCodes.RIGHT,
|
|
keyEventProperties);
|
|
goog.testing.events.fireKeySequence(el, goog.events.KeyCodes.RIGHT,
|
|
keyEventProperties);
|
|
|
|
var defaultExecuted = goog.testing.events.fireKeySequence(el,
|
|
goog.events.KeyCodes.LEFT, keyEventProperties);
|
|
|
|
assertFalse('The default action should be prevented for the key event',
|
|
defaultExecuted);
|
|
assertCursorAtElement(spellChecker.makeElementId(2));
|
|
|
|
spellChecker.resume();
|
|
}
|
|
|
|
function testKeyboardNavigatePreviousOnLastWord() {
|
|
var el = document.getElementById('test8');
|
|
spellChecker.decorate(el);
|
|
var text = 'a unit test for keyboard test';
|
|
el.appendChild(document.createTextNode(text));
|
|
var keyEventProperties =
|
|
goog.object.create('ctrlKey', true, 'shiftKey', false);
|
|
|
|
spellChecker.check();
|
|
waitForSpellCheckToFinish();
|
|
|
|
// Move to the first invalid word.
|
|
goog.testing.events.fireKeySequence(el, goog.events.KeyCodes.RIGHT,
|
|
keyEventProperties);
|
|
|
|
// Test moving to the previous invalid word. Should have no effect.
|
|
var defaultExecuted = goog.testing.events.fireKeySequence(el,
|
|
goog.events.KeyCodes.LEFT, keyEventProperties);
|
|
|
|
assertFalse('The default action should be prevented for the key event',
|
|
defaultExecuted);
|
|
assertCursorAtElement(spellChecker.makeElementId(1));
|
|
|
|
spellChecker.resume();
|
|
}
|
|
|
|
function assertCursorAtElement(expectedId) {
|
|
var range = goog.dom.Range.createFromWindow();
|
|
|
|
if (isCaret(range)) {
|
|
if (isMisspelledWordElement(range.getStartNode())) {
|
|
var focusedElementId = range.getStartNode().id;
|
|
}
|
|
|
|
// In Chrome a cursor at the start of a misspelled word will appear to be at
|
|
// the end of the text node preceding it.
|
|
if (isCursorAtEndOfStartNode(range) &&
|
|
range.getStartNode().nextSibling != null &&
|
|
isMisspelledWordElement(range.getStartNode().nextSibling)) {
|
|
var focusedElementId = range.getStartNode().nextSibling.id;
|
|
}
|
|
}
|
|
|
|
assertEquals('The cursor is not at the expected misspelled word.',
|
|
expectedId, focusedElementId);
|
|
}
|
|
|
|
function isCaret(range) {
|
|
return range.getStartNode() == range.getEndNode();
|
|
}
|
|
|
|
function isMisspelledWordElement(element) {
|
|
return goog.dom.classlist.contains(
|
|
element, 'goog-spellcheck-word');
|
|
}
|
|
|
|
function isCursorAtEndOfStartNode(range) {
|
|
return range.getStartNode().length == range.getStartOffset();
|
|
}
|