richtextspellchecker_test.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. // Copyright 2007 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. goog.provide('goog.ui.RichTextSpellCheckerTest');
  15. goog.setTestOnly('goog.ui.RichTextSpellCheckerTest');
  16. goog.require('goog.dom.Range');
  17. goog.require('goog.dom.TagName');
  18. goog.require('goog.dom.classlist');
  19. goog.require('goog.events.KeyCodes');
  20. goog.require('goog.object');
  21. goog.require('goog.spell.SpellCheck');
  22. goog.require('goog.testing.MockClock');
  23. goog.require('goog.testing.events');
  24. goog.require('goog.testing.jsunit');
  25. goog.require('goog.ui.RichTextSpellChecker');
  26. var VOCABULARY = ['test', 'words', 'a', 'few'];
  27. var SUGGESTIONS = ['foo', 'bar'];
  28. var EXCLUDED_DATA = ['DIV.goog-quote', 'goog-comment', 'SPAN.goog-note'];
  29. /**
  30. * Delay in ms needed for the spell check word lookup to finish. Finishing the
  31. * lookup also finishes the spell checking.
  32. * @see goog.spell.SpellCheck.LOOKUP_DELAY_
  33. */
  34. var SPELL_CHECK_LOOKUP_DELAY = 100;
  35. var TEST_TEXT1 = 'this test is longer than a few words now';
  36. var TEST_TEXT2 = 'test another simple text with misspelled words';
  37. var TEST_TEXT3 = 'test another simple text with misspelled words' +
  38. '<b class="goog-quote">test another simple text with misspelled words<u> ' +
  39. 'test another simple text with misspelled words<del class="goog-quote"> ' +
  40. 'test another simple text with misspelled words<i>this test is longer ' +
  41. 'than a few words now</i>test another simple text with misspelled words ' +
  42. '<i>this test is longer than a few words now</i></del>test another ' +
  43. 'simple text with misspelled words<del class="goog-quote">test another ' +
  44. 'simple text with misspelled words<i>this test is longer than a few ' +
  45. 'words now</i>test another simple text with misspelled words<i>this test ' +
  46. 'is longer than a few words now</i></del></u>test another simple text ' +
  47. 'with misspelled words<u>test another simple text with misspelled words' +
  48. '<del class="goog-quote">test another simple text with misspelled words' +
  49. '<i> thistest is longer than a few words now</i>test another simple text ' +
  50. 'with misspelled words<i>this test is longer than a few words ' +
  51. 'now</i></del>test another simple text with misspelled words' +
  52. '<del class="goog-quote">test another simple text with misspelled words' +
  53. '<i>this test is longer than a few words now</i>test another simple text ' +
  54. 'with misspelled words<i>this test is longer than a few words ' +
  55. 'now</i></del></u></b>';
  56. var spellChecker;
  57. var handler;
  58. var mockClock;
  59. function setUp() {
  60. mockClock = new goog.testing.MockClock(true /* install */);
  61. handler = new goog.spell.SpellCheck(localSpellCheckingFunction);
  62. spellChecker = new goog.ui.RichTextSpellChecker(handler);
  63. }
  64. function tearDown() {
  65. spellChecker.dispose();
  66. handler.dispose();
  67. mockClock.dispose();
  68. }
  69. function waitForSpellCheckToFinish() {
  70. mockClock.tick(SPELL_CHECK_LOOKUP_DELAY);
  71. }
  72. /**
  73. * Function to use for word lookup by the spell check handler. This function is
  74. * supplied as a constructor parameter for the spell check handler.
  75. * @param {!Array<string>} words Unknown words that need to be looked up.
  76. * @param {!goog.spell.SpellCheck} spellChecker The spell check handler.
  77. * @param {function(!Array.)} callback The lookup callback
  78. * function.
  79. */
  80. function localSpellCheckingFunction(words, spellChecker, callback) {
  81. var len = words.length;
  82. var results = [];
  83. for (var i = 0; i < len; i++) {
  84. var word = words[i];
  85. var found = false;
  86. for (var j = 0; j < VOCABULARY.length; ++j) {
  87. if (VOCABULARY[j] == word) {
  88. found = true;
  89. break;
  90. }
  91. }
  92. if (found) {
  93. results.push([word, goog.spell.SpellCheck.WordStatus.VALID]);
  94. } else {
  95. results.push(
  96. [word, goog.spell.SpellCheck.WordStatus.INVALID, SUGGESTIONS]);
  97. }
  98. }
  99. callback.call(spellChecker, results);
  100. }
  101. function testDocumentIntegrity() {
  102. var el = document.getElementById('test1');
  103. spellChecker.decorate(el);
  104. el.appendChild(document.createTextNode(TEST_TEXT3));
  105. var el2 = el.cloneNode(true);
  106. spellChecker.setExcludeMarker('goog-quote');
  107. spellChecker.check();
  108. waitForSpellCheckToFinish();
  109. spellChecker.ignoreWord('iggnore');
  110. waitForSpellCheckToFinish();
  111. spellChecker.check();
  112. waitForSpellCheckToFinish();
  113. spellChecker.resume();
  114. waitForSpellCheckToFinish();
  115. assertEquals(
  116. 'Spell checker run should not change the underlying element.',
  117. el2.innerHTML, el.innerHTML);
  118. }
  119. function testExcludeMarkers() {
  120. var el = document.getElementById('test1');
  121. spellChecker.decorate(el);
  122. spellChecker.setExcludeMarker(
  123. ['DIV.goog-quote', 'goog-comment', 'SPAN.goog-note']);
  124. assertArrayEquals(
  125. ['goog-quote', 'goog-comment', 'goog-note'], spellChecker.excludeMarker);
  126. assertArrayEquals(
  127. [String(goog.dom.TagName.DIV), undefined, String(goog.dom.TagName.SPAN)],
  128. spellChecker.excludeTags);
  129. el.innerHTML = '<div class="goog-quote">misspelling</div>' +
  130. '<div class="goog-yes">misspelling</div>' +
  131. '<div class="goog-note">misspelling</div>' +
  132. '<div class="goog-comment">misspelling</div>' +
  133. '<span>misspelling<span>';
  134. spellChecker.check();
  135. waitForSpellCheckToFinish();
  136. assertEquals(3, spellChecker.getLastIndex());
  137. }
  138. function testBiggerDocument() {
  139. var el = document.getElementById('test2');
  140. spellChecker.decorate(el);
  141. el.appendChild(document.createTextNode(TEST_TEXT3));
  142. var el2 = el.cloneNode(true);
  143. spellChecker.check();
  144. waitForSpellCheckToFinish();
  145. spellChecker.resume();
  146. waitForSpellCheckToFinish();
  147. assertEquals(
  148. 'Spell checker run should not change the underlying element.',
  149. el2.innerHTML, el.innerHTML);
  150. }
  151. function testElementOverflow() {
  152. var el = document.getElementById('test3');
  153. spellChecker.decorate(el);
  154. el.appendChild(document.createTextNode(TEST_TEXT3));
  155. var el2 = el.cloneNode(true);
  156. spellChecker.check();
  157. waitForSpellCheckToFinish();
  158. spellChecker.check();
  159. waitForSpellCheckToFinish();
  160. spellChecker.resume();
  161. waitForSpellCheckToFinish();
  162. assertEquals(
  163. 'Spell checker run should not change the underlying element.',
  164. el2.innerHTML, el.innerHTML);
  165. }
  166. function testKeyboardNavigateNext() {
  167. var el = document.getElementById('test4');
  168. spellChecker.decorate(el);
  169. var text = 'a unit test for keyboard test';
  170. el.appendChild(document.createTextNode(text));
  171. var keyEventProperties =
  172. goog.object.create('ctrlKey', true, 'shiftKey', false);
  173. spellChecker.check();
  174. waitForSpellCheckToFinish();
  175. // First call just moves focus to first misspelled word.
  176. goog.testing.events.fireKeySequence(
  177. el, goog.events.KeyCodes.RIGHT, keyEventProperties);
  178. // Test moving from first to second misspelled word.
  179. var defaultExecuted = goog.testing.events.fireKeySequence(
  180. el, goog.events.KeyCodes.RIGHT, keyEventProperties);
  181. assertFalse(
  182. 'The default action should be prevented for the key event',
  183. defaultExecuted);
  184. assertCursorAtElement(spellChecker.makeElementId(2));
  185. spellChecker.resume();
  186. }
  187. function testKeyboardNavigateNextOnLastWord() {
  188. var el = document.getElementById('test5');
  189. spellChecker.decorate(el);
  190. var text = 'a unit test for keyboard test';
  191. el.appendChild(document.createTextNode(text));
  192. var keyEventProperties =
  193. goog.object.create('ctrlKey', true, 'shiftKey', false);
  194. spellChecker.check();
  195. waitForSpellCheckToFinish();
  196. // Move to the last invalid word.
  197. goog.testing.events.fireKeySequence(
  198. el, goog.events.KeyCodes.RIGHT, keyEventProperties);
  199. goog.testing.events.fireKeySequence(
  200. el, goog.events.KeyCodes.RIGHT, keyEventProperties);
  201. goog.testing.events.fireKeySequence(
  202. el, goog.events.KeyCodes.RIGHT, keyEventProperties);
  203. // Test moving to the next invalid word. Should have no effect.
  204. var defaultExecuted = goog.testing.events.fireKeySequence(
  205. el, goog.events.KeyCodes.RIGHT, keyEventProperties);
  206. assertFalse(
  207. 'The default action should be prevented for the key event',
  208. defaultExecuted);
  209. assertCursorAtElement(spellChecker.makeElementId(3));
  210. spellChecker.resume();
  211. }
  212. function testKeyboardNavigateOpenSuggestions() {
  213. var el = document.getElementById('test6');
  214. spellChecker.decorate(el);
  215. var text = 'unit';
  216. el.appendChild(document.createTextNode(text));
  217. var keyEventProperties =
  218. goog.object.create('ctrlKey', true, 'shiftKey', false);
  219. spellChecker.check();
  220. waitForSpellCheckToFinish();
  221. var suggestionMenu = spellChecker.getMenu();
  222. goog.testing.events.fireKeySequence(
  223. el, goog.events.KeyCodes.RIGHT, keyEventProperties);
  224. assertFalse(
  225. 'The suggestion menu should not be visible yet.',
  226. suggestionMenu.isVisible());
  227. keyEventProperties.ctrlKey = false;
  228. var defaultExecuted = goog.testing.events.fireKeySequence(
  229. el, goog.events.KeyCodes.DOWN, keyEventProperties);
  230. assertFalse(
  231. 'The default action should be prevented for the key event',
  232. defaultExecuted);
  233. assertTrue(
  234. 'The suggestion menu should be visible after the key event.',
  235. suggestionMenu.isVisible());
  236. spellChecker.resume();
  237. }
  238. function testKeyboardNavigatePrevious() {
  239. var el = document.getElementById('test7');
  240. spellChecker.decorate(el);
  241. var text = 'a unit test for keyboard test';
  242. el.appendChild(document.createTextNode(text));
  243. var keyEventProperties =
  244. goog.object.create('ctrlKey', true, 'shiftKey', false);
  245. spellChecker.check();
  246. waitForSpellCheckToFinish();
  247. // Move to the third element, so we can test the move back to the second.
  248. goog.testing.events.fireKeySequence(
  249. el, goog.events.KeyCodes.RIGHT, keyEventProperties);
  250. goog.testing.events.fireKeySequence(
  251. el, goog.events.KeyCodes.RIGHT, keyEventProperties);
  252. goog.testing.events.fireKeySequence(
  253. el, goog.events.KeyCodes.RIGHT, keyEventProperties);
  254. var defaultExecuted = goog.testing.events.fireKeySequence(
  255. el, goog.events.KeyCodes.LEFT, keyEventProperties);
  256. assertFalse(
  257. 'The default action should be prevented for the key event',
  258. defaultExecuted);
  259. assertCursorAtElement(spellChecker.makeElementId(2));
  260. spellChecker.resume();
  261. }
  262. function testKeyboardNavigatePreviousOnLastWord() {
  263. var el = document.getElementById('test8');
  264. spellChecker.decorate(el);
  265. var text = 'a unit test for keyboard test';
  266. el.appendChild(document.createTextNode(text));
  267. var keyEventProperties =
  268. goog.object.create('ctrlKey', true, 'shiftKey', false);
  269. spellChecker.check();
  270. waitForSpellCheckToFinish();
  271. // Move to the first invalid word.
  272. goog.testing.events.fireKeySequence(
  273. el, goog.events.KeyCodes.RIGHT, keyEventProperties);
  274. // Test moving to the previous invalid word. Should have no effect.
  275. var defaultExecuted = goog.testing.events.fireKeySequence(
  276. el, goog.events.KeyCodes.LEFT, keyEventProperties);
  277. assertFalse(
  278. 'The default action should be prevented for the key event',
  279. defaultExecuted);
  280. assertCursorAtElement(spellChecker.makeElementId(1));
  281. spellChecker.resume();
  282. }
  283. function assertCursorAtElement(expectedId) {
  284. var range = goog.dom.Range.createFromWindow();
  285. if (isCaret(range)) {
  286. if (isMisspelledWordElement(range.getStartNode())) {
  287. var focusedElementId = range.getStartNode().id;
  288. }
  289. // In Chrome a cursor at the start of a misspelled word will appear to be at
  290. // the end of the text node preceding it.
  291. if (isCursorAtEndOfStartNode(range) &&
  292. range.getStartNode().nextSibling != null &&
  293. isMisspelledWordElement(range.getStartNode().nextSibling)) {
  294. var focusedElementId = range.getStartNode().nextSibling.id;
  295. }
  296. }
  297. assertEquals(
  298. 'The cursor is not at the expected misspelled word.', expectedId,
  299. focusedElementId);
  300. }
  301. function isCaret(range) {
  302. return range.getStartNode() == range.getEndNode();
  303. }
  304. function isMisspelledWordElement(element) {
  305. return goog.dom.classlist.contains(element, 'goog-spellcheck-word');
  306. }
  307. function isCursorAtEndOfStartNode(range) {
  308. return range.getStartNode().length == range.getStartOffset();
  309. }