From 41a7d6738bdb0528ba2d5545baaf4624f06c4d82 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 26 Mar 2026 20:24:44 +0200 Subject: [PATCH] chore(core): introduce becca_easy_mocking and becca_mocking --- apps/server/src/test/becca_easy_mocking.ts | 137 +----------------- apps/server/src/test/becca_mocking.ts | 79 +--------- packages/trilium-core/src/index.ts | 3 + packages/trilium-core/src/services/image.ts | 4 + .../services/search/services/search.spec.ts | 20 +-- .../src/test/becca_easy_mocking.ts | 134 +++++++++++++++++ .../trilium-core/src/test/becca_mocking.ts | 76 ++++++++++ 7 files changed, 232 insertions(+), 221 deletions(-) create mode 100644 packages/trilium-core/src/test/becca_easy_mocking.ts create mode 100644 packages/trilium-core/src/test/becca_mocking.ts diff --git a/apps/server/src/test/becca_easy_mocking.ts b/apps/server/src/test/becca_easy_mocking.ts index d809baa7f9..293d37a2e8 100644 --- a/apps/server/src/test/becca_easy_mocking.ts +++ b/apps/server/src/test/becca_easy_mocking.ts @@ -1,134 +1,3 @@ -import { NoteType } from "@triliumnext/commons"; - -import BAttachment from "../becca/entities/battachment.js"; -import BAttribute from "../becca/entities/battribute.js"; -import BBranch from "../becca/entities/bbranch.js"; -import BNote from "../becca/entities/bnote.js"; -import utils, { randomString } from "../services/utils.js"; - -type AttributeDefinitions = { [key in `#${string}`]: string; }; -type RelationDefinitions = { [key in `~${string}`]: string; }; - -interface NoteDefinition extends AttributeDefinitions, RelationDefinitions { - id?: string | undefined; - title?: string; - content?: string; - type?: NoteType; - mime?: string; - children?: NoteDefinition[]; - attachments?: { - title: string; - role: string; - mime: string; - }[]; -} - -/** - * Creates the given notes with the given title and optionally one or more attributes. - * - * For a label to be created, simply pass on a key prefixed with `#` and any desired value. - * - * The notes and attributes will be injected in the froca. - * - * @param notes - * @returns an array containing the IDs of the created notes. - * @example - * buildNotes([ - * { title: "A", "#startDate": "2025-05-05" }, - * { title: "B", "#startDate": "2025-05-07" } - * ]); - */ -export function buildNotes(notes: NoteDefinition[]) { - const ids: string[] = []; - - for (const noteDef of notes) { - ids.push(buildNote(noteDef).noteId); - } - - return ids; -} - -export function buildNote(noteDef: NoteDefinition) { - const note = new BNote({ - noteId: noteDef.id ?? utils.randomString(12), - title: noteDef.title ?? "New note", - type: noteDef.type ?? "text", - mime: noteDef.mime ?? "text/html", - isProtected: false, - blobId: "" - }); - - // Handle content. - if (noteDef.content !== undefined) { - note.getContent = () => noteDef.content!; - } - - // Handle children - if (noteDef.children) { - for (const childDef of noteDef.children) { - const childNote = buildNote(childDef); - new BBranch({ - noteId: childNote.noteId, - parentNoteId: note.noteId, - branchId: `${note.noteId}_${childNote.noteId}` - }); - } - } - - // Handle labels and relations. - let position = 0; - for (const [ key, value ] of Object.entries(noteDef)) { - const attributeId = utils.randomString(12); - const name = key.substring(1); - - let attribute: BAttribute | null = null; - if (key.startsWith("#")) { - attribute = new BAttribute({ - noteId: note.noteId, - attributeId, - type: "label", - name, - value, - position, - isInheritable: false - }); - } - - if (key.startsWith("~")) { - attribute = new BAttribute({ - noteId: note.noteId, - attributeId, - type: "relation", - name, - value, - position, - isInheritable: false - }); - } - - if (!attribute) { - continue; - } - - position++; - } - - // Handle attachments. - if (noteDef.attachments) { - const allAttachments: BAttachment[] = []; - for (const { title, role, mime } of noteDef.attachments) { - const attachment = new BAttachment({ - attachmentId: randomString(10), - ownerId: note.noteId, - title, - role, - mime - }); - allAttachments.push(attachment); - } - - note.getAttachmentsByRole = (role) => allAttachments.filter(a => a.role === role); - } - - return note; -} +import { becca_easy_mocking } from "@triliumnext/core"; +export const buildNote = becca_easy_mocking.buildNote; +export const buildNotes = becca_easy_mocking.buildNotes; diff --git a/apps/server/src/test/becca_mocking.ts b/apps/server/src/test/becca_mocking.ts index 0428582983..4e70eb4ff9 100644 --- a/apps/server/src/test/becca_mocking.ts +++ b/apps/server/src/test/becca_mocking.ts @@ -1,77 +1,2 @@ -import type { NoteRow, NoteType } from "@triliumnext/commons"; -import { SearchResult } from "@triliumnext/core"; -import randtoken from "rand-token"; - -import becca from "../becca/becca.js"; -import BAttribute from "../becca/entities/battribute.js"; -import BBranch from "../becca/entities/bbranch.js"; -import BNote from "../becca/entities/bnote.js"; -randtoken.generator({ source: "crypto" }); - -export function findNoteByTitle(searchResults: Array, title: string): BNote | undefined { - return searchResults.map((sr) => becca.notes[sr.noteId]).find((note) => note.title === title); -} - -export class NoteBuilder { - note: BNote; - constructor(note: BNote) { - this.note = note; - } - - label(name: string, value = "", isInheritable = false) { - new BAttribute({ - attributeId: id(), - noteId: this.note.noteId, - type: "label", - isInheritable, - name, - value - }); - - return this; - } - - relation(name: string, targetNote: BNote) { - new BAttribute({ - attributeId: id(), - noteId: this.note.noteId, - type: "relation", - name, - value: targetNote.noteId - }); - - return this; - } - - child(childNoteBuilder: NoteBuilder, prefix = "") { - new BBranch({ - branchId: id(), - noteId: childNoteBuilder.note.noteId, - parentNoteId: this.note.noteId, - prefix, - notePosition: 10 - }); - - return this; - } -} - -export function id() { - return randtoken.generate(10); -} - -export function note(title: string, extraParams: Partial = {}) { - const row = Object.assign( - { - noteId: id(), - title, - type: "text" as NoteType, - mime: "text/html" - }, - extraParams - ); - - const note = new BNote(row); - - return new NoteBuilder(note); -} +import { becca_mocking } from "@triliumnext/core"; +export const NoteBuilder = becca_mocking.NoteBuilder; diff --git a/packages/trilium-core/src/index.ts b/packages/trilium-core/src/index.ts index 1f24f2cfb7..3f51dbc451 100644 --- a/packages/trilium-core/src/index.ts +++ b/packages/trilium-core/src/index.ts @@ -98,6 +98,9 @@ export { t } from "i18next"; export type { RequestProvider, ExecOpts, CookieJar } from "./services/request"; export type * from "./meta"; +export * as becca_easy_mocking from "./test/becca_easy_mocking"; +export * as becca_mocking from "./test/becca_mocking"; + export async function initializeCore({ dbConfig, executionContext, crypto, translations, messaging, request, schema, extraAppInfo, platform }: { dbConfig: SqlServiceParams, executionContext: ExecutionContext, diff --git a/packages/trilium-core/src/services/image.ts b/packages/trilium-core/src/services/image.ts index e140ca0507..a40f0f0e25 100644 --- a/packages/trilium-core/src/services/image.ts +++ b/packages/trilium-core/src/services/image.ts @@ -1,5 +1,9 @@ export default { saveImageToAttachment(noteId: string, imageBuffer: Uint8Array, title: string, b1: boolean, b2: boolean) { console.warn("Image save ignored", noteId, title); + + return { + attachmentId: null, + }; } } diff --git a/packages/trilium-core/src/services/search/services/search.spec.ts b/packages/trilium-core/src/services/search/services/search.spec.ts index fc36d7d7cb..5d0f1fa569 100644 --- a/packages/trilium-core/src/services/search/services/search.spec.ts +++ b/packages/trilium-core/src/services/search/services/search.spec.ts @@ -710,22 +710,22 @@ describe("Search", () => { // Create a moderate-sized dataset to test performance const countries = ["Austria", "Belgium", "Croatia", "Denmark", "Estonia", "Finland", "Germany", "Hungary", "Ireland", "Japan"]; const europeanCountries = note("Europe"); - + countries.forEach(country => { europeanCountries.child(note(country).label("type", "country").label("continent", "Europe")); }); - + rootNote.child(europeanCountries); const searchContext = new SearchContext(); const startTime = Date.now(); - + // Perform a search that exercises multiple features const searchResults = searchService.findResultsWithQuery("#type=country AND continent", searchContext); - + const endTime = Date.now(); const duration = endTime - startTime; - + // Search should complete in under 1 second for reasonable dataset expect(duration).toBeLessThan(1000); expect(searchResults.length).toEqual(10); @@ -748,14 +748,14 @@ describe("Search", () => { // Get note titles in result order const resultTitles = searchResults.map(r => becca.notes[r.noteId].title); - + // Find all exact matches (contain "analysis") - const exactMatchIndices = resultTitles.map((title, index) => + const exactMatchIndices = resultTitles.map((title, index) => title.toLowerCase().includes("analysis") ? index : -1 ).filter(index => index !== -1); - + // Find all fuzzy matches (contain typos) - const fuzzyMatchIndices = resultTitles.map((title, index) => + const fuzzyMatchIndices = resultTitles.map((title, index) => (title.includes("Anaylsis") || title.includes("Anlaysis")) ? index : -1 ).filter(index => index !== -1); @@ -765,7 +765,7 @@ describe("Search", () => { // CRITICAL: All exact matches must appear before all fuzzy matches const lastExactIndex = Math.max(...exactMatchIndices); const firstFuzzyIndex = Math.min(...fuzzyMatchIndices); - + expect(lastExactIndex).toBeLessThan(firstFuzzyIndex); }); diff --git a/packages/trilium-core/src/test/becca_easy_mocking.ts b/packages/trilium-core/src/test/becca_easy_mocking.ts new file mode 100644 index 0000000000..7e900d3c21 --- /dev/null +++ b/packages/trilium-core/src/test/becca_easy_mocking.ts @@ -0,0 +1,134 @@ +import { NoteType } from "@triliumnext/commons"; + +import BAttachment from "../becca/entities/battachment.js"; +import BAttribute from "../becca/entities/battribute.js"; +import BBranch from "../becca/entities/bbranch.js"; +import BNote from "../becca/entities/bnote.js"; +import { randomString } from "../services/utils/index.js"; + +type AttributeDefinitions = { [key in `#${string}`]: string; }; +type RelationDefinitions = { [key in `~${string}`]: string; }; + +interface NoteDefinition extends AttributeDefinitions, RelationDefinitions { + id?: string | undefined; + title?: string; + content?: string; + type?: NoteType; + mime?: string; + children?: NoteDefinition[]; + attachments?: { + title: string; + role: string; + mime: string; + }[]; +} + +/** + * Creates the given notes with the given title and optionally one or more attributes. + * + * For a label to be created, simply pass on a key prefixed with `#` and any desired value. + * + * The notes and attributes will be injected in the froca. + * + * @param notes + * @returns an array containing the IDs of the created notes. + * @example + * buildNotes([ + * { title: "A", "#startDate": "2025-05-05" }, + * { title: "B", "#startDate": "2025-05-07" } + * ]); + */ +export function buildNotes(notes: NoteDefinition[]) { + const ids: string[] = []; + + for (const noteDef of notes) { + ids.push(buildNote(noteDef).noteId); + } + + return ids; +} + +export function buildNote(noteDef: NoteDefinition) { + const note = new BNote({ + noteId: noteDef.id ?? randomString(12), + title: noteDef.title ?? "New note", + type: noteDef.type ?? "text", + mime: noteDef.mime ?? "text/html", + isProtected: false, + blobId: "" + }); + + // Handle content. + if (noteDef.content !== undefined) { + note.getContent = () => noteDef.content!; + } + + // Handle children + if (noteDef.children) { + for (const childDef of noteDef.children) { + const childNote = buildNote(childDef); + new BBranch({ + noteId: childNote.noteId, + parentNoteId: note.noteId, + branchId: `${note.noteId}_${childNote.noteId}` + }); + } + } + + // Handle labels and relations. + let position = 0; + for (const [ key, value ] of Object.entries(noteDef)) { + const attributeId = randomString(12); + const name = key.substring(1); + + let attribute: BAttribute | null = null; + if (key.startsWith("#")) { + attribute = new BAttribute({ + noteId: note.noteId, + attributeId, + type: "label", + name, + value, + position, + isInheritable: false + }); + } + + if (key.startsWith("~")) { + attribute = new BAttribute({ + noteId: note.noteId, + attributeId, + type: "relation", + name, + value, + position, + isInheritable: false + }); + } + + if (!attribute) { + continue; + } + + position++; + } + + // Handle attachments. + if (noteDef.attachments) { + const allAttachments: BAttachment[] = []; + for (const { title, role, mime } of noteDef.attachments) { + const attachment = new BAttachment({ + attachmentId: randomString(10), + ownerId: note.noteId, + title, + role, + mime + }); + allAttachments.push(attachment); + } + + note.getAttachmentsByRole = (role) => allAttachments.filter(a => a.role === role); + } + + return note; +} diff --git a/packages/trilium-core/src/test/becca_mocking.ts b/packages/trilium-core/src/test/becca_mocking.ts new file mode 100644 index 0000000000..b273c76e76 --- /dev/null +++ b/packages/trilium-core/src/test/becca_mocking.ts @@ -0,0 +1,76 @@ +import type { NoteRow, NoteType } from "@triliumnext/commons"; + +import becca from "../becca/becca.js"; +import BAttribute from "../becca/entities/battribute.js"; +import BBranch from "../becca/entities/bbranch.js"; +import BNote from "../becca/entities/bnote.js"; +import { randomString } from "../services/utils/index.js"; +import SearchResult from "../services/search/search_result.js"; + +export function findNoteByTitle(searchResults: Array, title: string): BNote | undefined { + return searchResults.map((sr) => becca.notes[sr.noteId]).find((note) => note.title === title); +} + +export class NoteBuilder { + note: BNote; + constructor(note: BNote) { + this.note = note; + } + + label(name: string, value = "", isInheritable = false) { + new BAttribute({ + attributeId: id(), + noteId: this.note.noteId, + type: "label", + isInheritable, + name, + value + }); + + return this; + } + + relation(name: string, targetNote: BNote) { + new BAttribute({ + attributeId: id(), + noteId: this.note.noteId, + type: "relation", + name, + value: targetNote.noteId + }); + + return this; + } + + child(childNoteBuilder: NoteBuilder, prefix = "") { + new BBranch({ + branchId: id(), + noteId: childNoteBuilder.note.noteId, + parentNoteId: this.note.noteId, + prefix, + notePosition: 10 + }); + + return this; + } +} + +export function id() { + return randomString(10); +} + +export function note(title: string, extraParams: Partial = {}) { + const row = Object.assign( + { + noteId: id(), + title, + type: "text" as NoteType, + mime: "text/html" + }, + extraParams + ); + + const note = new BNote(row); + + return new NoteBuilder(note); +}