test: hoist vi.mock() calls to module scope

Vitest hoists `vi.mock`, `vi.unmock` and `vi.hoisted` above everything else in
the file, so a call written inside `beforeAll`, `beforeEach` or a `describe`
callback never runs where it is written. Vitest 4 only warned about the
mismatch; vitest 5 throws, and takes the whole file's collection with it:

    Error: 2 calls in "…/tree.spec.ts" were defined outside of the module's
    top level scope

Moving each call up to module scope is what already happened at runtime, so no
behaviour changes. Every factory here was self-contained — none read anything
from the hook it sat in — and the hooks keep whatever else they were doing;
content_renderer's held nothing else and goes away.

FormattingToolbar was not in the CI report. It is a client spec, and the
"Test development" job dies at typecheck before it runs any, so the break would
only have surfaced once the typecheck errors were cleared. Found by sweeping
the tree for indented hoisted calls; the sweep's one other hit,
request.spec.ts, is a continuation line of a top-level `const` and is fine.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Elian Doran
2026-09-07 23:19:53 +02:00
co-authored by Claude Opus 5
parent e8eeb68b50
commit eed9684710
5 changed files with 78 additions and 79 deletions
@@ -7,6 +7,20 @@ import { randomString } from "../../services/utils";
import { buildNote } from "../../test/easy-froca";
import { getFormattingToolbarState } from "./FormattingToolbar";
vi.mock("../../services/tree.ts", () => ({
default: {
getActiveContextNotePath() {
return "root";
},
resolveNotePath(inputNotePath: string) {
return inputNotePath;
},
getNoteIdFromUrl(url) {
return url.split("/").at(-1);
}
}
}));
interface NoteContextInfo {
type: NoteType;
viewScope?: ViewMode;
@@ -15,20 +29,6 @@ interface NoteContextInfo {
describe("Formatting toolbar logic", () => {
beforeAll(() => {
vi.mock("../../services/tree.ts", () => ({
default: {
getActiveContextNotePath() {
return "root";
},
resolveNotePath(inputNotePath: string) {
return inputNotePath;
},
getNoteIdFromUrl(url) {
return url.split("/").at(-1);
}
}
}));
buildNote({
id: "root",
title: "Root"
+14 -13
View File
@@ -6,6 +6,20 @@ import clipperRoute, { processContent } from "./clipper";
const { buildNote } = becca_easy_mocking;
vi.mock("../../services/image.js", () => ({
default: {
saveImageToAttachment() {
return {
attachmentId: "foo",
title: "encodedTitle",
};
},
// The clipping is read as soon as this answers, so the picture has to be stored by
// then; nothing here defers, so there is nothing for the wait to do.
awaitImageWrite: async () => {}
}
}));
let note!: BNote;
describe("processContent", () => {
@@ -14,19 +28,6 @@ describe("processContent", () => {
content: "Hi there"
});
note.saveAttachment = () => {};
vi.mock("../../services/image.js", () => ({
default: {
saveImageToAttachment() {
return {
attachmentId: "foo",
title: "encodedTitle",
};
},
// The clipping is read as soon as this answers, so the picture has to be stored by
// then; nothing here defers, so there is nothing for the wait to do.
awaitImageWrite: async () => {}
}
}));
});
it("processes basic note", async () => {
+8 -10
View File
@@ -2,7 +2,7 @@ import { trimIndentation } from "@triliumnext/commons";
import { sanitize, utils } from "@triliumnext/core";
import ejs from "ejs";
import { parse } from "node-html-parser";
import { beforeAll, describe, expect, it, vi } from "vitest";
import { describe, expect, it, vi } from "vitest";
import { buildShareNote, buildShareNotes } from "../test/shaca_mocking.js";
import { getContent, getDefaultTemplatePath, readTemplate, renderCode, renderNoteContent, type Result, shouldSyntaxHighlight } from "./content_renderer.js";
@@ -10,16 +10,14 @@ import type SNote from "./shaca/entities/snote.js";
import shaca from "./shaca/shaca.js";
import shareRoot from "./share_root.js";
describe("content_renderer", () => {
beforeAll(() => {
vi.mock("../becca/becca_loader.js", () => ({
default: {
load: vi.fn(),
loaded: Promise.resolve()
}
}));
});
vi.mock("../becca/becca_loader.js", () => ({
default: {
load: vi.fn(),
loaded: Promise.resolve()
}
}));
describe("content_renderer", () => {
it("Reports protected notes not being renderable", () => {
const note = buildShareNote({ isProtected: true });
const result = getContent(note);
@@ -8,6 +8,27 @@ import { getContext } from "./context.js";
import scriptService, { buildJsx, executeBundle, getScriptBundle } from "./script.js";
import ws from "./ws.js";
vi.mock("./sql.js", () => {
return {
default: {
transactional: (cb: Function) => {
cb();
},
execute: () => {},
replace: () => {},
getMap: () => {}
}
};
});
vi.mock("./sql_init.js", () => {
const mock = {
initializeDb: () => {},
dbReady: Promise.resolve()
};
return { default: mock, ...mock };
});
describe("Script", () => {
// executeBundle enforces the backendScriptingEnabled security toggle (default false).
const originalScriptingEnabled = config.Security.backendScriptingEnabled;
@@ -26,27 +47,6 @@ describe("Script", () => {
vi.spyOn(ws, "sendMessageToAllClients").mockImplementation(() => {}).mockClear();
buildNote({ id: "root", title: "root" });
vi.mock("./sql.js", () => {
return {
default: {
transactional: (cb: Function) => {
cb();
},
execute: () => {},
replace: () => {},
getMap: () => {}
}
};
});
vi.mock("./sql_init.js", () => {
const mock = {
initializeDb: () => {},
dbReady: Promise.resolve()
};
return { default: mock, ...mock };
});
});
it("returns result from script", () => {
+21 -21
View File
@@ -7,6 +7,27 @@ import tree from "./tree.js";
import {buildNote} from "../test/becca_easy_mocking.js";
import { getContext } from "./context.js";
vi.mock("./sql.js", () => {
return {
default: {
transactional: (cb: Function) => {
cb();
},
execute: () => {},
replace: () => {},
getMap: () => {}
}
};
});
vi.mock("./sql_init.js", () => {
const mock = {
initializeDb: () => {},
dbReady: Promise.resolve()
};
return { default: mock, ...mock };
});
describe("Tree", () => {
let rootNote!: NoteBuilder;
@@ -26,27 +47,6 @@ describe("Tree", () => {
parentNoteId: "none",
notePosition: 10
});
vi.mock("./sql.js", () => {
return {
default: {
transactional: (cb: Function) => {
cb();
},
execute: () => {},
replace: () => {},
getMap: () => {}
}
};
});
vi.mock("./sql_init.js", () => {
const mock = {
initializeDb: () => {},
dbReady: Promise.resolve()
};
return { default: mock, ...mock };
});
});
it("sorts notes by title (base case)", () => {