Remove spell check split tests (#21382)

GitOrigin-RevId: 819fc94f55dc1d73e4f58e36dd594a5019c68439
This commit is contained in:
Alf Eaton 2024-10-30 09:15:04 +00:00 committed by Copybot
parent bb465adba1
commit ea5b521882
31 changed files with 46 additions and 274 deletions

View file

@ -14,7 +14,6 @@ const DocstoreManager = require('../Docstore/DocstoreManager')
const logger = require('@overleaf/logger')
const { expressify } = require('@overleaf/promise-utils')
const Settings = require('@overleaf/settings')
const SplitTestHandler = require('../SplitTests/SplitTestHandler')
module.exports = {
joinProject: expressify(joinProject),
@ -57,33 +56,9 @@ async function joinProject(req, res, next) {
await ProjectDeleter.promises.unmarkAsDeletedByExternalSource(projectId)
}
let spellCheckClient
try {
const assignment = await SplitTestHandler.promises.getAssignmentForUser(
userId,
'spell-check-client'
)
spellCheckClient = assignment?.variant === 'enabled'
} catch {
spellCheckClient = false
}
let spellCheckNoServer
try {
const assignment = await SplitTestHandler.promises.getAssignmentForUser(
userId,
'spell-check-no-server'
)
spellCheckNoServer = assignment?.variant === 'enabled'
} catch {
spellCheckNoServer = false
}
if (project.spellCheckLanguage) {
project.spellCheckLanguage = await chooseSpellCheckLanguage(
project.spellCheckLanguage,
spellCheckClient,
spellCheckNoServer
project.spellCheckLanguage
)
}
@ -289,43 +264,20 @@ async function deleteEntity(req, res, next) {
res.sendStatus(204)
}
async function chooseSpellCheckLanguage(
spellCheckLanguage,
spellCheckClient = false,
spellCheckNoServer = false
) {
const supportedSpellCheckLanguages = new Set(
const supportedSpellCheckLanguages = new Set(
Settings.languages
// optionally only include languages which support client-side spell checking
.filter(language => {
if (!spellCheckClient) {
// only include spell-check languages that are available on the server
return language.server !== false
}
if (spellCheckNoServer) {
// only include spell-check languages that are available in the client
return language.dic !== undefined
}
return true
})
.filter(language => language.dic !== undefined)
.map(language => language.code)
)
)
async function chooseSpellCheckLanguage(spellCheckLanguage) {
if (supportedSpellCheckLanguages.has(spellCheckLanguage)) {
return spellCheckLanguage
}
// Disable spell checking for currently unsupported spell check languages.
// Preserve the value in the database so they can use it again once we add back support.
// existing behaviour: map all unsupported languages to "off"
if (!spellCheckNoServer) {
return ''
}
// new behaviour: map some server-only languages to a specific variant
// Map some server-only languages to a specific variant, or disable spell checking for currently unsupported spell check languages.
switch (spellCheckLanguage) {
case 'en':
// map "English" to "English (American)"

View file

@ -348,8 +348,6 @@ const _ProjectController = {
'write-and-cite-ars',
'default-visual-for-beginners',
'hotjar',
'spell-check-client',
'spell-check-no-server',
].filter(Boolean)
const getUserValues = async userId =>

View file

@ -5,7 +5,6 @@ import {
interceptDeferredCompile,
} from './compile'
import { interceptEvents } from './events'
import { interceptSpelling } from './spelling'
import { interceptAsync } from './intercept-async'
import { interceptFileUpload } from './upload'
import { interceptProjectListing } from './project-list'
@ -24,7 +23,6 @@ declare global {
interceptCompile: typeof interceptCompile
interceptEvents: typeof interceptEvents
interceptMetadata: typeof interceptMetadata
interceptSpelling: typeof interceptSpelling
waitForCompile: typeof waitForCompile
interceptDeferredCompile: typeof interceptDeferredCompile
interceptFileUpload: typeof interceptFileUpload
@ -40,7 +38,6 @@ Cypress.Commands.add('interceptAsync', interceptAsync)
Cypress.Commands.add('interceptCompile', interceptCompile)
Cypress.Commands.add('interceptEvents', interceptEvents)
Cypress.Commands.add('interceptMetadata', interceptMetadata)
Cypress.Commands.add('interceptSpelling', interceptSpelling)
Cypress.Commands.add('waitForCompile', waitForCompile)
Cypress.Commands.add('interceptDeferredCompile', interceptDeferredCompile)
Cypress.Commands.add('interceptFileUpload', interceptFileUpload)

View file

@ -1,3 +0,0 @@
export const interceptSpelling = () => {
cy.intercept('POST', '/spelling/check', [])
}

View file

@ -4,34 +4,19 @@ import getMeta from '../../../../utils/meta'
import { useProjectSettingsContext } from '../../context/project-settings-context'
import SettingsMenuSelect from './settings-menu-select'
import type { Optgroup } from './settings-menu-select'
import { useFeatureFlag } from '@/shared/context/split-test-context'
import { useEditorContext } from '@/shared/context/editor-context'
export default function SettingsSpellCheckLanguage() {
const { t } = useTranslation()
const languages = getMeta('ol-languages')
const spellCheckClientEnabled = useFeatureFlag('spell-check-client')
const spellCheckNoServer = useFeatureFlag('spell-check-no-server')
const { spellCheckLanguage, setSpellCheckLanguage } =
useProjectSettingsContext()
const { permissionsLevel } = useEditorContext()
const optgroup: Optgroup = useMemo(() => {
const options = (languages ?? []).filter(language => {
if (!spellCheckClientEnabled) {
// only include spell-check languages that are available on the server
return language.server !== false
}
if (spellCheckNoServer) {
const options = (getMeta('ol-languages') ?? [])
// only include spell-check languages that are available in the client
return language.dic !== undefined
}
return true
})
.filter(language => language.dic !== undefined)
return {
label: 'Language',
@ -40,7 +25,7 @@ export default function SettingsSpellCheckLanguage() {
label: language.name,
})),
}
}, [languages, spellCheckClientEnabled, spellCheckNoServer])
}, [])
return (
<SettingsMenuSelect

View file

@ -1,35 +1,13 @@
import { postJSON } from '../../../../infrastructure/fetch-json'
import { postJSON } from '@/infrastructure/fetch-json'
import { Word } from './spellchecker'
const apiUrl = (path: string) => {
return `/spelling${path}`
}
export async function learnWordRequest(word?: Word) {
if (!word || !word.text) {
throw new Error(`Invalid word supplied: ${word}`)
}
return await postJSON(apiUrl('/learn'), {
return await postJSON('/spelling/learn', {
body: {
word: word.text,
},
})
}
export function spellCheckRequest(
language: string,
words: Word[],
controller: AbortController
) {
const signal = controller.signal
const textWords = words.map(w => w.text)
return postJSON<{
misspellings: { index: number; suggestions: string[] }[]
}>(apiUrl('/check'), {
body: {
language,
words: textWords,
},
signal,
})
}

View file

@ -3,7 +3,6 @@ import { ignoredWordsField, resetSpellChecker } from './ignored-words'
import { cacheField, addWordToCache, WordCacheValue } from './cache'
import { WORD_REGEX } from './helpers'
import OError from '@overleaf/o-error'
import { spellCheckRequest } from './backend'
import { EditorView, ViewUpdate } from '@codemirror/view'
import { ChangeSet, Line, Range, RangeValue } from '@codemirror/state'
import { IgnoredWords } from '../../../dictionary/ignored-words'
@ -158,16 +157,6 @@ export class SpellChecker {
}
}
)
} else {
spellCheckRequest(this.language, unknownWords, this.abortController)
.then(result => {
this.abortController = null
return processResult(result.misspellings)
})
.catch(error => {
this.abortController = null
debugConsole.error(error)
})
}
}
}

View file

@ -1,7 +1,5 @@
import { FC, memo } from 'react'
import { useTranslation } from 'react-i18next'
import { useSplitTest } from '@/shared/context/split-test-context'
import { chooseBadgeClass } from '@/shared/components/beta-badge'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import classnames from 'classnames'
@ -9,46 +7,36 @@ import MaterialIcon from '@/shared/components/material-icon'
const SpellingSuggestionsFeedback: FC = () => {
const { t } = useTranslation()
const { info } = useSplitTest('spell-check-client')
if (!info) {
return null
}
const { tooltipText, url } = info.badgeInfo ?? {}
const badgeClass = chooseBadgeClass(info.phase)
return (
<OLTooltip
id="spell-check-client-tooltip"
description={
tooltipText || (
<>
We are testing an updated spellchecker.
The spell-checker has been updated.
<br />
Click to give feedback
</>
)
}
tooltipProps={{ className: 'split-test-badge-tooltip' }}
overlayProps={{ placement: 'bottom', delay: 100 }}
>
<a
href={url || '/beta/participate'}
href="https://docs.google.com/forms/d/e/1FAIpQLSdD1wa5SiCZ7x_UF6e8vywTN82kSm6ou2rTKz-XBiEjNilOXQ/viewform"
target="_blank"
rel="noopener noreferrer"
>
<BootstrapVersionSwitcher
bs3={
<span
className={classnames('badge', badgeClass)}
className={classnames('badge', 'info-badge')}
style={{ width: 14, height: 14 }}
/>
}
bs5={
<MaterialIcon
type="info"
className={classnames('align-middle', badgeClass)}
className={classnames('align-middle', 'info-badge')}
/>
}
/>

View file

@ -10,7 +10,6 @@ import { SpellChecker, Word } from './spellchecker'
import { useTranslation } from 'react-i18next'
import getMeta from '@/utils/meta'
import classnames from 'classnames'
import { useFeatureFlag } from '@/shared/context/split-test-context'
import { sendMB } from '@/infrastructure/event-tracking'
import SpellingSuggestionsFeedback from './spelling-suggestions-feedback'
import { SpellingSuggestionsLanguage } from './spelling-suggestions-language'
@ -76,14 +75,12 @@ export const SpellingSuggestions: FC<{
const language = useMemo(() => {
if (spellCheckLanguage) {
return getMeta('ol-languages').find(
return (getMeta('ol-languages') ?? []).find(
item => item.code === spellCheckLanguage
)
}
}, [spellCheckLanguage])
const spellCheckClientEnabled = useFeatureFlag('spell-check-client')
if (!language) {
return null
}
@ -146,7 +143,7 @@ export const SpellingSuggestions: FC<{
/>
</li>
{spellCheckClientEnabled && language.dic && (
{getMeta('ol-isSaas') && (
<>
<li className="divider" />
<li role="menuitem">

View file

@ -1,4 +1,3 @@
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
import { useEffect, useState } from 'react'
import getMeta from '@/utils/meta'
import { globalLearnedWords } from '@/features/dictionary/ignored-words'
@ -9,10 +8,10 @@ export const useHunspell = (spellCheckLanguage: string | null) => {
const [hunspellManager, setHunspellManager] = useState<HunspellManager>()
useEffect(() => {
if (isSplitTestEnabled('spell-check-client')) {
if (spellCheckLanguage) {
const languages = getMeta('ol-languages')
const lang = languages.find(item => item.code === spellCheckLanguage)
const lang = (getMeta('ol-languages') ?? []).find(
item => item.code === spellCheckLanguage
)
if (lang?.dic) {
const hunspellManager = new HunspellManager(lang.dic, [
...globalLearnedWords,
@ -28,7 +27,6 @@ export const useHunspell = (spellCheckLanguage: string | null) => {
setHunspellManager(undefined)
}
}
}
}, [spellCheckLanguage])
return hunspellManager

View file

@ -441,10 +441,12 @@ describe('<EditorLeftMenu />', function () {
{
name: 'Lang 1',
code: 'lang-1',
dic: 'lang_1',
},
{
name: 'Lang 2',
code: 'lang-2',
dic: 'lang_2',
},
]

View file

@ -10,10 +10,12 @@ describe('<SettingsSpellCheckLanguage />', function () {
{
name: 'Lang 1',
code: 'lang-1',
dic: 'lang_1',
},
{
name: 'Lang 2',
code: 'lang-2',
dic: 'lang_2',
},
]

View file

@ -9,7 +9,6 @@ describe('<ReviewPanel />', function () {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
cy.interceptEvents()
cy.interceptSpelling()
const scope = mockScope('')
scope.editor.showVisual = true

View file

@ -16,7 +16,6 @@ describe('autocomplete', { scrollBehavior: false }, function () {
window.metaAttributesCache.set('ol-showSymbolPalette', true)
cy.interceptEvents()
cy.interceptMetadata()
cy.interceptSpelling()
})
it('opens autocomplete on matched text', function () {

View file

@ -8,7 +8,6 @@ describe('close brackets', { scrollBehavior: false }, function () {
beforeEach(function () {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
cy.interceptEvents()
cy.interceptSpelling()
const scope = mockScope()

View file

@ -21,7 +21,6 @@ ${'long line '.repeat(200)}`
beforeEach(function () {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
cy.interceptEvents()
cy.interceptSpelling()
const scope = mockScope(content)

View file

@ -74,7 +74,7 @@ describe('<FigureModal />', function () {
cy.interceptMathJax()
cy.interceptEvents()
cy.interceptMetadata()
cy.interceptSpelling()
mount()
})

View file

@ -11,7 +11,6 @@ test
beforeEach(function () {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
cy.interceptEvents()
cy.interceptSpelling()
const scope = mockScope(content)

View file

@ -14,7 +14,6 @@ describe('keyboard shortcuts', { scrollBehavior: false }, function () {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
cy.interceptEvents()
cy.interceptMetadata()
cy.interceptSpelling()
const scope = mockScope()
@ -127,7 +126,6 @@ describe('emacs keybindings', { scrollBehavior: false }, function () {
beforeEach(function () {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
cy.interceptEvents()
cy.interceptSpelling()
const shortDoc = `
\\documentclass{article}
@ -229,7 +227,6 @@ describe('vim keybindings', { scrollBehavior: false }, function () {
beforeEach(function () {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
cy.interceptEvents()
cy.interceptSpelling()
// Make a short doc that will fit entirely into the dom tree, so that
// index() corresponds to line number - 1

View file

@ -114,14 +114,6 @@ forEach(Object.keys(suggestions)).describe(
beforeEach(function () {
cy.window().then(win => {
win.metaAttributesCache.set('ol-preventCompileOnLoad', true)
win.metaAttributesCache.set('ol-splitTestVariants', {
'spell-check-client': 'enabled',
'spell-check-no-server': 'enabled',
})
win.metaAttributesCache.set('ol-splitTestInfo', {
'spell-check-client': { phase: 'release' },
'spell-check-no-server': { phase: 'release' },
})
win.metaAttributesCache.set('ol-learnedWords', ['baz'])
win.metaAttributesCache.set(
'ol-dictionariesRoot',

View file

@ -1,86 +0,0 @@
import '../../../helpers/bootstrap-3'
import { mockScope } from '../helpers/mock-scope'
import { EditorProviders } from '../../../helpers/editor-providers'
import CodeMirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
import { TestContainer } from '../helpers/test-container'
describe('Spellchecker', function () {
const content = `
\\documentclass{}
\\title{}
\\author{}
\\begin{document}
\\maketitle
\\begin{abstract}
\\end{abstract}
\\section{}
\\end{document}`
beforeEach(function () {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
cy.interceptEvents()
const scope = mockScope(content)
cy.mount(
<TestContainer>
<EditorProviders scope={scope}>
<CodeMirrorEditor />
</EditorProviders>
</TestContainer>
)
cy.get('.cm-line').eq(13).as('line')
cy.get('@line').click()
})
it('makes initial spellcheck request', function () {
cy.intercept('POST', '/spelling/check').as('spellCheckRequest')
cy.get('@line').type('wombat')
cy.wait('@spellCheckRequest')
})
it('makes only one spellcheck request for multiple typed characters', function () {
let spellCheckRequestCount = 0
cy.intercept('POST', '/spelling/check', req => {
++spellCheckRequestCount
if (spellCheckRequestCount > 1) {
throw new Error('No more than one request was expected')
}
req.reply({
misspellings: [],
})
}).as('spellCheckRequest')
cy.get('@line').type('wombat')
cy.wait('@spellCheckRequest')
})
it('shows red underline for misspelled word', function () {
cy.intercept('POST', '/spelling/check', {
misspellings: [
{
index: 0,
suggestions: [
'noncombat',
'wombat',
'nutmeat',
'nitwit',
'steamboat',
'entombed',
'tombed',
],
},
],
}).as('spellCheckRequest')
cy.get('@line').type('notawombat')
cy.wait('@spellCheckRequest')
cy.get('@line').get('.ol-cm-spelling-error').contains('notawombat')
})
})

View file

@ -107,7 +107,7 @@ function checkBordersWithNoMultiColumn(
describe('<CodeMirrorEditor/> Table editor', function () {
beforeEach(function () {
cy.interceptEvents()
cy.interceptSpelling()
cy.interceptMathJax()
cy.interceptCompile('compile', Number.MAX_SAFE_INTEGER)
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)

View file

@ -25,7 +25,6 @@ describe('<CodeMirrorEditor/> command tooltip in Visual mode', function () {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
cy.interceptEvents()
cy.interceptMetadata()
cy.interceptSpelling()
})
it('shows a tooltip for \\href', function () {

View file

@ -24,7 +24,6 @@ describe('<CodeMirrorEditor/> floats', function () {
beforeEach(function () {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
cy.interceptEvents()
cy.interceptSpelling()
})
it('decorates a caption', function () {

View file

@ -26,7 +26,6 @@ describe('<CodeMirrorEditor/> lists in Rich Text mode', function () {
beforeEach(function () {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
cy.interceptEvents()
cy.interceptSpelling()
})
it('creates a nested list inside an unindented list', function () {

View file

@ -25,7 +25,6 @@ describe('<CodeMirrorEditor/> paste HTML in Visual mode', function () {
beforeEach(function () {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
cy.interceptEvents()
cy.interceptSpelling()
})
it('handles paste', function () {

View file

@ -62,7 +62,6 @@ describe('<CodeMirrorEditor/> in Visual mode with read-only permission', functio
cy.interceptMathJax()
cy.interceptEvents()
cy.interceptMetadata()
cy.interceptSpelling()
})
it('decorates footnote content', function () {

View file

@ -39,7 +39,6 @@ describe('<CodeMirrorEditor/> toolbar in Rich Text mode', function () {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
cy.interceptEvents()
cy.interceptMetadata()
cy.interceptSpelling()
})
it('should handle Undo and Redo', function () {

View file

@ -10,7 +10,6 @@ describe('<CodeMirrorEditor/> tooltips in Visual mode', function () {
cy.interceptMathJax()
cy.interceptMetadata()
cy.interceptEvents()
cy.interceptSpelling()
const scope = mockScope('\n\n\n')
scope.editor.showVisual = true

View file

@ -14,7 +14,6 @@ describe('<CodeMirrorEditor/> in Visual mode', function () {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
cy.interceptEvents()
cy.interceptMetadata()
cy.interceptSpelling()
cy.interceptMathJax()
// 3 blank lines

View file

@ -12,7 +12,6 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
beforeEach(function () {
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
cy.interceptEvents()
cy.interceptSpelling()
})
it('deletes selected text on Backspace', function () {