mirror of
https://github.com/zadam/trilium.git
synced 2026-05-20 07:11:04 +02:00
fix(spellcheck): custom dictionary not actually saved due to CLS
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import becca from "../becca/becca.js";
|
||||
import { buildNote } from "../test/becca_easy_mocking.js";
|
||||
import customDictionary from "./custom_dictionary.js";
|
||||
|
||||
vi.mock("./log.js", () => ({
|
||||
default: {
|
||||
@@ -7,20 +10,17 @@ vi.mock("./log.js", () => ({
|
||||
}
|
||||
}));
|
||||
|
||||
const mockNote = {
|
||||
getContent: vi.fn(),
|
||||
setContent: vi.fn()
|
||||
};
|
||||
|
||||
vi.mock("../becca/becca.js", () => ({
|
||||
vi.mock("./sql.js", () => ({
|
||||
default: {
|
||||
getNote: vi.fn()
|
||||
transactional: (cb: Function) => cb(),
|
||||
execute: () => {},
|
||||
replace: () => {},
|
||||
getMap: () => {},
|
||||
getValue: () => null,
|
||||
upsert: () => {}
|
||||
}
|
||||
}));
|
||||
|
||||
import becca from "../becca/becca.js";
|
||||
import customDictionary from "./custom_dictionary.js";
|
||||
|
||||
function mockSession(localWords: string[] = []) {
|
||||
return {
|
||||
listWordsInSpellCheckerDictionary: vi.fn().mockResolvedValue(localWords),
|
||||
@@ -31,67 +31,89 @@ function mockSession(localWords: string[] = []) {
|
||||
describe("custom_dictionary", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(becca.getNote).mockReturnValue(mockNote as any);
|
||||
becca.reset();
|
||||
buildNote({
|
||||
id: "_customDictionary",
|
||||
title: "Custom Dictionary",
|
||||
type: "code",
|
||||
content: ""
|
||||
});
|
||||
});
|
||||
|
||||
describe("loadForSession", () => {
|
||||
it("does nothing when note is empty and no local words", async () => {
|
||||
mockNote.getContent.mockReturnValue("");
|
||||
const session = mockSession();
|
||||
|
||||
await customDictionary.loadForSession(session);
|
||||
|
||||
expect(session.addWordToSpellCheckerDictionary).not.toHaveBeenCalled();
|
||||
expect(mockNote.setContent).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("imports local words when note is empty (one-time import)", async () => {
|
||||
mockNote.getContent.mockReturnValue("");
|
||||
const session = mockSession(["hello", "world"]);
|
||||
|
||||
await customDictionary.loadForSession(session);
|
||||
|
||||
expect(mockNote.setContent).toHaveBeenCalledWith("hello\nworld");
|
||||
expect(session.addWordToSpellCheckerDictionary).toHaveBeenCalledTimes(2);
|
||||
expect(session.addWordToSpellCheckerDictionary).toHaveBeenCalledWith("hello");
|
||||
expect(session.addWordToSpellCheckerDictionary).toHaveBeenCalledWith("world");
|
||||
});
|
||||
|
||||
it("loads note words into session when no local words exist", async () => {
|
||||
mockNote.getContent.mockReturnValue("apple\nbanana");
|
||||
becca.reset();
|
||||
buildNote({
|
||||
id: "_customDictionary",
|
||||
title: "Custom Dictionary",
|
||||
type: "code",
|
||||
content: "apple\nbanana"
|
||||
});
|
||||
const session = mockSession();
|
||||
|
||||
await customDictionary.loadForSession(session);
|
||||
|
||||
expect(mockNote.setContent).not.toHaveBeenCalled();
|
||||
expect(session.addWordToSpellCheckerDictionary).toHaveBeenCalledTimes(2);
|
||||
expect(session.addWordToSpellCheckerDictionary).toHaveBeenCalledWith("apple");
|
||||
expect(session.addWordToSpellCheckerDictionary).toHaveBeenCalledWith("banana");
|
||||
});
|
||||
|
||||
it("merges note and local words when both have content", async () => {
|
||||
mockNote.getContent.mockReturnValue("apple\nbanana");
|
||||
becca.reset();
|
||||
buildNote({
|
||||
id: "_customDictionary",
|
||||
title: "Custom Dictionary",
|
||||
type: "code",
|
||||
content: "apple\nbanana"
|
||||
});
|
||||
const session = mockSession(["banana", "cherry"]);
|
||||
|
||||
await customDictionary.loadForSession(session);
|
||||
|
||||
// Should save the merged set (apple + banana + cherry), sorted
|
||||
expect(mockNote.setContent).toHaveBeenCalledWith("apple\nbanana\ncherry");
|
||||
expect(session.addWordToSpellCheckerDictionary).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it("does not save when local words are a subset of note words", async () => {
|
||||
mockNote.getContent.mockReturnValue("apple\nbanana\ncherry");
|
||||
becca.reset();
|
||||
buildNote({
|
||||
id: "_customDictionary",
|
||||
title: "Custom Dictionary",
|
||||
type: "code",
|
||||
content: "apple\nbanana\ncherry"
|
||||
});
|
||||
const session = mockSession(["apple", "banana"]);
|
||||
|
||||
await customDictionary.loadForSession(session);
|
||||
|
||||
expect(mockNote.setContent).not.toHaveBeenCalled();
|
||||
expect(session.addWordToSpellCheckerDictionary).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it("handles note with whitespace and blank lines", async () => {
|
||||
mockNote.getContent.mockReturnValue(" apple \n\n banana \n\n");
|
||||
becca.reset();
|
||||
buildNote({
|
||||
id: "_customDictionary",
|
||||
title: "Custom Dictionary",
|
||||
type: "code",
|
||||
content: " apple \n\n banana \n\n"
|
||||
});
|
||||
const session = mockSession();
|
||||
|
||||
await customDictionary.loadForSession(session);
|
||||
@@ -102,12 +124,11 @@ describe("custom_dictionary", () => {
|
||||
});
|
||||
|
||||
it("handles missing dictionary note gracefully", async () => {
|
||||
vi.mocked(becca.getNote).mockReturnValue(null as any);
|
||||
becca.reset(); // no note created
|
||||
const session = mockSession(["hello"]);
|
||||
|
||||
await customDictionary.loadForSession(session);
|
||||
|
||||
// Can't save, but shouldn't crash
|
||||
expect(session.addWordToSpellCheckerDictionary).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Session } from "electron";
|
||||
|
||||
import becca from "../becca/becca.js";
|
||||
import cls from "./cls.js";
|
||||
import log from "./log.js";
|
||||
|
||||
const DICTIONARY_NOTE_ID = "_customDictionary";
|
||||
@@ -30,14 +31,16 @@ function getWords(): Set<string> {
|
||||
* Saves the given words to the custom dictionary note, one per line.
|
||||
*/
|
||||
function saveWords(words: Set<string>) {
|
||||
const note = becca.getNote(DICTIONARY_NOTE_ID);
|
||||
if (!note) {
|
||||
log.error("Custom dictionary note not found.");
|
||||
return;
|
||||
}
|
||||
cls.init(() => {
|
||||
const note = becca.getNote(DICTIONARY_NOTE_ID);
|
||||
if (!note) {
|
||||
log.error("Custom dictionary note not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
const sorted = [...words].sort((a, b) => a.localeCompare(b));
|
||||
note.setContent(sorted.join("\n"));
|
||||
const sorted = [...words].sort((a, b) => a.localeCompare(b));
|
||||
note.setContent(sorted.join("\n"));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user