// 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.TagName');
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' +
'test another simple text with misspelled words ' +
'test another simple text with misspelled words ' +
'test another simple text with misspelled wordsthis test is longer ' +
'than a few words nowtest another simple text with misspelled words ' +
'this test is longer than a few words nowtest another ' +
'simple text with misspelled wordstest another ' +
'simple text with misspelled wordsthis test is longer than a few ' +
'words nowtest another simple text with misspelled wordsthis test ' +
'is longer than a few words nowtest another simple text ' +
'with misspelled wordstest another simple text with misspelled words' +
'test another simple text with misspelled words' +
' thistest is longer than a few words nowtest another simple text ' +
'with misspelled wordsthis test is longer than a few words ' +
'nowtest another simple text with misspelled words' +
'test another simple text with misspelled words' +
'this test is longer than a few words nowtest another simple text ' +
'with misspelled wordsthis test is longer than a few words ' +
'now';
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);
}
/**
* 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} 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(
[String(goog.dom.TagName.DIV), undefined, String(goog.dom.TagName.SPAN)],
spellChecker.excludeTags);
el.innerHTML = 'misspelling
' +
'misspelling
' +
'misspelling
' +
'' +
'misspelling';
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 misspelled 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();
}