mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-15 14:43:46 +01:00
Ensure that the correct lines are spell-checked (#19967)
GitOrigin-RevId: f63601288821d37bddcddc776270263e291d3985
This commit is contained in:
-206
@@ -1,206 +0,0 @@
|
||||
import { LineTracker } from '../../../../../../frontend/js/features/source-editor/extensions/spelling/line-tracker'
|
||||
// import sinon from 'sinon'
|
||||
import { expect } from 'chai'
|
||||
import { EditorView } from '@codemirror/view'
|
||||
import { EditorState } from '@codemirror/state'
|
||||
import { Word } from '../../../../../../frontend/js/features/source-editor/extensions/spelling/spellchecker'
|
||||
|
||||
const doc = [
|
||||
'Hello test one two',
|
||||
'three four five six',
|
||||
'seven eight nine.',
|
||||
].join('\n')
|
||||
|
||||
describe('LineTracker', function () {
|
||||
describe('basic operations', function () {
|
||||
let state: EditorState,
|
||||
view: EditorView,
|
||||
lineTracker: LineTracker,
|
||||
check: (spec: [number, any][]) => void
|
||||
beforeEach(function () {
|
||||
state = EditorState.create({
|
||||
doc,
|
||||
extensions: [
|
||||
EditorView.updateListener.of(update => {
|
||||
lineTracker.applyUpdate(update)
|
||||
}),
|
||||
],
|
||||
})
|
||||
view = new EditorView({ state })
|
||||
lineTracker = new LineTracker(view.state.doc)
|
||||
check = spec => {
|
||||
spec.forEach(([n, expectedValue]) => {
|
||||
expect(lineTracker.lineHasChanged(n)).to.equal(expectedValue)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('start with the correct number of lines', function () {
|
||||
expect(state.doc.lines).to.equal(3)
|
||||
expect(lineTracker.count()).to.equal(state.doc.lines)
|
||||
})
|
||||
|
||||
it('starts with all lines marked as changed', function () {
|
||||
expect(state.doc.lines).to.equal(3)
|
||||
check([
|
||||
[1, true],
|
||||
[2, true],
|
||||
[3, true],
|
||||
])
|
||||
})
|
||||
|
||||
it('clears a line', function () {
|
||||
check([[1, true]])
|
||||
lineTracker.clearLine(1)
|
||||
check([[1, false]])
|
||||
})
|
||||
|
||||
it('clears lines based on a list of words', function () {
|
||||
lineTracker.clearChangedLinesForWords([
|
||||
{ lineNumber: 1 },
|
||||
{ lineNumber: 3 },
|
||||
] as Word[])
|
||||
check([
|
||||
[1, false],
|
||||
[2, true],
|
||||
[3, false],
|
||||
])
|
||||
})
|
||||
|
||||
it('should update lines in response to text insertion', function () {
|
||||
lineTracker.clearChangedLinesForWords([
|
||||
{ lineNumber: 1 },
|
||||
{ lineNumber: 2 },
|
||||
{ lineNumber: 3 },
|
||||
] as Word[])
|
||||
check([
|
||||
[1, false],
|
||||
[2, false],
|
||||
[3, false],
|
||||
])
|
||||
|
||||
let transaction = view.state.update({
|
||||
changes: [{ from: 0, insert: 'x' }],
|
||||
})
|
||||
view.dispatch(transaction)
|
||||
check([
|
||||
[1, true],
|
||||
[2, false],
|
||||
[3, false],
|
||||
])
|
||||
|
||||
transaction = view.state.update({
|
||||
changes: [{ from: view.state.doc.length - 2, insert: 'x' }],
|
||||
})
|
||||
view.dispatch(transaction)
|
||||
check([
|
||||
[1, true],
|
||||
[2, false],
|
||||
[3, true],
|
||||
])
|
||||
})
|
||||
|
||||
it('should update lines in response to large text insertion', function () {
|
||||
lineTracker.clearChangedLinesForWords([
|
||||
{ lineNumber: 1 },
|
||||
{ lineNumber: 2 },
|
||||
{ lineNumber: 3 },
|
||||
] as Word[])
|
||||
check([
|
||||
[1, false],
|
||||
[2, false],
|
||||
[3, false],
|
||||
])
|
||||
|
||||
const text = new Array(1000).fill('x').join('\n')
|
||||
|
||||
const transaction = view.state.update({
|
||||
changes: [{ from: 0, insert: text }],
|
||||
})
|
||||
view.dispatch(transaction)
|
||||
expect(lineTracker.count()).to.equal(1002)
|
||||
const expectations: [number, boolean][] = []
|
||||
for (let i = 1; i <= 1000; i++) {
|
||||
expectations.push([i, true])
|
||||
}
|
||||
expectations.push([1001, false])
|
||||
expectations.push([1002, false])
|
||||
check(expectations)
|
||||
})
|
||||
|
||||
it('should update lines in response to removal of a line', function () {
|
||||
lineTracker.clearChangedLinesForWords([
|
||||
{ lineNumber: 1 },
|
||||
{ lineNumber: 2 },
|
||||
{ lineNumber: 3 },
|
||||
] as Word[])
|
||||
check([
|
||||
[1, false],
|
||||
[2, false],
|
||||
[3, false],
|
||||
])
|
||||
|
||||
// Overwrite the line plus some part of the second line
|
||||
const transaction = view.state.update({
|
||||
changes: [{ from: 0, to: doc[0].length + 3, insert: 'x' }],
|
||||
})
|
||||
view.dispatch(transaction)
|
||||
check([
|
||||
[1, true],
|
||||
[2, false],
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle multiple changes', function () {
|
||||
lineTracker.clearChangedLinesForWords([
|
||||
{ lineNumber: 1 },
|
||||
{ lineNumber: 2 },
|
||||
{ lineNumber: 3 },
|
||||
] as Word[])
|
||||
check([
|
||||
[1, false],
|
||||
[2, false],
|
||||
[3, false],
|
||||
])
|
||||
|
||||
const transaction = view.state.update({
|
||||
changes: [
|
||||
{ from: 0, insert: 'x' },
|
||||
{ from: doc[0].length + 2, insert: 'xxxxx\nxxxxx\nxxxx' },
|
||||
],
|
||||
})
|
||||
view.dispatch(transaction)
|
||||
check([
|
||||
[1, true],
|
||||
[2, true],
|
||||
[3, true],
|
||||
[4, false],
|
||||
[5, false],
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle multiple deletions', function () {
|
||||
const transaction = view.state.update({
|
||||
changes: [
|
||||
{ from: 0, to: 24, insert: '' },
|
||||
{ from: 39, to: 44, insert: '' },
|
||||
],
|
||||
})
|
||||
view.dispatch(transaction)
|
||||
check([
|
||||
[1, true],
|
||||
[2, true],
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle multiple insertions', function () {
|
||||
const transactionUndo = view.state.update({
|
||||
changes: [
|
||||
{ from: 0, to: 0, insert: 'big change\n'.repeat(20) },
|
||||
{ from: 50, to: 50, insert: 'xxxx' },
|
||||
],
|
||||
})
|
||||
view.dispatch(transactionUndo)
|
||||
})
|
||||
})
|
||||
})
|
||||
+24
-134
@@ -1,29 +1,15 @@
|
||||
import {
|
||||
getWordsFromLine,
|
||||
viewportLinesToCheck,
|
||||
buildSpellCheckResult,
|
||||
Word,
|
||||
} from '../../../../../../frontend/js/features/source-editor/extensions/spelling/spellchecker'
|
||||
import { LineTracker } from '../../../../../../frontend/js/features/source-editor/extensions/spelling/line-tracker'
|
||||
} from '@/features/source-editor/extensions/spelling/spellchecker'
|
||||
import { expect } from 'chai'
|
||||
import { EditorView } from '@codemirror/view'
|
||||
import { EditorState, Line } from '@codemirror/state'
|
||||
import _ from 'lodash'
|
||||
import { IgnoredWords } from '../../../../../../frontend/js/features/dictionary/ignored-words'
|
||||
import { LaTeXLanguage } from '../../../../../../frontend/js/features/source-editor/languages/latex/latex-language'
|
||||
import { IgnoredWords } from '@/features/dictionary/ignored-words'
|
||||
import { LaTeXLanguage } from '@/features/source-editor/languages/latex/latex-language'
|
||||
import { LanguageSupport } from '@codemirror/language'
|
||||
|
||||
const latex = new LanguageSupport(LaTeXLanguage)
|
||||
|
||||
const makeView = (text: string) => {
|
||||
const view = new EditorView({
|
||||
state: EditorState.create({
|
||||
doc: text,
|
||||
extensions: [latex],
|
||||
}),
|
||||
})
|
||||
return view
|
||||
}
|
||||
const extensions = [new LanguageSupport(LaTeXLanguage)]
|
||||
|
||||
describe('SpellChecker', function () {
|
||||
describe('getWordsFromLine', function () {
|
||||
@@ -35,7 +21,10 @@ describe('SpellChecker', function () {
|
||||
})
|
||||
|
||||
it('should get words from a line', function () {
|
||||
const view = makeView('Hello test one two')
|
||||
const view = new EditorView({
|
||||
doc: 'Hello test one two',
|
||||
extensions,
|
||||
})
|
||||
const line = view.state.doc.line(1)
|
||||
const words = getWordsFromLine(view, line, ignoredWords, lang)
|
||||
expect(words).to.deep.equal([
|
||||
@@ -48,7 +37,10 @@ describe('SpellChecker', function () {
|
||||
|
||||
it('should ignore words in ignoredWords', function () {
|
||||
ignoredWords = new Set(['test']) as unknown as IgnoredWords
|
||||
const view = makeView('Hello test one two')
|
||||
const view = new EditorView({
|
||||
doc: 'Hello test one two',
|
||||
extensions,
|
||||
})
|
||||
const line = view.state.doc.line(1)
|
||||
const words = getWordsFromLine(view, line, ignoredWords, lang)
|
||||
expect(words).to.deep.equal([
|
||||
@@ -59,14 +51,20 @@ describe('SpellChecker', function () {
|
||||
})
|
||||
|
||||
it('should get no words from an empty line', function () {
|
||||
const view = makeView(' ')
|
||||
const view = new EditorView({
|
||||
doc: ' ',
|
||||
extensions,
|
||||
})
|
||||
const line = view.state.doc.line(1)
|
||||
const words = getWordsFromLine(view, line, ignoredWords, lang)
|
||||
expect(words).to.deep.equal([])
|
||||
})
|
||||
|
||||
it('should ignore content of some commands in the text', function () {
|
||||
const view = makeView('\\usepackage[foo]{ bar } seven eight')
|
||||
const view = new EditorView({
|
||||
doc: '\\usepackage[foo]{ bar } seven eight',
|
||||
extensions,
|
||||
})
|
||||
const line = view.state.doc.line(1)
|
||||
const words = getWordsFromLine(view, line, ignoredWords, lang)
|
||||
expect(words).to.deep.equal([
|
||||
@@ -76,7 +74,10 @@ describe('SpellChecker', function () {
|
||||
})
|
||||
|
||||
it('should ignore command names in the text', function () {
|
||||
const view = makeView('\\foo nine \\bar ten \\baz[]{}')
|
||||
const view = new EditorView({
|
||||
doc: '\\foo nine \\bar ten \\baz[]{}',
|
||||
extensions,
|
||||
})
|
||||
const line = view.state.doc.line(1)
|
||||
const words = getWordsFromLine(view, line, ignoredWords, lang)
|
||||
expect(words).to.deep.equal([
|
||||
@@ -86,117 +87,6 @@ describe('SpellChecker', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('viewportLinesToCheck', function () {
|
||||
const expectLines = (lines: Line[], expectations: any) => {
|
||||
expect(lines.map(l => l.number)).to.deep.equal(expectations)
|
||||
}
|
||||
const expectLineRange = (lines: Line[], from: number, to: number) => {
|
||||
expect(lines.map(l => l.number)).to.deep.equal(_.range(from, to + 1))
|
||||
}
|
||||
|
||||
let view: EditorView
|
||||
beforeEach(function () {
|
||||
view = makeView(new Array(1000).fill('aa bb cc dd').join('\n'))
|
||||
// Test preconditions on these structures
|
||||
const viewport = view.viewport
|
||||
expect(view.state.doc.lines).to.equal(1000)
|
||||
const firstVisibleLine = view.state.doc.lineAt(viewport.from).number
|
||||
const lastVisibleLine = view.state.doc.lineAt(viewport.to).number
|
||||
expect(firstVisibleLine).to.equal(1)
|
||||
expect(lastVisibleLine).to.equal(36)
|
||||
})
|
||||
|
||||
describe('when all lines are changed', function () {
|
||||
let lineTracker: LineTracker
|
||||
beforeEach(function () {
|
||||
lineTracker = new LineTracker(view.state.doc)
|
||||
})
|
||||
|
||||
it('should check all lines in the viewport when not first check', function () {
|
||||
const firstCheck = false
|
||||
const linesToCheck = viewportLinesToCheck(lineTracker, firstCheck, view)
|
||||
expectLineRange(linesToCheck, 1, 36)
|
||||
})
|
||||
|
||||
it('should check more lines than the viewport on first check', function () {
|
||||
const firstCheck = true
|
||||
const lineTracker = new LineTracker(view.state.doc)
|
||||
const linesToCheck = viewportLinesToCheck(lineTracker, firstCheck, view)
|
||||
expectLineRange(linesToCheck, 1, 108)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when no lines have changed', function () {
|
||||
let lineTracker: LineTracker
|
||||
beforeEach(function () {
|
||||
lineTracker = new LineTracker(view.state.doc)
|
||||
lineTracker.clearAllLines()
|
||||
})
|
||||
|
||||
it('on first check, should not check any lines', function () {
|
||||
const firstCheck = true
|
||||
const linesToCheck = viewportLinesToCheck(lineTracker, firstCheck, view)
|
||||
expect(linesToCheck).to.deep.equal([])
|
||||
})
|
||||
|
||||
it('on not first check, should not check any lines', function () {
|
||||
const firstCheck = false
|
||||
const linesToCheck = viewportLinesToCheck(lineTracker, firstCheck, view)
|
||||
expectLines(linesToCheck, [])
|
||||
})
|
||||
})
|
||||
|
||||
describe('when some lines have changed in viewport', function () {
|
||||
let lineTracker: LineTracker
|
||||
beforeEach(function () {
|
||||
lineTracker = new LineTracker(view.state.doc)
|
||||
lineTracker.clearAllLines()
|
||||
lineTracker.markLineAsUpdated(3)
|
||||
lineTracker.markLineAsUpdated(7)
|
||||
})
|
||||
|
||||
it('should check correct lines', function () {
|
||||
const firstCheck = false
|
||||
const linesToCheck = viewportLinesToCheck(lineTracker, firstCheck, view)
|
||||
expectLines(linesToCheck, [3, 7])
|
||||
})
|
||||
})
|
||||
|
||||
describe('when some lines have changed outside viewport', function () {
|
||||
let lineTracker: LineTracker
|
||||
beforeEach(function () {
|
||||
lineTracker = new LineTracker(view.state.doc)
|
||||
lineTracker.clearAllLines()
|
||||
lineTracker.markLineAsUpdated(300)
|
||||
lineTracker.markLineAsUpdated(307)
|
||||
})
|
||||
|
||||
it('should not check lines', function () {
|
||||
const firstCheck = false
|
||||
const linesToCheck = viewportLinesToCheck(lineTracker, firstCheck, view)
|
||||
expectLines(linesToCheck, [])
|
||||
})
|
||||
})
|
||||
|
||||
describe('when some lines have changed inside and outside viewport', function () {
|
||||
let lineTracker: LineTracker
|
||||
beforeEach(function () {
|
||||
lineTracker = new LineTracker(view.state.doc)
|
||||
lineTracker.clearAllLines()
|
||||
lineTracker.markLineAsUpdated(10)
|
||||
lineTracker.markLineAsUpdated(12)
|
||||
lineTracker.markLineAsUpdated(300)
|
||||
lineTracker.markLineAsUpdated(307)
|
||||
})
|
||||
|
||||
it('should check only lines in viewport', function () {
|
||||
const firstCheck = false
|
||||
const linesToCheck = viewportLinesToCheck(lineTracker, firstCheck, view)
|
||||
expectLines(linesToCheck, [10, 12])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildSpellCheckResult', function () {
|
||||
it('should build an empty result', function () {
|
||||
const knownMisspelledWords: Word[] = []
|
||||
|
||||
Reference in New Issue
Block a user