diff --git a/apps/server/src/services/custom_dictionary.spec.ts b/apps/server/src/services/custom_dictionary.spec.ts index 9d94dc79ab..2f74fd41aa 100644 --- a/apps/server/src/services/custom_dictionary.spec.ts +++ b/apps/server/src/services/custom_dictionary.spec.ts @@ -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 () => { diff --git a/apps/server/src/services/custom_dictionary.ts b/apps/server/src/services/custom_dictionary.ts index c8f2748fdd..770cefb81e 100644 --- a/apps/server/src/services/custom_dictionary.ts +++ b/apps/server/src/services/custom_dictionary.ts @@ -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.