Files
Steffen Becker 44b351e865 feat: a thesaurus, LibreOffice's own synonyms
Ctrl+F7, Review ▸ Thesaurus and the context menu open one dialog: the
word at the caret, the sense groups it is in, click one to replace it,
and a box to look up any other word. The data is MyThes re-packed to one
group per line (de 3.0MB/37k groups, en 6.7MB/143k) — the format repeats
every group once per member, ten times the bytes. Loaded on first use for
the document's language and scanned, not indexed: ~4ms a lookup, where a
word→group index measured ~100MB of memory against the text's ~8MB.
2026-08-15 20:23:32 +02:00

51 lines
1.9 KiB
TypeScript

// The thesaurus lookup: a word is matched as a whole group member, never as a
// substring of one, and the groups come back without the word itself.
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { synonyms, loadThesaurus } from '../../src/lib/spell/thesaurus';
import { NO_LANGUAGE } from '../../src/lib/storage/documentLanguage';
const DATA = [
'Ausgedingehaus;Stöckli;Haus',
'Haus;Gebäude;Bauwerk',
'Haus;Zuhause;Heim',
'Hausboot;Boot',
'schnell;rasch;flink',
'C++;Programmiersprache',
].join('\n');
// One language per test file run: the module caches a loaded thesaurus for the session.
beforeEach(() => {
vi.stubGlobal('fetch', vi.fn(async () => new Response(DATA, { status: 200 })));
});
describe('synonyms', () => {
it('returns every group the word is in, without the word', async () => {
// The groups leading with it come first — its own senses before a compound's.
expect(await synonyms('de', 'Haus')).toEqual([
['Gebäude', 'Bauwerk'],
['Zuhause', 'Heim'],
['Ausgedingehaus', 'Stöckli'],
]);
});
it('matches whole members only, and ignores case', async () => {
expect(await synonyms('de', 'haus')).toHaveLength(3); // the groups holding Haus, not Hausboot's
expect(await synonyms('de', 'boot')).toEqual([['Hausboot']]);
});
it('takes a word with regex characters in it', async () => {
expect(await synonyms('de', 'C++')).toEqual([['Programmiersprache']]);
});
it('has nothing for an unknown word or no language', async () => {
expect(await synonyms('de', 'Fahrrad')).toEqual([]);
expect(await synonyms(NO_LANGUAGE, 'Haus')).toEqual([]);
});
it('fetches the file once per language', async () => {
await synonyms('en', 'Haus'); // 'en' is untouched above, so this is its first load
await loadThesaurus('en');
expect(vi.mocked(fetch)).toHaveBeenCalledTimes(1);
});
});