Improve spell check when dictionary is edited (#22635)

GitOrigin-RevId: 20d36cb987d014809423240a46c7c577781dfde6
This commit is contained in:
Alf Eaton
2025-01-14 09:05:03 +00:00
committed by Copybot
parent 47af13c8a8
commit cffa9c1a28
16 changed files with 218 additions and 375 deletions
@@ -1,6 +1,6 @@
import { WordCache } from '../../../../../../frontend/js/features/source-editor/extensions/spelling/cache'
import { WordCache } from '@/features/source-editor/extensions/spelling/cache'
import { expect } from 'chai'
import { Word } from '../../../../../../frontend/js/features/source-editor/extensions/spelling/spellchecker'
import { Word } from '@/features/source-editor/extensions/spelling/spellchecker'
describe('WordCache', function () {
describe('basic operations', function () {
@@ -18,25 +18,25 @@ describe('WordCache', function () {
word = 'bar'
expect(cache.get(lang, word)).to.not.exist
cache.set(lang, word, ['a', 'b'])
expect(cache.get(lang, word)).to.deep.equal(['a', 'b'])
cache.set(lang, word, false)
expect(cache.get(lang, word)).to.equal(false)
})
it('should store words in separate languages', function () {
const word = 'foo'
const otherLang = 'zz'
cache.set(lang, word, 101)
expect(cache.get(lang, word)).to.equal(101)
cache.set(lang, word, true)
expect(cache.get(lang, word)).to.equal(true)
expect(cache.get(otherLang, word)).to.not.exist
cache.set(otherLang, word, 202)
expect(cache.get(lang, word)).to.equal(101)
expect(cache.get(otherLang, word)).to.equal(202)
cache.set(otherLang, word, false)
expect(cache.get(lang, word)).to.equal(true)
expect(cache.get(otherLang, word)).to.equal(false)
})
it('should check words against cache', function () {
cache.set(lang, 'foo', ['a', 'b'])
cache.set(lang, 'foo', false)
cache.set(lang, 'bar', true)
cache.set(lang, 'baz', true)
const wordsToCheck = [
@@ -49,8 +49,8 @@ describe('WordCache', function () {
const result = cache.checkWords(lang, wordsToCheck)
expect(result).to.have.keys('knownMisspelledWords', 'unknownWords')
expect(result.knownMisspelledWords).to.deep.equal([
{ text: 'foo', suggestions: ['a', 'b'], from: 0 },
{ text: 'foo', suggestions: ['a', 'b'], from: 3 },
{ text: 'foo', from: 0 },
{ text: 'foo', from: 3 },
])
expect(result.unknownWords).to.deep.equal([
{ text: 'quux', from: 2 },
@@ -5,7 +5,6 @@ import {
} from '@/features/source-editor/extensions/spelling/spellchecker'
import { expect } from 'chai'
import { EditorView } from '@codemirror/view'
import { IgnoredWords } from '@/features/dictionary/ignored-words'
import { LaTeXLanguage } from '@/features/source-editor/languages/latex/latex-language'
import { LanguageSupport } from '@codemirror/language'
@@ -13,11 +12,10 @@ const extensions = [new LanguageSupport(LaTeXLanguage)]
describe('SpellChecker', function () {
describe('getWordsFromLine', function () {
let lang: string, ignoredWords: IgnoredWords
let lang: string
beforeEach(function () {
/* Note: ignore the word 'test' */
lang = 'en'
ignoredWords = new Set([]) as unknown as IgnoredWords
})
it('should get words from a line', function () {
@@ -26,7 +24,7 @@ describe('SpellChecker', function () {
extensions,
})
const line = view.state.doc.line(1)
const words = Array.from(getWordsFromLine(view, line, ignoredWords, lang))
const words = Array.from(getWordsFromLine(view, line, lang))
expect(words).to.deep.equal([
{ text: 'Hello', from: 0, to: 5, lineNumber: 1, lang: 'en' },
{ text: 'test', from: 6, to: 10, lineNumber: 1, lang: 'en' },
@@ -35,28 +33,13 @@ describe('SpellChecker', function () {
])
})
it('should ignore words in ignoredWords', function () {
ignoredWords = new Set(['test']) as unknown as IgnoredWords
const view = new EditorView({
doc: 'Hello test one two',
extensions,
})
const line = view.state.doc.line(1)
const words = Array.from(getWordsFromLine(view, line, ignoredWords, lang))
expect(words).to.deep.equal([
{ text: 'Hello', from: 0, to: 5, lineNumber: 1, lang: 'en' },
{ text: 'one', from: 11, to: 14, lineNumber: 1, lang: 'en' },
{ text: 'two', from: 15, to: 18, lineNumber: 1, lang: 'en' },
])
})
it('should get no words from an empty line', function () {
const view = new EditorView({
doc: ' ',
extensions,
})
const line = view.state.doc.line(1)
const words = Array.from(getWordsFromLine(view, line, ignoredWords, lang))
const words = Array.from(getWordsFromLine(view, line, lang))
expect(words).to.deep.equal([])
})
@@ -66,7 +49,7 @@ describe('SpellChecker', function () {
extensions,
})
const line = view.state.doc.line(1)
const words = Array.from(getWordsFromLine(view, line, ignoredWords, lang))
const words = Array.from(getWordsFromLine(view, line, lang))
expect(words).to.deep.equal([
{ text: 'seven', from: 24, to: 29, lineNumber: 1, lang: 'en' },
{ text: 'eight', from: 30, to: 35, lineNumber: 1, lang: 'en' },
@@ -79,7 +62,7 @@ describe('SpellChecker', function () {
extensions,
})
const line = view.state.doc.line(1)
const words = Array.from(getWordsFromLine(view, line, ignoredWords, lang))
const words = Array.from(getWordsFromLine(view, line, lang))
expect(words).to.deep.equal([
{ text: 'nine', from: 5, to: 9, lineNumber: 1, lang: 'en' },
{ text: 'ten', from: 15, to: 18, lineNumber: 1, lang: 'en' },
@@ -91,7 +74,7 @@ describe('SpellChecker', function () {
it('should build an empty result', function () {
const knownMisspelledWords: Word[] = []
const unknownWords: Word[] = []
const misspellings: { index: number; suggestions: string[] }[] = []
const misspellings: { index: number }[] = []
const result = buildSpellCheckResult(
knownMisspelledWords,
unknownWords,
@@ -103,21 +86,17 @@ describe('SpellChecker', function () {
})
})
it('should build a realistic result', function () {
const _makeWord = (text: string, suggestions?: string[]) => {
const word = new Word({
const _makeWord = (text: string) => {
return new Word({
text,
from: 0,
to: 0,
lineNumber: 0,
lang: 'xx',
})
if (suggestions != null) {
word.suggestions = suggestions
}
return word
}
// We know this word is misspelled
const knownMisspelledWords = [_makeWord('fff', ['food', 'fleece'])]
const knownMisspelledWords = [_makeWord('fff')]
// These words we didn't know
const unknownWords = [
_makeWord('aaa'),
@@ -126,10 +105,7 @@ describe('SpellChecker', function () {
_makeWord('ddd'),
]
// These are the suggestions we got back from the backend
const misspellings = [
{ index: 1, suggestions: ['box', 'bass'] },
{ index: 3, suggestions: ['docs', 'dance'] },
]
const misspellings = [{ index: 1 }, { index: 3 }]
// Build the result structure
const result = buildSpellCheckResult(
knownMisspelledWords,
@@ -140,21 +116,19 @@ describe('SpellChecker', function () {
// Check cache additions
expect(result.cacheAdditions.map(([k, v]) => [k.text, v])).to.deep.equal([
// Put these in cache as known misspellings
['bbb', ['box', 'bass']],
['ddd', ['docs', 'dance']],
['bbb', false],
['ddd', false],
// Put these in cache as known-correct
['aaa', true],
['ccc', true],
])
// Check misspellings
expect(
result.misspelledWords.map(w => [w.text, w.suggestions])
).to.deep.equal([
expect(result.misspelledWords.map(w => w.text)).to.deep.equal([
// Words in the payload that we now know were misspelled
['bbb', ['box', 'bass']],
['ddd', ['docs', 'dance']],
'bbb',
'ddd',
// Word we already knew was misspelled, preserved here
['fff', ['food', 'fleece']],
'fff',
])
})
})