feat(spellcheck): clean up local words

This commit is contained in:
Elian Doran
2026-04-06 20:36:51 +03:00
parent 3ed7d48d42
commit 3e7488e4f3
2 changed files with 44 additions and 1 deletions

View File

@@ -24,7 +24,8 @@ vi.mock("./sql.js", () => ({
function mockSession(localWords: string[] = []) {
return {
listWordsInSpellCheckerDictionary: vi.fn().mockResolvedValue(localWords),
addWordToSpellCheckerDictionary: vi.fn()
addWordToSpellCheckerDictionary: vi.fn(),
removeWordFromSpellCheckerDictionary: vi.fn()
} as any;
}
@@ -47,6 +48,7 @@ describe("custom_dictionary", () => {
await customDictionary.loadForSession(session);
expect(session.addWordToSpellCheckerDictionary).not.toHaveBeenCalled();
expect(session.removeWordFromSpellCheckerDictionary).not.toHaveBeenCalled();
});
it("imports local words when note is empty (one-time import)", async () => {
@@ -59,6 +61,16 @@ describe("custom_dictionary", () => {
expect(session.addWordToSpellCheckerDictionary).toHaveBeenCalledWith("world");
});
it("clears local dictionary after one-time import", async () => {
const session = mockSession(["hello", "world"]);
await customDictionary.loadForSession(session);
expect(session.removeWordFromSpellCheckerDictionary).toHaveBeenCalledTimes(2);
expect(session.removeWordFromSpellCheckerDictionary).toHaveBeenCalledWith("hello");
expect(session.removeWordFromSpellCheckerDictionary).toHaveBeenCalledWith("world");
});
it("loads note words into session when no local words exist", async () => {
becca.reset();
buildNote({
@@ -91,6 +103,23 @@ describe("custom_dictionary", () => {
expect(session.addWordToSpellCheckerDictionary).toHaveBeenCalledTimes(3);
});
it("clears local dictionary after merging", async () => {
becca.reset();
buildNote({
id: "_customDictionary",
title: "Custom Dictionary",
type: "code",
content: "apple\nbanana"
});
const session = mockSession(["banana", "cherry"]);
await customDictionary.loadForSession(session);
expect(session.removeWordFromSpellCheckerDictionary).toHaveBeenCalledTimes(2);
expect(session.removeWordFromSpellCheckerDictionary).toHaveBeenCalledWith("banana");
expect(session.removeWordFromSpellCheckerDictionary).toHaveBeenCalledWith("cherry");
});
it("does not save when local words are a subset of note words", async () => {
becca.reset();
buildNote({
@@ -104,6 +133,7 @@ describe("custom_dictionary", () => {
await customDictionary.loadForSession(session);
expect(session.addWordToSpellCheckerDictionary).toHaveBeenCalledTimes(3);
expect(session.removeWordFromSpellCheckerDictionary).toHaveBeenCalledTimes(2);
});
it("handles note with whitespace and blank lines", async () => {

View File

@@ -52,6 +52,17 @@ function addWord(word: string) {
saveWords(words);
}
/**
* Removes all words from Electron's local spellchecker dictionary
* so they are not re-imported on subsequent startups.
*/
function clearFromLocalDictionary(session: Session, localWords: string[]) {
for (const word of localWords) {
session.removeWordFromSpellCheckerDictionary(word);
}
log.info(`Cleared ${localWords.length} words from local spellchecker dictionary.`);
}
/**
* Loads the custom dictionary into Electron's spellchecker session,
* performing a one-time import of locally stored words on first use.
@@ -73,6 +84,7 @@ async function loadForSession(session: Session) {
log.info(`Importing ${localWords.length} words from local spellchecker dictionary.`);
merged = new Set(localWords);
saveWords(merged);
clearFromLocalDictionary(session, localWords);
} else if (noteWords.size > 0 && localWords.length > 0) {
// Merge both sources so no words are lost.
const before = noteWords.size;
@@ -83,6 +95,7 @@ async function loadForSession(session: Session) {
log.info(`Merged ${merged.size - before} new words from local dictionary.`);
saveWords(merged);
}
clearFromLocalDictionary(session, localWords);
}
// Load all words into Electron's spellchecker.