// 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.PlainTextSpellCheckerTest'); goog.setTestOnly('goog.ui.PlainTextSpellCheckerTest'); goog.require('goog.Timer'); goog.require('goog.dom'); goog.require('goog.events.KeyCodes'); goog.require('goog.spell.SpellCheck'); goog.require('goog.testing.events'); goog.require('goog.testing.jsunit'); goog.require('goog.ui.PlainTextSpellChecker'); var missspelling = 'missspelling'; var iggnore = 'iggnore'; var vocabulary = ['test', 'words', 'a', 'few', missspelling, iggnore]; // We don't use Math.random() to make test predictable. Math.random is not // repeatable, so a success on the dev machine != success in the lab (or on // other dev machines). This is the same pseudorandom logic that CRT rand() // uses. var rseed = 1; function random(range) { rseed = (rseed * 1103515245 + 12345) & 0xffffffff; return ((rseed >> 16) & 0x7fff) % range; } 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; // Last two words are considered misspellings for (var j = 0; j < vocabulary.length - 2; ++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, ['foo', 'bar']]); } } callback.call(spellChecker, results); } function generateRandomSpace() { var string = ''; var nSpace = 1 + random(4); for (var i = 0; i < nSpace; ++i) { string += ' '; } return string; } function generateRandomString(maxWords, doQuotes) { var x = random(10); var string = ''; if (doQuotes) { if (x == 0) { string = 'On xxxxx yyyy wrote:\n> '; } else if (x < 3) { string = '> '; } } var nWords = 1 + random(maxWords); for (var i = 0; i < nWords; ++i) { string += vocabulary[random(vocabulary.length)]; string += generateRandomSpace(); } return string; } var timerQueue = []; function processTimerQueue() { while (timerQueue.length > 0) { var fn = timerQueue.shift(); fn(); } } function localTimer(fn, delay, obj) { if (obj) { fn = goog.bind(fn, obj); } timerQueue.push(fn); return timerQueue.length; } function testPlainTextSpellCheckerNoQuotes() { var handler = new goog.spell.SpellCheck(localSpellCheckingFunction); var s = new goog.ui.PlainTextSpellChecker(handler); s.asyncWordsPerBatch_ = 100; var el = document.getElementById('test1'); s.decorate(el); var text = ''; for (var i = 0; i < 10; ++i) { text += generateRandomString(10, false) + '\n'; } el.value = text; // Yes this looks bizarre. This is for '\n' processing. // They get converted to CRLF as part of the above statement. text = el.value; var timerSav = goog.Timer.callOnce; goog.Timer.callOnce = localTimer; s.check(); processTimerQueue(); s.ignoreWord(iggnore); processTimerQueue(); s.check(); processTimerQueue(); s.resume(); processTimerQueue(); goog.Timer.callOnce = timerSav; assertEquals( 'Spell checker run should not change the underlying element.', text, el.value); s.dispose(); } function testPlainTextSpellCheckerWithQuotes() { var handler = new goog.spell.SpellCheck(localSpellCheckingFunction); var s = new goog.ui.PlainTextSpellChecker(handler); s.asyncWordsPerBatch_ = 100; var el = document.getElementById('test2'); s.decorate(el); var text = ''; for (var i = 0; i < 10; ++i) { text += generateRandomString(10, true) + '\n'; } el.value = text; // Yes this looks bizarre. This is for '\n' processing. // They get converted to CRLF as part of the above statement. text = el.value; var timerSav = goog.Timer.callOnce; goog.Timer.callOnce = localTimer; s.setExcludeMarker(new RegExp('\nOn .* wrote:\n(> .*\n)+|\n(> .*\n)', 'g')); s.check(); processTimerQueue(); s.ignoreWord(iggnore); processTimerQueue(); s.check(); processTimerQueue(); s.resume(); processTimerQueue(); goog.Timer.callOnce = timerSav; assertEquals( 'Spell checker run should not change the underlying element.', text, el.value); s.dispose(); } function testPlainTextSpellCheckerWordReplacement() { var handler = new goog.spell.SpellCheck(localSpellCheckingFunction); var s = new goog.ui.PlainTextSpellChecker(handler); s.asyncWordsPerBatch_ = 100; var el = document.getElementById('test3'); s.decorate(el); var text = ''; for (var i = 0; i < 10; ++i) { text += generateRandomString(10, false) + '\n'; } el.value = text; var timerSav = goog.Timer.callOnce; goog.Timer.callOnce = localTimer; s.check(); processTimerQueue(); var container = s.overlay_; var wordEl = container.firstChild; while (wordEl) { if (goog.dom.getTextContent(wordEl) == missspelling) { break; } wordEl = wordEl.nextSibling; } if (!wordEl) { assertTrue( 'Cannot find the world that should have been here.' + 'Please revise the test', false); return; } s.activeWord_ = missspelling; s.activeElement_ = wordEl; var suggestions = s.getSuggestions_(); s.replaceWord(wordEl, missspelling, 'foo'); assertEquals( 'Should have set the original word attribute!', wordEl.getAttribute(goog.ui.AbstractSpellChecker.ORIGINAL_), missspelling); s.activeWord_ = goog.dom.getTextContent(wordEl); s.activeElement_ = wordEl; var newSuggestions = s.getSuggestions_(); assertEquals( 'Suggestion list should still be present even if the word ' + 'is now correct!', suggestions, newSuggestions); s.resume(); processTimerQueue(); goog.Timer.callOnce = timerSav; s.dispose(); } function testPlainTextSpellCheckerKeyboardNavigateNext() { var handler = new goog.spell.SpellCheck(localSpellCheckingFunction); var s = new goog.ui.PlainTextSpellChecker(handler); var el = document.getElementById('test4'); s.decorate(el); var text = 'a unit test for keyboard test'; el.value = text; var keyEventProperties = {}; keyEventProperties.ctrlKey = true; keyEventProperties.shiftKey = false; var timerSav = goog.Timer.callOnce; goog.Timer.callOnce = localTimer; s.check(); processTimerQueue(); var container = s.overlay_; // First call just moves focus to first misspelled word. goog.testing.events.fireKeySequence( container, goog.events.KeyCodes.RIGHT, keyEventProperties); // Test moving from first to second misspelled word. var defaultExecuted = goog.testing.events.fireKeySequence( container, goog.events.KeyCodes.RIGHT, keyEventProperties); assertFalse( 'The default action should be prevented for the key event', defaultExecuted); assertEquals( 'The second misspelled word should have focus.', document.activeElement, container.children[1]); s.resume(); processTimerQueue(); goog.Timer.callOnce = timerSav; s.dispose(); } function testPlainTextSpellCheckerKeyboardNavigateNextOnLastWord() { var handler = new goog.spell.SpellCheck(localSpellCheckingFunction); var s = new goog.ui.PlainTextSpellChecker(handler); var el = document.getElementById('test5'); s.decorate(el); var text = 'a unit test for keyboard test'; el.value = text; var keyEventProperties = {}; keyEventProperties.ctrlKey = true; keyEventProperties.shiftKey = false; var timerSav = goog.Timer.callOnce; goog.Timer.callOnce = localTimer; s.check(); processTimerQueue(); var container = s.overlay_; // First call just moves focus to first misspelled word. goog.testing.events.fireKeySequence( container, goog.events.KeyCodes.RIGHT, keyEventProperties); goog.testing.events.fireKeySequence( container, goog.events.KeyCodes.RIGHT, keyEventProperties); goog.testing.events.fireKeySequence( container, goog.events.KeyCodes.RIGHT, keyEventProperties); // Test moving to the next invalid word. var defaultExecuted = goog.testing.events.fireKeySequence( container, goog.events.KeyCodes.RIGHT, keyEventProperties); assertFalse( 'The default action should be prevented for the key event', defaultExecuted); assertEquals( 'The third/last misspelled word should have focus.', document.activeElement, container.children[2]); s.resume(); processTimerQueue(); goog.Timer.callOnce = timerSav; s.dispose(); } function testPlainTextSpellCheckerKeyboardNavigateOpenSuggestions() { var handler = new goog.spell.SpellCheck(localSpellCheckingFunction); var s = new goog.ui.PlainTextSpellChecker(handler); var el = document.getElementById('test6'); s.decorate(el); var text = 'unit'; el.value = text; var keyEventProperties = {}; keyEventProperties.ctrlKey = true; keyEventProperties.shiftKey = false; var timerSav = goog.Timer.callOnce; goog.Timer.callOnce = localTimer; s.check(); processTimerQueue(); var container = s.overlay_; var suggestionMenu = s.getMenu(); goog.testing.events.fireKeySequence( container, 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( container, 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()); s.resume(); processTimerQueue(); goog.Timer.callOnce = timerSav; s.dispose(); } function testPlainTextSpellCheckerKeyboardNavigatePrevious() { var handler = new goog.spell.SpellCheck(localSpellCheckingFunction); var s = new goog.ui.PlainTextSpellChecker(handler); var el = document.getElementById('test7'); s.decorate(el); var text = 'a unit test for keyboard test'; el.value = text; var keyEventProperties = {}; keyEventProperties.ctrlKey = true; keyEventProperties.shiftKey = false; var timerSav = goog.Timer.callOnce; goog.Timer.callOnce = localTimer; s.check(); processTimerQueue(); var container = s.overlay_; // Move to the third element, so we can test the move back to the second. goog.testing.events.fireKeySequence( container, goog.events.KeyCodes.RIGHT, keyEventProperties); goog.testing.events.fireKeySequence( container, goog.events.KeyCodes.RIGHT, keyEventProperties); goog.testing.events.fireKeySequence( container, goog.events.KeyCodes.RIGHT, keyEventProperties); // Test moving from third to second misspelled word. var defaultExecuted = goog.testing.events.fireKeySequence( container, goog.events.KeyCodes.LEFT, keyEventProperties); assertFalse( 'The default action should be prevented for the key event', defaultExecuted); assertEquals( 'The second misspelled word should have focus.', document.activeElement, container.children[1]); s.resume(); processTimerQueue(); goog.Timer.callOnce = timerSav; s.dispose(); } function testPlainTextSpellCheckerKeyboardNavigatePreviousOnFirstWord() { var handler = new goog.spell.SpellCheck(localSpellCheckingFunction); var s = new goog.ui.PlainTextSpellChecker(handler); var el = document.getElementById('test8'); s.decorate(el); var text = 'a unit test for keyboard test'; el.value = text; var keyEventProperties = {}; keyEventProperties.ctrlKey = true; keyEventProperties.shiftKey = false; var timerSav = goog.Timer.callOnce; goog.Timer.callOnce = localTimer; s.check(); processTimerQueue(); var container = s.overlay_; // Move to the first invalid word. goog.testing.events.fireKeySequence( container, goog.events.KeyCodes.RIGHT, keyEventProperties); // Test moving to the previous invalid word. var defaultExecuted = goog.testing.events.fireKeySequence( container, goog.events.KeyCodes.LEFT, keyEventProperties); assertFalse( 'The default action should be prevented for the key event', defaultExecuted); assertEquals( 'The first misspelled word should have focus.', document.activeElement, container.children[0]); s.resume(); processTimerQueue(); goog.Timer.callOnce = timerSav; s.dispose(); }