import { describe, it, expect } from 'vitest' import { combine, interleave, type Loaded } from './useSpellChecker' // The both-dictionaries rule (SUGGESTIONS.md §3a), separated from the fetching // so it can be checked without a 15 MB word list. What matters here is not // "does nspell work" but which way the combination is allowed to be wrong. // A dictionary that accepts exactly the words it was given. function dict(lang: string, words: string[], corrections: string[] = [], extendedAlphabet = false): Loaded { const set = new Set(words) return { lang, extendedAlphabet, spell: { correct: (w: string) => set.has(w), suggest: () => corrections, add: (w: string) => set.add(w), }, } } const en = dict('en', ['sale', 'the', 'river'], ['sailed', 'salt']) const pt = dict('pt-PT', ['sale', 'coração', 'jardim'], ['salte', 'sala'], true) describe('the both-dictionaries rule', () => { it('accepts a word either dictionary knows', () => { const c = combine(() => [en, pt]) expect(c.correct('river')).toBe(true) // English only expect(c.correct('jardim')).toBe(true) // Portuguese only expect(c.correct('sale')).toBe(true) // both — the collision case }) it('flags only what every dictionary rejects', () => { expect(combine(() => [en, pt]).correct('qqzzx')).toBe(false) }) it('never flags a Portuguese word just because English has not heard of it', () => { // The property the whole design exists for. With English alone, "coração" // is a misspelling; with her own dictionary loaded it is a word she wrote. expect(combine(() => [en]).correct('coração')).toBe(false) expect(combine(() => [en, pt]).correct('coração')).toBe(true) }) it('accepts everything when no dictionary loaded', () => { // A failed fetch must not underline every word in the document. Silence is // the safe failure; a page of red is not. expect(combine(() => []).correct('qqzzx')).toBe(true) }) it('sees a dictionary that arrives after the checker was built', () => { // English loads immediately; hers lands a moment later, once /api/me has // named her pair. The checker reads through a getter for exactly this. let loaded: Loaded[] = [en] const c = combine(() => loaded) expect(c.correct('jardim')).toBe(false) expect(c.extendedAlphabet).toBe(false) loaded = [en, pt] expect(c.correct('jardim')).toBe(true) expect(c.extendedAlphabet).toBe(true) }) }) describe('correction pills', () => { it('interleaves the two dictionaries rather than letting one fill the list', () => { // Five pills fit. Concatenating would spend all of them on English and // leave a misspelt Portuguese word with no Portuguese correction — the one // case the second dictionary was loaded for. expect(combine(() => [en, pt]).suggest('salle')).toEqual(['sailed', 'salte', 'salt', 'sala']) }) it('drops duplicates, keeping the first dictionary to offer one', () => { expect(interleave([['a', 'b'], ['a', 'c']])).toEqual(['a', 'b', 'c']) }) it('keeps going when one dictionary runs out of ideas', () => { expect(interleave([['a'], ['x', 'y', 'z']])).toEqual(['a', 'x', 'y', 'z']) expect(interleave([[], []])).toEqual([]) expect(interleave([])).toEqual([]) }) })