From 785272540e70fe89e15d022cc373de3288989231 Mon Sep 17 00:00:00 2001 From: ehhc Date: Sat, 23 Jun 2018 18:15:16 +0200 Subject: [PATCH] Spellcheck - liveSpellcheck --- browser/components/CodeEditor.js | 2 +- browser/lib/spellcheck.js | 49 ++-- tests/lib/spellcheck.test.js | 415 +++++++++++++++++++++++++++++-- 3 files changed, 430 insertions(+), 36 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 6287e1fd..230b55a7 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -322,7 +322,7 @@ export default class CodeEditor extends React.Component { } handleChange (editor, changeObject) { - spellcheck.handleChange(editor, changeObject) + spellcheck.liveSpellcheck(editor, changeObject) this.value = editor.getValue() if (this.props.onChange) { this.props.onChange(editor) diff --git a/browser/lib/spellcheck.js b/browser/lib/spellcheck.js index 37dcce4c..09f9c646 100644 --- a/browser/lib/spellcheck.js +++ b/browser/lib/spellcheck.js @@ -75,7 +75,7 @@ function checkMultiLineRange (thisReference, editor, from, to) { } while (w < wEnd) { const wordRange = editor.findWordAt({line: l, ch: w}) - thisReference.checkRange(editor, wordRange) + thisReference.checkWord(editor, wordRange) w += (wordRange.head.ch - wordRange.anchor.ch) + 1 } } @@ -89,7 +89,7 @@ function checkMultiLineRange (thisReference, editor, from, to) { * @param wordRange Object specifying the range that should be checked. * Having the following structure: {anchor: {line: integer, ch: integer}, head: {line: integer, ch: integer}} */ -function checkRange (editor, wordRange) { +function checkWord (editor, wordRange) { const word = editor.getRange(wordRange.anchor, wordRange.head) if (word == null || word.length <= 3) { return @@ -99,16 +99,21 @@ function checkRange (editor, wordRange) { } } -function handleChange (editor, changeObject) { +/** + * Checks the changes recently made (aka live check) + * @param {Codemirror} editor CodeMirror-Editor + * @param changeObject codeMirror changeObject + */ +function liveSpellcheck (editor, changeObject) { /** * Returns the range that is smaller (i.e. that is before the other in the editor) */ function getLesserRange (from, to) { if (from.line > to.line) { - from = to + return to } else { if (from.ch > to.ch) { - from = to + return to } } return from @@ -118,11 +123,7 @@ function handleChange (editor, changeObject) { let to = {line: from.line, ch: from.ch} const changeArray = changeObject.text || [''] to.line += changeArray.length - 1 - let charactersInLastLineOfChange = changeArray[changeArray.length - 1].length - // If the new text is not empty we need to subtract one from the length due to the counting starting at 0 - if (changeArray[changeArray.length - 1] !== '') { - charactersInLastLineOfChange -= 1 - } + const charactersInLastLineOfChange = changeArray[changeArray.length - 1].length if (from.line === to.line) { to.ch += charactersInLastLineOfChange } else { @@ -131,20 +132,34 @@ function handleChange (editor, changeObject) { return to } - if (dictionary !== null) { + if (dictionary === null || editor == null) { return } + try { + let rangeCheck = true let from = getLesserRange(changeObject.from, changeObject.to) let to = calcTo(from) - if (from.line === to.line && from.ch === to.ch) { - if (changeObject.text[changeObject.text.length - 1] !== '') { - from.ch -= 1 + const newTextLastLine = changeObject.text[changeObject.text.length - 1] + if (from.line === to.line && newTextLastLine.length <= 1) { + if (newTextLastLine === '' || newTextLastLine === ' ') { + from.ch = Math.max(0, from.ch - 1) } + const wordRange = editor.findWordAt({line: from.line, ch: from.ch}) + from = wordRange.anchor + to = wordRange.head + rangeCheck = false } const existingMarks = editor.findMarks(from, to) || [] for (const mark of existingMarks) { mark.clear() } - checkMultiLineRange(this, editor, from, to) + + if (rangeCheck) { + this.checkMultiLineRange(this, editor, from, to) + } else { + this.checkWord(editor, {anchor: from, head: to}) + } + } catch (e) { + console.info('Error during the spell check. It might be due to problems figuring out the range of the new text..', e) } } @@ -154,8 +169,8 @@ module.exports = { SPELLCHECK_DISABLED, getAvailableDictionaries, initialize, - handleChange, - checkRange, + liveSpellcheck, + checkWord, checkMultiLineRange, checkWholeDocument, setDictionaryForTestsOnly diff --git a/tests/lib/spellcheck.test.js b/tests/lib/spellcheck.test.js index 87d3e8a5..4a8068eb 100644 --- a/tests/lib/spellcheck.test.js +++ b/tests/lib/spellcheck.test.js @@ -8,7 +8,7 @@ beforeEach(() => { Typo.mockClear() }) -it('should test that checkRange does not marks words that do not contain a typo', function () { +it('should test that checkWord does not marks words that do not contain a typo', function () { const testWord = 'testWord' const editor = jest.fn() editor.getRange = jest.fn(() => testWord) @@ -18,14 +18,14 @@ it('should test that checkRange does not marks words that do not contain a typo' mockDictionary.check = jest.fn(() => true) systemUnderTest.setDictionaryForTestsOnly(mockDictionary) - systemUnderTest.checkRange(editor, range) + systemUnderTest.checkWord(editor, range) expect(editor.getRange).toHaveBeenCalledWith(range.anchor, range.head) expect(mockDictionary.check).toHaveBeenCalledWith(testWord) expect(editor.markText).not.toHaveBeenCalled() }) -it('should test that checkRange should marks words that contain a typo', function () { +it('should test that checkWord should marks words that contain a typo', function () { const testWord = 'testWord' const editor = jest.fn() editor.getRange = jest.fn(() => testWord) @@ -35,7 +35,7 @@ it('should test that checkRange should marks words that contain a typo', functio mockDictionary.check = jest.fn(() => false) systemUnderTest.setDictionaryForTestsOnly(mockDictionary) - systemUnderTest.checkRange(editor, range) + systemUnderTest.checkWord(editor, range) expect(editor.getRange).toHaveBeenCalledWith(range.anchor, range.head) expect(mockDictionary.check).toHaveBeenCalledWith(testWord) @@ -117,7 +117,7 @@ it('should test that checkMultiLineRange performs checks for each word in the st editor.findWordAt.mockReturnValueOnce(ranges[8]) editor.findWordAt.mockReturnValueOnce(ranges[9]) editor.findWordAt.mockReturnValueOnce(ranges[10]) - const checkRangeSpy = jest.spyOn(systemUnderTest, 'checkRange').mockImplementation() + const checkWordSpy = jest.spyOn(systemUnderTest, 'checkWord').mockImplementation() systemUnderTest.checkMultiLineRange(systemUnderTest, editor, rangeFrom, rangeTo) @@ -134,18 +134,397 @@ it('should test that checkMultiLineRange performs checks for each word in the st expect(editor.findWordAt.mock.calls[8][0]).toEqual({line: 3, ch: 0}) expect(editor.findWordAt.mock.calls[9][0]).toEqual({line: 3, ch: 11}) expect(editor.findWordAt.mock.calls[10][0]).toEqual({line: 3, ch: 12}) - expect(checkRangeSpy).toHaveBeenCalledTimes(11) - expect(checkRangeSpy.mock.calls[0][1]).toEqual(ranges[0]) - expect(checkRangeSpy.mock.calls[1][1]).toEqual(ranges[1]) - expect(checkRangeSpy.mock.calls[2][1]).toEqual(ranges[2]) - expect(checkRangeSpy.mock.calls[3][1]).toEqual(ranges[3]) - expect(checkRangeSpy.mock.calls[4][1]).toEqual(ranges[4]) - expect(checkRangeSpy.mock.calls[5][1]).toEqual(ranges[5]) - expect(checkRangeSpy.mock.calls[6][1]).toEqual(ranges[6]) - expect(checkRangeSpy.mock.calls[7][1]).toEqual(ranges[7]) - expect(checkRangeSpy.mock.calls[8][1]).toEqual(ranges[8]) - expect(checkRangeSpy.mock.calls[9][1]).toEqual(ranges[9]) - expect(checkRangeSpy.mock.calls[10][1]).toEqual(ranges[10]) + expect(checkWordSpy).toHaveBeenCalledTimes(11) + expect(checkWordSpy.mock.calls[0][1]).toEqual(ranges[0]) + expect(checkWordSpy.mock.calls[1][1]).toEqual(ranges[1]) + expect(checkWordSpy.mock.calls[2][1]).toEqual(ranges[2]) + expect(checkWordSpy.mock.calls[3][1]).toEqual(ranges[3]) + expect(checkWordSpy.mock.calls[4][1]).toEqual(ranges[4]) + expect(checkWordSpy.mock.calls[5][1]).toEqual(ranges[5]) + expect(checkWordSpy.mock.calls[6][1]).toEqual(ranges[6]) + expect(checkWordSpy.mock.calls[7][1]).toEqual(ranges[7]) + expect(checkWordSpy.mock.calls[8][1]).toEqual(ranges[8]) + expect(checkWordSpy.mock.calls[9][1]).toEqual(ranges[9]) + expect(checkWordSpy.mock.calls[10][1]).toEqual(ranges[10]) - checkRangeSpy.mockRestore() + checkWordSpy.mockRestore() +}) + +it('should make sure that liveSpellcheck dont work if the spellcheck is not enabled', function () { + const checkMultiLineRangeSpy = jest.spyOn(systemUnderTest, 'checkMultiLineRange').mockImplementation() + const checkWordSpy = jest.spyOn(systemUnderTest, 'checkWord').mockImplementation() + const editor = jest.fn() + editor.findMarks = jest.fn() + + systemUnderTest.setDictionaryForTestsOnly(null) + systemUnderTest.liveSpellcheck(editor, {}) + + expect(checkWordSpy).not.toHaveBeenCalled() + expect(checkMultiLineRangeSpy).not.toHaveBeenCalled() + expect(editor.findMarks).not.toHaveBeenCalled() + + checkWordSpy.mockRestore() + checkMultiLineRangeSpy.mockRestore() +}) + +it('should make sure that liveSpellcheck works for a range of changes', function () { + const checkMultiLineRangeSpy = jest.spyOn(systemUnderTest, 'checkMultiLineRange').mockImplementation() + const checkWordSpy = jest.spyOn(systemUnderTest, 'checkWord').mockImplementation() + const editor = jest.fn() + editor.findMarks = jest.fn() + const dummyDictionary = jest.fn() + const changeObject = { + from: {line: 2, ch: 0}, + to: {line: 7, ch: 13}, + text: [ + 'first line', + 'second line', + 'third line' + ] + } + + const expectedFrom = changeObject.from + const expectedTo = {line: 4, ch: 10} + + systemUnderTest.setDictionaryForTestsOnly(dummyDictionary) + systemUnderTest.liveSpellcheck(editor, changeObject) + + expect(checkWordSpy).not.toHaveBeenCalled() + expect(checkMultiLineRangeSpy).toHaveBeenCalledTimes(1) + expect(checkMultiLineRangeSpy.mock.calls[0][1]).toEqual(editor) + expect(checkMultiLineRangeSpy.mock.calls[0][2]).toEqual(expectedFrom) + expect(checkMultiLineRangeSpy.mock.calls[0][3]).toEqual(expectedTo) + + checkWordSpy.mockRestore() + checkMultiLineRangeSpy.mockRestore() +}) + +it('should make sure that liveSpellcheck works for a range of changes with inverted from to range (due to paste action)', function () { + const checkMultiLineRangeSpy = jest.spyOn(systemUnderTest, 'checkMultiLineRange').mockImplementation() + const checkWordSpy = jest.spyOn(systemUnderTest, 'checkWord').mockImplementation() + const editor = jest.fn() + editor.findMarks = jest.fn() + const dummyDictionary = jest.fn() + const changeObject = { + from: {line: 7, ch: 12}, + to: {line: 2, ch: 0}, + text: [ + 'first line', + 'second line', + 'third line' + ] + } + + const expectedFrom = changeObject.to + const expectedTo = {line: 4, ch: 10} + + systemUnderTest.setDictionaryForTestsOnly(dummyDictionary) + systemUnderTest.liveSpellcheck(editor, changeObject) + + expect(checkWordSpy).not.toHaveBeenCalled() + expect(checkMultiLineRangeSpy).toHaveBeenCalledTimes(1) + expect(checkMultiLineRangeSpy.mock.calls[0][1]).toEqual(editor) + expect(checkMultiLineRangeSpy.mock.calls[0][2]).toEqual(expectedFrom) + expect(checkMultiLineRangeSpy.mock.calls[0][3]).toEqual(expectedTo) + + checkWordSpy.mockRestore() + checkMultiLineRangeSpy.mockRestore() +}) + +it('should make sure that liveSpellcheck deletes all existing marks in the given range', function () { + const checkMultiLineRangeSpy = jest.spyOn(systemUnderTest, 'checkMultiLineRange').mockImplementation() + const checkWordSpy = jest.spyOn(systemUnderTest, 'checkWord').mockImplementation() + const editor = jest.fn() + const dummyMarks = [ + {clear: jest.fn()}, + {clear: jest.fn()}, + {clear: jest.fn()} + ] + editor.findMarks = jest.fn(() => dummyMarks) + const dummyDictionary = jest.fn() + const changeObject = { + from: {line: 7, ch: 12}, + to: {line: 2, ch: 0}, + text: [ + 'first line', + 'second line', + 'third line' + ] + } + const expectedFrom = changeObject.to + const expectedTo = {line: 4, ch: 10} + + systemUnderTest.setDictionaryForTestsOnly(dummyDictionary) + systemUnderTest.liveSpellcheck(editor, changeObject) + + expect(editor.findMarks).toHaveBeenCalledWith(expectedFrom, expectedTo) + for (const dummyMark of dummyMarks) { + expect(dummyMark.clear).toHaveBeenCalled() + } + + checkWordSpy.mockRestore() + checkMultiLineRangeSpy.mockRestore() +}) + +it('should make sure that liveSpellcheck works when adding a sign', function () { + const checkMultiLineRangeSpy = jest.spyOn(systemUnderTest, 'checkMultiLineRange').mockImplementation() + const checkWordSpy = jest.spyOn(systemUnderTest, 'checkWord').mockImplementation() + const dummyWordRangeResult = {anchor: {line: 33, ch: 33}, head: {line: 33, ch: 34}} + const editor = jest.fn() + editor.findMarks = jest.fn() + editor.findWordAt = jest.fn(() => dummyWordRangeResult) + const dummyDictionary = jest.fn() + const changeObject = { + from: {line: 2, ch: 0}, + to: {line: 2, ch: 1}, + text: [ + 'a' + ] + } + + systemUnderTest.setDictionaryForTestsOnly(dummyDictionary) + systemUnderTest.liveSpellcheck(editor, changeObject) + + expect(checkMultiLineRangeSpy).not.toHaveBeenCalled() + expect(checkWordSpy).toHaveBeenCalledTimes(1) + expect(editor.findWordAt).toHaveBeenCalledWith(changeObject.from) + expect(checkWordSpy.mock.calls[0][0]).toEqual(editor) + expect(checkWordSpy.mock.calls[0][1]).toEqual(dummyWordRangeResult) + + checkWordSpy.mockRestore() + checkMultiLineRangeSpy.mockRestore() +}) + +it('should make sure that liveSpellcheck deletes the correct mark when adding a sign', function () { + const checkMultiLineRangeSpy = jest.spyOn(systemUnderTest, 'checkMultiLineRange').mockImplementation() + const checkWordSpy = jest.spyOn(systemUnderTest, 'checkWord').mockImplementation() + const dummyWordRangeResult = {anchor: {line: 33, ch: 33}, head: {line: 33, ch: 34}} + const editor = jest.fn() + const dummyMarks = [ + {clear: jest.fn()}, + {clear: jest.fn()}, + {clear: jest.fn()} + ] + editor.findMarks = jest.fn(() => dummyMarks) + editor.findWordAt = jest.fn(() => dummyWordRangeResult) + const dummyDictionary = jest.fn() + const changeObject = { + from: {line: 2, ch: 0}, + to: {line: 2, ch: 1}, + text: [ + 'a' + ] + } + + systemUnderTest.setDictionaryForTestsOnly(dummyDictionary) + systemUnderTest.liveSpellcheck(editor, changeObject) + + expect(checkMultiLineRangeSpy).not.toHaveBeenCalled() + expect(checkWordSpy).toHaveBeenCalledTimes(1) + expect(editor.findWordAt).toHaveBeenCalledWith(changeObject.from) + expect(editor.findMarks).toHaveBeenCalledWith(dummyWordRangeResult.anchor, dummyWordRangeResult.head) + for (const dummyMark of dummyMarks) { + expect(dummyMark.clear).toHaveBeenCalled() + } + + checkWordSpy.mockRestore() + checkMultiLineRangeSpy.mockRestore() +}) + +it('should make sure that liveSpellcheck works when deleting a sign', function () { + const checkMultiLineRangeSpy = jest.spyOn(systemUnderTest, 'checkMultiLineRange').mockImplementation() + const checkWordSpy = jest.spyOn(systemUnderTest, 'checkWord').mockImplementation() + const dummyWordRangeResult = {anchor: {line: 33, ch: 33}, head: {line: 33, ch: 34}} + const editor = jest.fn() + editor.findMarks = jest.fn() + editor.findWordAt = jest.fn(() => dummyWordRangeResult) + const dummyDictionary = jest.fn() + const changeObject = { + from: {line: 2, ch: 10}, + to: {line: 2, ch: 10}, + text: [ + '' + ] + } + + const expectedFrom = {line: 2, ch: 9} + + systemUnderTest.setDictionaryForTestsOnly(dummyDictionary) + systemUnderTest.liveSpellcheck(editor, changeObject) + + expect(checkMultiLineRangeSpy).not.toHaveBeenCalled() + expect(checkWordSpy).toHaveBeenCalledTimes(1) + expect(editor.findWordAt).toHaveBeenCalledWith(expectedFrom) + expect(checkWordSpy.mock.calls[0][0]).toEqual(editor) + expect(checkWordSpy.mock.calls[0][1]).toEqual(dummyWordRangeResult) + + checkWordSpy.mockRestore() + checkMultiLineRangeSpy.mockRestore() +}) + +it('should make sure that liveSpellcheck deletes the right marks when deleting a sign', function () { + const checkMultiLineRangeSpy = jest.spyOn(systemUnderTest, 'checkMultiLineRange').mockImplementation() + const checkWordSpy = jest.spyOn(systemUnderTest, 'checkWord').mockImplementation() + const dummyWordRangeResult = {anchor: {line: 33, ch: 33}, head: {line: 33, ch: 34}} + const editor = jest.fn() + const dummyMarks = [ + {clear: jest.fn()}, + {clear: jest.fn()}, + {clear: jest.fn()} + ] + editor.findMarks = jest.fn(() => dummyMarks) + editor.findWordAt = jest.fn(() => dummyWordRangeResult) + const dummyDictionary = jest.fn() + const changeObject = { + from: {line: 2, ch: 10}, + to: {line: 2, ch: 10}, + text: [ + '' + ] + } + + systemUnderTest.setDictionaryForTestsOnly(dummyDictionary) + systemUnderTest.liveSpellcheck(editor, changeObject) + + expect(checkMultiLineRangeSpy).not.toHaveBeenCalled() + expect(checkWordSpy).toHaveBeenCalledTimes(1) + expect(editor.findMarks).toHaveBeenCalledWith(dummyWordRangeResult.anchor, dummyWordRangeResult.head) + for (const dummyMark of dummyMarks) { + expect(dummyMark.clear).toHaveBeenCalled() + } + + checkWordSpy.mockRestore() + checkMultiLineRangeSpy.mockRestore() +}) + +it('should make sure that liveSpellcheck works when inserting a space', function () { + const checkMultiLineRangeSpy = jest.spyOn(systemUnderTest, 'checkMultiLineRange').mockImplementation() + const checkWordSpy = jest.spyOn(systemUnderTest, 'checkWord').mockImplementation() + const dummyWordRangeResult = {anchor: {line: 33, ch: 33}, head: {line: 33, ch: 34}} + const editor = jest.fn() + editor.findMarks = jest.fn() + editor.findWordAt = jest.fn(() => dummyWordRangeResult) + const dummyDictionary = jest.fn() + const changeObject = { + from: {line: 2, ch: 10}, + to: {line: 2, ch: 10}, + text: [ + ' ' + ] + } + + const expectedFrom = {line: 2, ch: 9} + + systemUnderTest.setDictionaryForTestsOnly(dummyDictionary) + systemUnderTest.liveSpellcheck(editor, changeObject) + + expect(checkMultiLineRangeSpy).not.toHaveBeenCalled() + expect(checkWordSpy).toHaveBeenCalledTimes(1) + expect(editor.findWordAt).toHaveBeenCalledWith(expectedFrom) + expect(checkWordSpy.mock.calls[0][0]).toEqual(editor) + expect(checkWordSpy.mock.calls[0][1]).toEqual(dummyWordRangeResult) + + checkWordSpy.mockRestore() + checkMultiLineRangeSpy.mockRestore() +}) + +it('should make sure that liveSpellcheck deletes the right marks when inserting a space', function () { + const checkMultiLineRangeSpy = jest.spyOn(systemUnderTest, 'checkMultiLineRange').mockImplementation() + const checkWordSpy = jest.spyOn(systemUnderTest, 'checkWord').mockImplementation() + const dummyWordRangeResult = {anchor: {line: 33, ch: 33}, head: {line: 33, ch: 34}} + const editor = jest.fn() + const dummyMarks = [ + {clear: jest.fn()}, + {clear: jest.fn()}, + {clear: jest.fn()} + ] + editor.findMarks = jest.fn(() => dummyMarks) + editor.findWordAt = jest.fn(() => dummyWordRangeResult) + const dummyDictionary = jest.fn() + const changeObject = { + from: {line: 2, ch: 10}, + to: {line: 2, ch: 10}, + text: [ + ' ' + ] + } + + systemUnderTest.setDictionaryForTestsOnly(dummyDictionary) + systemUnderTest.liveSpellcheck(editor, changeObject) + + expect(checkMultiLineRangeSpy).not.toHaveBeenCalled() + expect(checkWordSpy).toHaveBeenCalledTimes(1) + expect(editor.findMarks).toHaveBeenCalledWith(dummyWordRangeResult.anchor, dummyWordRangeResult.head) + for (const dummyMark of dummyMarks) { + expect(dummyMark.clear).toHaveBeenCalled() + } + + checkWordSpy.mockRestore() + checkMultiLineRangeSpy.mockRestore() +}) + +it('should make sure that liveSpellcheck works when a character is replaced', function () { + const checkMultiLineRangeSpy = jest.spyOn(systemUnderTest, 'checkMultiLineRange').mockImplementation() + const checkWordSpy = jest.spyOn(systemUnderTest, 'checkWord').mockImplementation() + const dummyWordRangeResult = {anchor: {line: 33, ch: 33}, head: {line: 33, ch: 34}} + const editor = jest.fn() + editor.findMarks = jest.fn() + editor.findWordAt = jest.fn(() => dummyWordRangeResult) + const dummyDictionary = jest.fn() + const changeObject = { + from: {line: 2, ch: 9}, + to: {line: 2, ch: 8}, + text: [ + 'a' + ] + } + + const expectedFrom = {line: 2, ch: 8} + + systemUnderTest.setDictionaryForTestsOnly(dummyDictionary) + systemUnderTest.liveSpellcheck(editor, changeObject) + + expect(checkMultiLineRangeSpy).not.toHaveBeenCalled() + expect(checkWordSpy).toHaveBeenCalledTimes(1) + expect(editor.findWordAt).toHaveBeenCalledWith(expectedFrom) + expect(checkWordSpy.mock.calls[0][0]).toEqual(editor) + expect(checkWordSpy.mock.calls[0][1]).toEqual(dummyWordRangeResult) + + checkWordSpy.mockRestore() + checkMultiLineRangeSpy.mockRestore() +}) + +it('should make sure that liveSpellcheck deletes the right marks when a character is replaced', function () { + const checkMultiLineRangeSpy = jest.spyOn(systemUnderTest, 'checkMultiLineRange').mockImplementation() + const checkWordSpy = jest.spyOn(systemUnderTest, 'checkWord').mockImplementation() + const dummyWordRangeResult = {anchor: {line: 33, ch: 33}, head: {line: 33, ch: 34}} + const editor = jest.fn() + const dummyMarks = [ + {clear: jest.fn()}, + {clear: jest.fn()}, + {clear: jest.fn()} + ] + editor.findMarks = jest.fn(() => dummyMarks) + editor.findWordAt = jest.fn(() => dummyWordRangeResult) + const dummyDictionary = jest.fn() + const changeObject = { + from: {line: 2, ch: 9}, + to: {line: 2, ch: 8}, + text: [ + 'b' + ] + } + + systemUnderTest.setDictionaryForTestsOnly(dummyDictionary) + systemUnderTest.liveSpellcheck(editor, changeObject) + + expect(checkMultiLineRangeSpy).not.toHaveBeenCalled() + expect(checkWordSpy).toHaveBeenCalledTimes(1) + expect(editor.findMarks).toHaveBeenCalledWith(dummyWordRangeResult.anchor, dummyWordRangeResult.head) + for (const dummyMark of dummyMarks) { + expect(dummyMark.clear).toHaveBeenCalled() + } + + checkWordSpy.mockRestore() + checkMultiLineRangeSpy.mockRestore() })