blob: 143a55a3c5ea762cc8bf6c458bb2c93033ac0acf [file] [log] [blame]
// 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();
}