From 668cc7e1a41ef43faafed64ff2d94d0cfd7f56ce Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Wed, 22 Jan 2025 07:38:25 +0100
Subject: [PATCH 01/50] test(import/utils): prepare spec file
---
src/services/utils.spec.ts | 75 ++++++++++++++++++++++++++++++++++++++
1 file changed, 75 insertions(+)
create mode 100644 src/services/utils.spec.ts
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
new file mode 100644
index 000000000..c2712ccc9
--- /dev/null
+++ b/src/services/utils.spec.ts
@@ -0,0 +1,75 @@
+import { describe, it, expect } from "vitest";
+import utils from "./utils.js";
+
+describe.todo("#newEntityId", () => {});
+
+describe.todo("#randomString", () => {});
+
+describe.todo("#randomSecureToken", () => {});
+
+describe.todo("#md5", () => {});
+
+describe.todo("#hashedBlobId", () => {});
+
+describe.todo("#toBase64", () => {});
+
+describe.todo("#fromBase64", () => {});
+
+describe.todo("#hmac", () => {});
+
+describe.todo("#isElectron", () => {});
+
+describe.todo("#hash", () => {});
+
+describe.todo("#isEmptyOrWhitespace", () => {});
+
+describe.todo("#sanitizeSqlIdentifier", () => {});
+
+describe.todo("#escapeHtml", () => {});
+
+describe.todo("#unescapeHtml", () => {});
+
+describe.todo("#toObject", () => {});
+
+describe.todo("#stripTags", () => {});
+
+describe.todo("#union", () => {});
+
+describe.todo("#escapeRegExp", () => {});
+
+describe.todo("#crash", () => {});
+
+describe.todo("#sanitizeFilenameForHeader", () => {});
+
+describe.todo("#getContentDisposition", () => {});
+
+describe.todo("#isStringNote", () => {});
+
+describe.todo("#quoteRegex", () => {});
+
+describe.todo("#replaceAll", () => {});
+
+// TriliumNextTODO move existing formatDownloadTitle in here
+// describe.todo("#formatDownloadTitle", () => {});
+
+describe.todo("#removeTextFileExtension", () => {});
+
+describe.todo("#getNoteTitle", () => {});
+
+describe.todo("#timeLimit", () => {});
+
+describe.todo("#deferred", () => {});
+
+describe.todo("#removeDiacritic", () => {});
+
+describe.todo("#normalize", () => {});
+
+describe.todo("#toMap", () => {});
+
+describe.todo("#isString", () => {});
+
+describe.todo("#getResourceDir", () => {});
+
+describe.todo("#isMac", () => {});
+
+describe.todo("#isWindows", () => {});
\ No newline at end of file
From e6f5321444933a86e14da55b346141ff48c1cc23 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Wed, 22 Jan 2025 08:23:44 +0100
Subject: [PATCH 02/50] test(server/utils): move formatDownloadTitle tests to
spec file
---
.../utils.formatDownloadTitle.spec.ts | 61 ---------
src/services/utils.spec.ts | 116 +++++++++++++++++-
2 files changed, 115 insertions(+), 62 deletions(-)
delete mode 100644 src/services/utils.formatDownloadTitle.spec.ts
diff --git a/src/services/utils.formatDownloadTitle.spec.ts b/src/services/utils.formatDownloadTitle.spec.ts
deleted file mode 100644
index 0cc259e16..000000000
--- a/src/services/utils.formatDownloadTitle.spec.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-import { expect, describe, it } from "vitest";
-import { formatDownloadTitle } from "./utils.js";
-
-const testCases: [fnValue: Parameters, expectedValue: ReturnType][] = [
- // empty fileName tests
- [["", "text", ""], "untitled.html"],
-
- [["", "canvas", ""], "untitled.json"],
-
- [["", null, ""], "untitled"],
-
- // json extension from type tests
- [["test_file", "canvas", ""], "test_file.json"],
-
- [["test_file", "relationMap", ""], "test_file.json"],
-
- [["test_file", "search", ""], "test_file.json"],
-
- // extension based on mime type
- [["test_file", null, "text/csv"], "test_file.csv"],
-
- [["test_file_wo_ext", "image", "image/svg+xml"], "test_file_wo_ext.svg"],
-
- [["test_file_wo_ext", "file", "application/json"], "test_file_wo_ext.json"],
-
- [["test_file_w_fake_ext.ext", "image", "image/svg+xml"], "test_file_w_fake_ext.ext.svg"],
-
- [["test_file_w_correct_ext.svg", "image", "image/svg+xml"], "test_file_w_correct_ext.svg"],
-
- [["test_file_w_correct_ext.svgz", "image", "image/svg+xml"], "test_file_w_correct_ext.svgz"],
-
- [["test_file.zip", "file", "application/zip"], "test_file.zip"],
-
- [["test_file", "file", "application/zip"], "test_file.zip"],
-
- // application/octet-stream tests
- [["test_file", "file", "application/octet-stream"], "test_file"],
-
- [["test_file.zip", "file", "application/octet-stream"], "test_file.zip"],
-
- [["test_file.unknown", null, "application/octet-stream"], "test_file.unknown"],
-
- // sanitized filename tests
- [["test/file", null, "application/octet-stream"], "testfile"],
-
- [["test:file.zip", "file", "application/zip"], "testfile.zip"],
-
- [[":::", "file", "application/zip"], ".zip"],
-
- [[":::a", "file", "application/zip"], "a.zip"]
-];
-
-describe("utils/formatDownloadTitle unit tests", () => {
- testCases.forEach((testCase) => {
- return it(`With args '${JSON.stringify(testCase[0])}' it should return '${testCase[1]}'`, () => {
- const [value, expected] = testCase;
- const actual = formatDownloadTitle(...value);
- expect(actual).toStrictEqual(expected);
- });
- });
-});
\ No newline at end of file
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index c2712ccc9..9986f5dc2 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -72,4 +72,118 @@ describe.todo("#getResourceDir", () => {});
describe.todo("#isMac", () => {});
-describe.todo("#isWindows", () => {});
\ No newline at end of file
+describe.todo("#isWindows", () => {});
+
+
+describe("#formatDownloadTitle", () => {
+
+ //prettier-ignore
+ const testCases: [fnValue: Parameters, expectedValue: ReturnType][] = [
+
+ // empty fileName tests
+ [
+ ["", "text", ""],
+ "untitled.html"
+ ],
+ [
+ ["", "canvas", ""],
+ "untitled.json"
+ ],
+ [
+ ["", null, ""],
+ "untitled"
+ ],
+
+
+ // json extension from type tests
+ [
+ ["test_file", "canvas", ""],
+ "test_file.json"
+ ],
+ [
+ ["test_file", "relationMap", ""],
+ "test_file.json"
+ ],
+ [
+ ["test_file", "search", ""],
+ "test_file.json"
+ ],
+
+
+ // extension based on mime type
+ [
+ ["test_file", null, "text/csv"],
+ "test_file.csv"
+ ],
+ [
+ ["test_file_wo_ext", "image", "image/svg+xml"],
+ "test_file_wo_ext.svg"
+ ],
+ [
+ ["test_file_wo_ext", "file", "application/json"],
+ "test_file_wo_ext.json"
+ ],
+ [
+ ["test_file_w_fake_ext.ext", "image", "image/svg+xml"],
+ "test_file_w_fake_ext.ext.svg"
+ ],
+ [
+ ["test_file_w_correct_ext.svg", "image", "image/svg+xml"],
+ "test_file_w_correct_ext.svg"
+ ],
+ [
+ ["test_file_w_correct_ext.svgz", "image", "image/svg+xml"],
+ "test_file_w_correct_ext.svgz"
+ ],
+ [
+ ["test_file.zip", "file", "application/zip"],
+ "test_file.zip"
+ ],
+ [
+ ["test_file", "file", "application/zip"],
+ "test_file.zip"
+ ],
+
+
+ // application/octet-stream tests
+ [
+ ["test_file", "file", "application/octet-stream"],
+ "test_file"
+ ],
+ [
+ ["test_file.zip", "file", "application/octet-stream"],
+ "test_file.zip"
+ ],
+ [
+ ["test_file.unknown", null, "application/octet-stream"],
+ "test_file.unknown"
+ ],
+
+
+ // sanitized filename tests
+ [
+ ["test/file", null, "application/octet-stream"],
+ "testfile"
+ ],
+ [
+ ["test:file.zip", "file", "application/zip"],
+ "testfile.zip"
+ ],
+ [
+ [":::", "file", "application/zip"],
+ ".zip"
+ ],
+ [
+ [":::a", "file", "application/zip"],
+ "a.zip"
+ ]
+ ];
+
+ testCases.forEach((testCase) => {
+ const [fnParams, expected] = testCase;
+ return it(`With args '${JSON.stringify(fnParams)}', it should return '${expected}'`, () => {
+ const actual = utils.formatDownloadTitle(...fnParams);
+ expect(actual).toStrictEqual(expected);
+ });
+ });
+});
\ No newline at end of file
From 8546fe2333ec9ff40675dd09ba448601a869aa77 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Wed, 29 Jan 2025 16:54:49 +0100
Subject: [PATCH 03/50] test(server/utils): add tests for isEmptyOrWhitespace
---
src/services/utils.spec.ts | 22 +++++++++++++++++++++-
1 file changed, 21 insertions(+), 1 deletion(-)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index 9986f5dc2..6ead22576 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -21,7 +21,27 @@ describe.todo("#isElectron", () => {});
describe.todo("#hash", () => {});
-describe.todo("#isEmptyOrWhitespace", () => {});
+describe("#isEmptyOrWhitespace", () => {
+
+ const testCases: TestCase[] = [
+ ["w/ 'null' it should return true", [null], true],
+ ["w/ 'null' it should return true", [null], true],
+ ["w/ undefined it should return true", [undefined], true],
+ ["w/ empty string '' it should return true", [""], true],
+ ["w/ single whitespace string ' ' it should return true", [" "], true],
+ ["w/ multiple whitespace string ' ' it should return true", [" "], true],
+ ["w/ non-empty string ' t ' it should return false", [" t "], false],
+ ];
+
+ testCases.forEach(testCase => {
+ const [desc, fnParams, expected] = testCase;
+ it(desc, () => {
+ const result = utils.isEmptyOrWhitespace(...fnParams);
+ expect(result).toStrictEqual(expected);
+ })
+ })
+
+});
describe.todo("#sanitizeSqlIdentifier", () => {});
From 03c1128a726b6b05a4411899d1c7d9c133ab2882 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Wed, 29 Jan 2025 17:07:48 +0100
Subject: [PATCH 04/50] fix(isEmptyOrWhitespace): avoid exception throwing when
passed value is undefined
the req.body value from "routes/api/branches" actually seems to never get parsed into a JS object, but arrives as text string, so req.body.prefix could be undefined, which of course would cause an error to be thrown, when trying to call "match" on undefined.
---
src/routes/api/branches.ts | 1 +
src/services/utils.ts | 5 +++--
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/routes/api/branches.ts b/src/routes/api/branches.ts
index 746ebb92a..b9c5f751d 100644
--- a/src/routes/api/branches.ts
+++ b/src/routes/api/branches.ts
@@ -216,6 +216,7 @@ function deleteBranch(req: Request) {
function setPrefix(req: Request) {
const branchId = req.params.branchId;
+ //TriliumNextTODO: req.body arrives as string, so req.body.prefix will be undefined – did the code below ever even work?
const prefix = utils.isEmptyOrWhitespace(req.body.prefix) ? null : req.body.prefix;
const branch = becca.getBranchOrThrow(branchId);
diff --git a/src/services/utils.ts b/src/services/utils.ts
index b00c1e488..f101f6064 100644
--- a/src/services/utils.ts
+++ b/src/services/utils.ts
@@ -71,8 +71,9 @@ export function hash(text: string) {
return crypto.createHash("sha1").update(text).digest("base64");
}
-export function isEmptyOrWhitespace(str: string) {
- return str === null || str.match(/^ *$/) !== null;
+export function isEmptyOrWhitespace(str: string | null | undefined) {
+ if (!str) return true;
+ return str.match(/^ *$/) !== null;
}
export function sanitizeSqlIdentifier(str: string) {
From 33346e0cee95c14714038a3642bc306def86ccdd Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Wed, 29 Jan 2025 17:28:46 +0100
Subject: [PATCH 05/50] test(server/utils): add tests for sanitizeSqlIdentifier
---
src/services/utils.spec.ts | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index 6ead22576..57f658b5b 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -43,7 +43,28 @@ describe("#isEmptyOrWhitespace", () => {
});
-describe.todo("#sanitizeSqlIdentifier", () => {});
+describe("#sanitizeSqlIdentifier", () => {
+
+ const testCases: TestCase[] = [
+ ["w/ 'test' it should not strip anything", ["test"], "test"],
+ ["w/ 'test123' it should not strip anything", ["test123"], "test123"],
+ ["w/ 'tEst_TeSt' it should not strip anything", ["tEst_TeSt"], "tEst_TeSt"],
+ ["w/ 'test_test' it should not strip '_'", ["test_test"], "test_test"],
+ ["w/ 'test-' it should strip the '-'", ["test-"], "test"],
+ ["w/ 'test-test' it should strip the '-'", ["test-test"], "testtest"],
+ ["w/ 'test; --test' it should strip the '; --'", ["test; --test"], "testtest"],
+ ["w/ 'test test' it should strip the ' '", ["test test"], "testtest"],
+ ];
+
+ testCases.forEach(testCase => {
+ const [desc, fnParams, expected] = testCase;
+ it(desc, () => {
+ const result = utils.sanitizeSqlIdentifier(...fnParams);
+ expect(result).toStrictEqual(expected);
+ })
+ });
+
+});
describe.todo("#escapeHtml", () => {});
From 05a0acbdb518483dc7b37772ebd84ffae2b7dc15 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Wed, 29 Jan 2025 19:05:13 +0100
Subject: [PATCH 06/50] test(server/utils): add tests for newEntityId and
randomString
---
src/services/utils.spec.ts | 23 +++++++++++++++++++++--
1 file changed, 21 insertions(+), 2 deletions(-)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index 57f658b5b..36eae4e82 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -1,9 +1,28 @@
import { describe, it, expect } from "vitest";
import utils from "./utils.js";
-describe.todo("#newEntityId", () => {});
+type TestCase any> = [desc: string, fnParams: Parameters, expected: ReturnType];
-describe.todo("#randomString", () => {});
+describe("#newEntityId", () => {
+
+ it("should return a string with a length of 12", () => {
+ const result = utils.newEntityId();
+ expect(result).toBeTypeOf("string");
+ expect(result).toHaveLength(12);
+ });
+
+});
+
+describe("#randomString", () => {
+
+ it("should return a string with a length as per argument", () => {
+ const stringLength = 5;
+ const result = utils.randomString(stringLength);
+ expect(result).toBeTypeOf("string");
+ expect(result).toHaveLength(stringLength);
+ });
+
+});
describe.todo("#randomSecureToken", () => {});
From 9689222fd430de776b6081e95fb0de32d79eaf45 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Wed, 29 Jan 2025 19:15:21 +0100
Subject: [PATCH 07/50] test(server/utils): add tests for
removeTextFileExtension
---
src/services/utils.spec.ts | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index 36eae4e82..92c70268d 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -112,7 +112,24 @@ describe.todo("#replaceAll", () => {});
// TriliumNextTODO move existing formatDownloadTitle in here
// describe.todo("#formatDownloadTitle", () => {});
-describe.todo("#removeTextFileExtension", () => {});
+describe("#removeTextFileExtension", () => {
+ const testCases: TestCase[] = [
+ ["w/ 'test.md' it should strip '.md'", ["test.md"], "test"],
+ ["w/ 'test.markdown' it should strip '.markdown'", ["test.markdown"], "test"],
+ ["w/ 'test.html' it should strip '.html'", ["test.html"], "test"],
+ ["w/ 'test.htm' it should strip '.htm'", ["test.htm"], "test"],
+ ["w/ 'test.zip' it should NOT strip '.zip'", ["test.zip"], "test.zip"],
+ ];
+
+ testCases.forEach(testCase => {
+ const [desc, fnParams, expected] = testCase;
+ it(desc, () => {
+ const result = utils.removeTextFileExtension(...fnParams);
+ expect(result).toStrictEqual(expected);
+ });
+ });
+
+});
describe.todo("#getNoteTitle", () => {});
From 41c96fb2023fad91ec7a18d83a396798cfe2653b Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Wed, 29 Jan 2025 19:23:09 +0100
Subject: [PATCH 08/50] test(server/utils): add tests for envToBoolean
---
src/services/utils.spec.ts | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index 92c70268d..190bfabf4 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -145,6 +145,34 @@ describe.todo("#toMap", () => {});
describe.todo("#isString", () => {});
+describe("#envToBoolean", () => {
+ const testCases: TestCase[] = [
+ ["w/ 'true' it should return boolean 'true'", ["true"], true],
+ ["w/ 'True' it should return boolean 'true'", ["True"], true],
+ ["w/ 'TRUE' it should return boolean 'true'", ["TRUE"], true],
+ ["w/ 'true ' it should return boolean 'true'", ["true "], true],
+ ["w/ 'false' it should return boolean 'false'", ["false"], false],
+ ["w/ 'False' it should return boolean 'false'", ["False"], false],
+ ["w/ 'FALSE' it should return boolean 'false'", ["FALSE"], false],
+ ["w/ 'false ' it should return boolean 'false'", ["false "], false],
+ ["w/ 'whatever' (non-boolean string) it should return undefined", ["whatever"], undefined],
+ ["w/ '-' (non-boolean string) it should return undefined", ["-"], undefined],
+ ["w/ '' (empty string) it should return undefined", [""], undefined],
+ ["w/ ' ' (white space string) it should return undefined", [" "], undefined],
+ ["w/ undefined it should return undefined", [undefined], undefined],
+ //@ts-expect-error - pass wrong type as param
+ ["w/ number 1 it should return undefined", [1], undefined],
+ ];
+
+ testCases.forEach(testCase => {
+ const [desc, fnParams, expected] = testCase;
+ it(desc, () => {
+ const result = utils.envToBoolean(...fnParams);
+ expect(result).toStrictEqual(expected);
+ });
+ });
+});
+
describe.todo("#getResourceDir", () => {});
describe.todo("#isMac", () => {});
From 92123e17615eefaf24465d7b200290cc6f20df6b Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Wed, 29 Jan 2025 19:39:26 +0100
Subject: [PATCH 09/50] refactor(server/utils): get rid of isString
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
let's use typeof x === "string" → works exactly the same and at the same speed as this custom isString fn
---
src/services/notes.ts | 4 ++--
src/services/utils.spec.ts | 2 --
src/services/utils.ts | 5 -----
3 files changed, 2 insertions(+), 9 deletions(-)
diff --git a/src/services/notes.ts b/src/services/notes.ts
index d0a699984..0faf08d74 100644
--- a/src/services/notes.ts
+++ b/src/services/notes.ts
@@ -6,7 +6,7 @@ import eventService from "./events.js";
import cls from "../services/cls.js";
import protectedSessionService from "../services/protected_session.js";
import log from "../services/log.js";
-import { newEntityId, isString, unescapeHtml, quoteRegex, toMap } from "../services/utils.js";
+import { newEntityId, unescapeHtml, quoteRegex, toMap } from "../services/utils.js";
import revisionService from "./revisions.js";
import request from "./request.js";
import path from "path";
@@ -884,7 +884,7 @@ async function asyncPostProcessContent(note: BNote, content: string | Buffer) {
return;
}
- if (note.hasStringContent() && !isString(content)) {
+ if (note.hasStringContent() && typeof content !== "string") {
content = content.toString();
}
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index 190bfabf4..3ede44aa5 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -143,8 +143,6 @@ describe.todo("#normalize", () => {});
describe.todo("#toMap", () => {});
-describe.todo("#isString", () => {});
-
describe("#envToBoolean", () => {
const testCases: TestCase[] = [
["w/ 'true' it should return boolean 'true'", ["true"], true],
diff --git a/src/services/utils.ts b/src/services/utils.ts
index f101f6064..a1a7cc05c 100644
--- a/src/services/utils.ts
+++ b/src/services/utils.ts
@@ -295,10 +295,6 @@ export function toMap>(list: T[], key: keyof T): R
return map;
}
-export function isString(x: any) {
- return Object.prototype.toString.call(x) === "[object String]";
-}
-
// try to turn 'true' and 'false' strings from process.env variables into boolean values or undefined
export function envToBoolean(val: string | undefined) {
if (val === undefined || typeof val !== "string") return undefined;
@@ -357,7 +353,6 @@ export default {
normalize,
hashedBlobId,
toMap,
- isString,
getResourceDir,
isMac,
isWindows,
From 738436061c0a66089b6a1e47554d8da63a41c699 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Wed, 29 Jan 2025 20:37:00 +0100
Subject: [PATCH 10/50] test(server/utils): add tests for removeDiacritic and
normalize
---
src/services/utils.spec.ts | 40 ++++++++++++++++++++++++++++++++++++--
1 file changed, 38 insertions(+), 2 deletions(-)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index 3ede44aa5..49adcc0f4 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -137,9 +137,45 @@ describe.todo("#timeLimit", () => {});
describe.todo("#deferred", () => {});
-describe.todo("#removeDiacritic", () => {});
+describe("#removeDiacritic", () => {
-describe.todo("#normalize", () => {});
+ const testCases: TestCase[] = [
+ ["w/ 'Äpfel' it should replace the 'Ä'", ["Äpfel"], "Apfel"],
+ ["w/ 'Été' it should replace the 'É' and 'é'", ["Été"], "Ete"],
+ ["w/ 'Fête' it should replace the 'ê'", ["Fête"], "Fete"],
+ ["w/ 'Αλφαβήτα' it should replace the 'ή'", ["Αλφαβήτα"], "Αλφαβητα"],
+ ["w/ '' (empty string) it should return empty string", [""], ""],
+ ];
+
+ testCases.forEach(testCase => {
+ const [desc, fnParams, expected] = testCase;
+ it(desc, () => {
+ const result = utils.removeDiacritic(...fnParams);
+ expect(result).toStrictEqual(expected);
+ });
+ });
+});
+
+
+describe("#normalize", () => {
+
+ const testCases: TestCase[] = [
+ ["w/ 'Äpfel' it should replace the 'Ä' and return lowercased", ["Äpfel"], "apfel"],
+ ["w/ 'Été' it should replace the 'É' and 'é' and return lowercased", ["Été"], "ete"],
+ ["w/ 'FêTe' it should replace the 'ê' and return lowercased", ["FêTe"], "fete"],
+ ["w/ 'ΑλΦαβήΤα' it should replace the 'ή' and return lowercased", ["ΑλΦαβήΤα"], "αλφαβητα"],
+ ["w/ '' (empty string) it should return empty string", [""], ""],
+ ];
+
+ testCases.forEach(testCase => {
+ const [desc, fnParams, expected] = testCase;
+ it(desc, () => {
+ const result = utils.normalize(...fnParams);
+ expect(result).toStrictEqual(expected);
+ });
+ });
+
+});
describe.todo("#toMap", () => {});
From 45cf0334f18e6908ebe8b9543264b08ad3d059b6 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Wed, 29 Jan 2025 21:30:53 +0100
Subject: [PATCH 11/50] test(server/utils): add tests for stripTags
---
src/services/utils.spec.ts | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index 49adcc0f4..0b54d6d95 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -91,7 +91,28 @@ describe.todo("#unescapeHtml", () => {});
describe.todo("#toObject", () => {});
-describe.todo("#stripTags", () => {});
+describe("#stripTags", () => {
+
+ //pre
+ const htmlWithNewlines =
+`abc
+def
+ghi
`;
+
+ const testCases: TestCase[] = [
+ ["should strip all tags and only return the content, leaving new lines and spaces in tact", [htmlWithNewlines], "abc\ndef\nghi"],
+ //TriliumNextTODO: should this actually insert a space between content to prevent concatenated text?
+ ["should strip all tags and only return the content", ["abc
def
"], "abcdef"],
+ ];
+
+ testCases.forEach(testCase => {
+ const [desc, fnParams, expected] = testCase;
+ it(desc, () => {
+ const result = utils.stripTags(...fnParams);
+ expect(result).toStrictEqual(expected);
+ })
+ });
+});
describe.todo("#union", () => {});
From 81db6817329c5b2fe589e9bd9b7df87da4a3da9e Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Wed, 29 Jan 2025 21:34:47 +0100
Subject: [PATCH 12/50] refactor(server/utils): remove unused union
function is not used at all anywhere
---
src/services/utils.spec.ts | 2 --
src/services/utils.ts | 24 ------------------------
2 files changed, 26 deletions(-)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index 0b54d6d95..06eccab7c 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -114,8 +114,6 @@ def
});
});
-describe.todo("#union", () => {});
-
describe.todo("#escapeRegExp", () => {});
describe.todo("#crash", () => {});
diff --git a/src/services/utils.ts b/src/services/utils.ts
index a1a7cc05c..6ebc93ccc 100644
--- a/src/services/utils.ts
+++ b/src/services/utils.ts
@@ -104,29 +104,6 @@ export function stripTags(text: string) {
return text.replace(/<(?:.|\n)*?>/gm, "");
}
-export function union(a: T[], b: T[]): T[] {
- const obj: Record = {} as Record; // TODO: unsafe?
-
- for (let i = a.length - 1; i >= 0; i--) {
- obj[a[i]] = a[i];
- }
-
- for (let i = b.length - 1; i >= 0; i--) {
- obj[b[i]] = b[i];
- }
-
- const res: T[] = [];
-
- for (const k in obj) {
- if (obj.hasOwnProperty(k)) {
- // <-- optional
- res.push(obj[k]);
- }
- }
-
- return res;
-}
-
export function escapeRegExp(str: string) {
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}
@@ -337,7 +314,6 @@ export default {
unescapeHtml,
toObject,
stripTags,
- union,
escapeRegExp,
crash,
getContentDisposition,
From de5ffb591d38dd8967650a7152f07f57d53918b0 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Wed, 29 Jan 2025 21:45:14 +0100
Subject: [PATCH 13/50] test(server/utils): add tests for
isMac/isWindows/isElectron
---
src/services/utils.spec.ts | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index 06eccab7c..ce1e83392 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -36,8 +36,6 @@ describe.todo("#fromBase64", () => {});
describe.todo("#hmac", () => {});
-describe.todo("#isElectron", () => {});
-
describe.todo("#hash", () => {});
describe("#isEmptyOrWhitespace", () => {
@@ -228,10 +226,23 @@ describe("#envToBoolean", () => {
describe.todo("#getResourceDir", () => {});
-describe.todo("#isMac", () => {});
+describe("#isElectron", () => {
+ it("should export a boolean", () => {
+ expect(utils.isElectron).toBeTypeOf("boolean");
+ });
+});
-describe.todo("#isWindows", () => {});
+describe("#isMac", () => {
+ it("should export a boolean", () => {
+ expect(utils.isMac).toBeTypeOf("boolean");
+ });
+});
+describe("#isWindows", () => {
+ it("should export a boolean", () => {
+ expect(utils.isWindows).toBeTypeOf("boolean");
+ });
+});
describe("#formatDownloadTitle", () => {
From 283b19c716b2eb038728818b93908d488959aa5d Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Wed, 29 Jan 2025 21:53:18 +0100
Subject: [PATCH 14/50] fix(server/utils): add missed export of isDev in
default export object
---
src/services/utils.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/services/utils.ts b/src/services/utils.ts
index 6ebc93ccc..21649b5b2 100644
--- a/src/services/utils.ts
+++ b/src/services/utils.ts
@@ -332,5 +332,6 @@ export default {
getResourceDir,
isMac,
isWindows,
+ isDev,
envToBoolean
};
From 5c20a6d5eebf3ec8d29d575fbae8595469cf793c Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Wed, 29 Jan 2025 21:53:31 +0100
Subject: [PATCH 15/50] test(server/utils): add tests for isDev
---
src/services/utils.spec.ts | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index ce1e83392..3e1766f48 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -244,6 +244,12 @@ describe("#isWindows", () => {
});
});
+describe("#isDev", () => {
+ it("should export a boolean", () => {
+ expect(utils.isDev).toBeTypeOf("boolean");
+ });
+});
+
describe("#formatDownloadTitle", () => {
//prettier-ignore
From 3094b1779e6856c18a96202b42be6a61e73671dd Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Wed, 29 Jan 2025 22:58:30 +0100
Subject: [PATCH 16/50] test(server/utils): add tests for isStringNote
---
src/services/utils.spec.ts | 44 +++++++++++++++++++++++++++++++++++++-
1 file changed, 43 insertions(+), 1 deletion(-)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index 3e1766f48..f765eea5b 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -120,7 +120,49 @@ describe.todo("#sanitizeFilenameForHeader", () => {});
describe.todo("#getContentDisposition", () => {});
-describe.todo("#isStringNote", () => {});
+describe("#isStringNote", () => {
+
+ const testCases: TestCase[] = [
+ [
+ "w/ 'undefined' note type, but a string mime type, it should return true",
+ [undefined, "application/javascript"],
+ true
+ ],
+ [
+ "w/ non-string note type, it should return false",
+ ["image", "image/jpeg"],
+ false
+ ],
+ [
+ "w/ string note type (text), it should return true",
+ ["text", "text/html"],
+ true
+ ],
+ [
+ "w/ string note type (code), it should return true",
+ ["code", "application/json"],
+ true
+ ],
+ [
+ "w/ non-string note type (file), but string mime type, it should return true",
+ ["file", "application/json"],
+ true
+ ],
+ [
+ "w/ non-string note type (file), but mime type starting with 'text/', it should return true",
+ ["file", "text/html"],
+ true
+ ],
+ ];
+
+ testCases.forEach(testCase => {
+ const [desc, fnParams, expected] = testCase;
+ it(desc, () => {
+ const result = utils.isStringNote(...fnParams);
+ expect(result).toStrictEqual(expected);
+ });
+ });
+});
describe.todo("#quoteRegex", () => {});
From 440dbfd4d4672bc76ab65ee018282cc22cd6da80 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Wed, 29 Jan 2025 23:19:08 +0100
Subject: [PATCH 17/50] refactor(server/utils): use Set for isStringNote
---
src/services/utils.ts | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/services/utils.ts b/src/services/utils.ts
index 21649b5b2..67c415f9f 100644
--- a/src/services/utils.ts
+++ b/src/services/utils.ts
@@ -132,11 +132,12 @@ export function getContentDisposition(filename: string) {
return `file; filename="${sanitizedFilename}"; filename*=UTF-8''${sanitizedFilename}`;
}
+// render and book are string note in the sense that they are expected to contain empty string
+const STRING_NOTE_TYPES = new Set(["text", "code", "relationMap", "search", "render", "book", "mermaid", "canvas"]);
const STRING_MIME_TYPES = new Set(["application/javascript", "application/x-javascript", "application/json", "application/x-sql", "image/svg+xml"]);
export function isStringNote(type: string | undefined, mime: string) {
- // render and book are string note in the sense that they are expected to contain empty string
- return (type && ["text", "code", "relationMap", "search", "render", "book", "mermaid", "canvas"].includes(type)) || mime.startsWith("text/") || STRING_MIME_TYPES.has(mime);
+ return (type && STRING_NOTE_TYPES.has(type)) || mime.startsWith("text/") || STRING_MIME_TYPES.has(mime);
}
export function quoteRegex(url: string) {
From 9eeedc827ce152b46e676eda40c4f1629c4d4ec3 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Fri, 31 Jan 2025 07:39:33 +0100
Subject: [PATCH 18/50] test(server/utils): add tests for timeLimit
---
src/services/utils.spec.ts | 59 +++++++++++++++++++++++++++++++++++++-
1 file changed, 58 insertions(+), 1 deletion(-)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index f765eea5b..d7a2e27bb 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -192,7 +192,64 @@ describe("#removeTextFileExtension", () => {
describe.todo("#getNoteTitle", () => {});
-describe.todo("#timeLimit", () => {});
+describe("#timeLimit", () => {
+
+ it("when promise execution does NOT exceed timeout, it should resolve with promises' value", async () => {
+ const resolvedValue = `resolved: ${new Date().toISOString()}`;
+ const testPromise = new Promise((res, rej) => {
+ setTimeout(() => {
+ return res(resolvedValue);
+ }, 200);
+ //rej("rejected!");
+ });
+ expect(utils.timeLimit(testPromise, 1_000)).resolves.toBe(resolvedValue);
+ });
+
+ it("when promise execution rejects within timeout, it should return the original promises' rejected value, not the custom set one", async () => {
+ const rejectedValue = `rejected: ${new Date().toISOString()}`;
+ const testPromise = new Promise((res, rej) => {
+ setTimeout(() => {
+ //return res("resolved");
+ rej(rejectedValue);
+ }, 100);
+ });
+ expect(utils.timeLimit(testPromise, 200, "Custom Error")).rejects.toThrow(rejectedValue)
+ });
+
+ it("when promise execution exceeds the set timeout, and 'errorMessage' is NOT set, it should reject the promise and display default error message", async () => {
+ const testPromise = new Promise((res, rej) => {
+ setTimeout(() => {
+ return res("resolved");
+ }, 500);
+ //rej("rejected!");
+ });
+ expect(utils.timeLimit(testPromise, 200)).rejects.toThrow(`Process exceeded time limit 200`)
+ });
+
+ it("when promise execution exceeds the set timeout, and 'errorMessage' is set, it should reject the promise and display set error message", async () => {
+ const customErrorMsg = "Custom Error";
+ const testPromise = new Promise((res, rej) => {
+ setTimeout(() => {
+ return res("resolved");
+ }, 500);
+ //rej("rejected!");
+ });
+ expect(utils.timeLimit(testPromise, 200, customErrorMsg)).rejects.toThrow(customErrorMsg)
+ });
+
+ // TriliumNextTODO: since TS avoids this from ever happening – do we need this check?
+ it("when the passed promise is not a promise but 'undefined', it should return 'undefined'", async () => {
+ //@ts-expect-error - passing in illegal type 'undefined'
+ expect(utils.timeLimit(undefined, 200)).toBe(undefined)
+ });
+
+ // TriliumNextTODO: since TS avoids this from ever happening – do we need this check?
+ it("when the passed promise is not a promise, it should return the passed value", async () => {
+ //@ts-expect-error - passing in illegal type 'object'
+ expect(utils.timeLimit({test: 1}, 200)).toStrictEqual({test: 1})
+ });
+
+});
describe.todo("#deferred", () => {});
From 9a8a27c02c2226d4fc1ed216875aa07433fd5f22 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Fri, 31 Jan 2025 07:43:01 +0100
Subject: [PATCH 19/50] refactor(server/utils): avoid same variable name for
error in timeLimit
rename the error created in timeLimit to `errorTimeLimit` to differentiate it from the error that is caught inside the promise
makes it a bit easier to quickly distinguish these
---
src/services/utils.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/services/utils.ts b/src/services/utils.ts
index 67c415f9f..c9636f16f 100644
--- a/src/services/utils.ts
+++ b/src/services/utils.ts
@@ -209,7 +209,7 @@ export function timeLimit(promise: Promise, limitMs: number, errorMessage?
}
// better stack trace if created outside of promise
- const error = new Error(errorMessage || `Process exceeded time limit ${limitMs}`);
+ const errorTimeLimit = new Error(errorMessage || `Process exceeded time limit ${limitMs}`);
return new Promise((res, rej) => {
let resolved = false;
@@ -224,7 +224,7 @@ export function timeLimit(promise: Promise, limitMs: number, errorMessage?
setTimeout(() => {
if (!resolved) {
- rej(error);
+ rej(errorTimeLimit);
}
}, limitMs);
});
From ef66d330eca0a19a083b6c71b3330338ccae35ee Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Fri, 31 Jan 2025 07:43:25 +0100
Subject: [PATCH 20/50] chore(server/utils): timeLimit - add TODO comment
---
src/services/utils.ts | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/services/utils.ts b/src/services/utils.ts
index c9636f16f..aec55305d 100644
--- a/src/services/utils.ts
+++ b/src/services/utils.ts
@@ -9,6 +9,7 @@ import mimeTypes from "mime-types";
import path from "path";
import { fileURLToPath } from "url";
import { dirname, join } from "path";
+//import type { NoteType } from "../rows.js";
const randtoken = generator({ source: "crypto" });
@@ -203,6 +204,7 @@ export function getNoteTitle(filePath: string, replaceUnderscoresWithSpaces: boo
}
export function timeLimit(promise: Promise, limitMs: number, errorMessage?: string): Promise {
+ // TriliumNextTODO: since TS avoids this from ever happening – do we need this check?
if (!promise || !promise.then) {
// it's not actually a promise
return promise;
From df1d47972023d6ee76754320c6e009d025171131 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Fri, 31 Jan 2025 07:59:12 +0100
Subject: [PATCH 21/50] chore(server/utils): sort exports alphabetically
---
src/services/utils.ts | 62 +++++++++++++++++++++----------------------
1 file changed, 31 insertions(+), 31 deletions(-)
diff --git a/src/services/utils.ts b/src/services/utils.ts
index aec55305d..d7eef0fca 100644
--- a/src/services/utils.ts
+++ b/src/services/utils.ts
@@ -302,39 +302,39 @@ export function getResourceDir() {
}
export default {
- randomSecureToken,
- randomString,
+ crash,
+ deferred,
+ envToBoolean,
+ escapeHtml,
+ escapeRegExp,
+ formatDownloadTitle,
+ fromBase64,
+ getContentDisposition,
+ getNoteTitle,
+ getResourceDir,
+ hash,
+ hashedBlobId,
+ hmac,
+ isDev,
+ isElectron,
+ isEmptyOrWhitespace,
+ isMac,
+ isStringNote,
+ isWindows,
md5,
newEntityId,
- toBase64,
- fromBase64,
- hmac,
- isElectron,
- hash,
- isEmptyOrWhitespace,
- sanitizeSqlIdentifier,
- escapeHtml,
- unescapeHtml,
- toObject,
- stripTags,
- escapeRegExp,
- crash,
- getContentDisposition,
- isStringNote,
- quoteRegex,
- replaceAll,
- getNoteTitle,
- removeTextFileExtension,
- formatDownloadTitle,
- timeLimit,
- deferred,
- removeDiacritic,
normalize,
- hashedBlobId,
+ quoteRegex,
+ randomSecureToken,
+ randomString,
+ removeDiacritic,
+ removeTextFileExtension,
+ replaceAll,
+ sanitizeSqlIdentifier,
+ stripTags,
+ timeLimit,
+ toBase64,
toMap,
- getResourceDir,
- isMac,
- isWindows,
- isDev,
- envToBoolean
+ toObject,
+ unescapeHtml
};
From acb7098bb8c6be6acd65b6b0e8be58f59f4188d6 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Fri, 31 Jan 2025 08:09:54 +0100
Subject: [PATCH 22/50] test(server/utils): add tests for
sanitizeFilenameForHeader
---
src/services/utils.spec.ts | 20 +++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index d7a2e27bb..39c276717 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -116,7 +116,25 @@ describe.todo("#escapeRegExp", () => {});
describe.todo("#crash", () => {});
-describe.todo("#sanitizeFilenameForHeader", () => {});
+describe("#sanitizeFilenameForHeader", () => {
+ // only test our own code, not the sanitize-filename side of things
+ const testCases: TestCase[] = [
+ ["when passed filename is empty, it should fallback to default value 'file'", [" "], "file"],
+ ["when passed filename '..' would cause sanitized filename to be empty, it should fallback to default value 'file'", [".."], "file"],
+ // COM1 is a Windows specific "illegal filename" that sanitize filename strips away
+ ["when passed filename 'COM1' would cause sanitized filename to be empty, it should fallback to default value 'file'", ["COM1"], "file"],
+ ["sanitized passed filename should be returned URIEncoded", ["test file.csv"], "test%20file.csv"]
+ ]
+
+ testCases.forEach(testCase => {
+ const [desc, fnParams, expected] = testCase;
+ it(desc, () => {
+ const result = utils.sanitizeFilenameForHeader(...fnParams);
+ expect(result).toStrictEqual(expected);
+ })
+ });
+
+});
describe.todo("#getContentDisposition", () => {});
From 6ab9a3979d37613c410591353fe1a5f003212f25 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Fri, 31 Jan 2025 08:12:11 +0100
Subject: [PATCH 23/50] refactor(server/utils): simplify
sanitizeFilenameForHeader
since "" is falsy, we can just use "||" here
---
src/services/utils.ts | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/src/services/utils.ts b/src/services/utils.ts
index d7eef0fca..126e07f76 100644
--- a/src/services/utils.ts
+++ b/src/services/utils.ts
@@ -118,12 +118,7 @@ export async function crash() {
}
export function sanitizeFilenameForHeader(filename: string) {
- let sanitizedFilename = sanitize(filename);
-
- if (sanitizedFilename.trim().length === 0) {
- sanitizedFilename = "file";
- }
-
+ const sanitizedFilename = sanitize(filename).trim() || "file";
return encodeURIComponent(sanitizedFilename);
}
From 29b1befd602eeb7822d6aa6c327788552bfb594f Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Fri, 31 Jan 2025 08:12:42 +0100
Subject: [PATCH 24/50] fix(server/utils): missing export for
sanitizeFilenameForHeader
---
src/services/utils.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/services/utils.ts b/src/services/utils.ts
index 126e07f76..9af0c20fc 100644
--- a/src/services/utils.ts
+++ b/src/services/utils.ts
@@ -325,6 +325,7 @@ export default {
removeDiacritic,
removeTextFileExtension,
replaceAll,
+ sanitizeFilenameForHeader,
sanitizeSqlIdentifier,
stripTags,
timeLimit,
From fedaec6c79ebec062e1c2175e54e385b2aa0e6a9 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Fri, 31 Jan 2025 08:33:59 +0100
Subject: [PATCH 25/50] refactor(server/utils): merge sanitizeFilenameForHeader
into getContentDisposition
sanitizeFilenameForHeader is not used anywhere else and is tiny, so let's merge it
---
src/services/utils.ts | 12 +++---------
1 file changed, 3 insertions(+), 9 deletions(-)
diff --git a/src/services/utils.ts b/src/services/utils.ts
index 9af0c20fc..174929334 100644
--- a/src/services/utils.ts
+++ b/src/services/utils.ts
@@ -117,15 +117,10 @@ export async function crash() {
}
}
-export function sanitizeFilenameForHeader(filename: string) {
- const sanitizedFilename = sanitize(filename).trim() || "file";
- return encodeURIComponent(sanitizedFilename);
-}
-
export function getContentDisposition(filename: string) {
- const sanitizedFilename = sanitizeFilenameForHeader(filename);
-
- return `file; filename="${sanitizedFilename}"; filename*=UTF-8''${sanitizedFilename}`;
+ const sanitizedFilename = sanitize(filename).trim() || "file";
+ const uriEncodedFilename = encodeURIComponent(sanitizedFilename);
+ return `file; filename="${uriEncodedFilename}"; filename*=UTF-8''${uriEncodedFilename}`;
}
// render and book are string note in the sense that they are expected to contain empty string
@@ -325,7 +320,6 @@ export default {
removeDiacritic,
removeTextFileExtension,
replaceAll,
- sanitizeFilenameForHeader,
sanitizeSqlIdentifier,
stripTags,
timeLimit,
From b812e67794aa44956e3295cec1575cace8f6d942 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Fri, 31 Jan 2025 08:35:18 +0100
Subject: [PATCH 26/50] test(server/utils): adapt tests for
getContentDisposition
since we merged these functions, adapt the previous tests of sanitizeFilenameForHeader and use them for the newly merged function getContentDisposition
---
src/services/utils.spec.ts | 38 ++++++++++++++++++++++++++------------
1 file changed, 26 insertions(+), 12 deletions(-)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index 39c276717..16bdbccc5 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -116,28 +116,42 @@ describe.todo("#escapeRegExp", () => {});
describe.todo("#crash", () => {});
-describe("#sanitizeFilenameForHeader", () => {
- // only test our own code, not the sanitize-filename side of things
- const testCases: TestCase[] = [
- ["when passed filename is empty, it should fallback to default value 'file'", [" "], "file"],
- ["when passed filename '..' would cause sanitized filename to be empty, it should fallback to default value 'file'", [".."], "file"],
+describe("#getContentDisposition", () => {
+
+ const defaultFallBackDisposition = `file; filename="file"; filename*=UTF-8''file`;
+ const testCases: TestCase[] = [
+ [
+ "when passed filename is empty, it should fallback to default value 'file'",
+ [" "],
+ defaultFallBackDisposition
+ ],
+ [
+ "when passed filename '..' would cause sanitized filename to be empty, it should fallback to default value 'file'",
+ [".."],
+ defaultFallBackDisposition
+ ],
// COM1 is a Windows specific "illegal filename" that sanitize filename strips away
- ["when passed filename 'COM1' would cause sanitized filename to be empty, it should fallback to default value 'file'", ["COM1"], "file"],
- ["sanitized passed filename should be returned URIEncoded", ["test file.csv"], "test%20file.csv"]
+ [
+ "when passed filename 'COM1' would cause sanitized filename to be empty, it should fallback to default value 'file'",
+ ["COM1"],
+ defaultFallBackDisposition
+ ],
+ [
+ "sanitized passed filename should be returned URIEncoded",
+ ["test file.csv"],
+ `file; filename="test%20file.csv"; filename*=UTF-8''test%20file.csv`
+ ]
]
testCases.forEach(testCase => {
const [desc, fnParams, expected] = testCase;
it(desc, () => {
- const result = utils.sanitizeFilenameForHeader(...fnParams);
- expect(result).toStrictEqual(expected);
+ const result = utils.getContentDisposition(...fnParams);
+ expect(result).toStrictEqual(expected);
})
});
-
});
-describe.todo("#getContentDisposition", () => {});
-
describe("#isStringNote", () => {
const testCases: TestCase[] = [
From c15e46bf2556fa44b0452bff48dd091e9713117a Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Fri, 31 Jan 2025 08:46:56 +0100
Subject: [PATCH 27/50] chore(server/utils): improve types for getNoteTitle
---
src/services/utils.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/services/utils.ts b/src/services/utils.ts
index 174929334..a24698a1c 100644
--- a/src/services/utils.ts
+++ b/src/services/utils.ts
@@ -9,7 +9,7 @@ import mimeTypes from "mime-types";
import path from "path";
import { fileURLToPath } from "url";
import { dirname, join } from "path";
-//import type { NoteType } from "../rows.js";
+import type NoteMeta from "./meta/note_meta.js";
const randtoken = generator({ source: "crypto" });
@@ -181,7 +181,7 @@ export function removeTextFileExtension(filePath: string) {
}
}
-export function getNoteTitle(filePath: string, replaceUnderscoresWithSpaces: boolean, noteMeta?: { title?: string }) {
+export function getNoteTitle(filePath: string, replaceUnderscoresWithSpaces: boolean, noteMeta?: NoteMeta) {
if (noteMeta?.title) {
return noteMeta.title;
} else {
From c3587ad5361416f29d808eda89b502c7f65489e4 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Fri, 31 Jan 2025 21:32:52 +0100
Subject: [PATCH 28/50] test(server/utils): add tests for getNoteTitle
---
src/services/utils.spec.ts | 61 +++++++++++++++++++++++++++++++++++++-
1 file changed, 60 insertions(+), 1 deletion(-)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index 16bdbccc5..9fbb6c545 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -222,7 +222,66 @@ describe("#removeTextFileExtension", () => {
});
-describe.todo("#getNoteTitle", () => {});
+describe("#getNoteTitle", () => {
+ const testCases: TestCase[] = [
+ [
+ "when file has no spaces, and no special file extension, it should return the filename unaltered",
+ ["test.json", true, undefined],
+ "test.json"
+ ],
+ [
+ "when replaceUnderscoresWithSpaces is false, it should keep the underscores in the title",
+ ["test_file.json", false, undefined],
+ "test_file.json"
+ ],
+ [
+ "when replaceUnderscoresWithSpaces is true, it should replace the underscores in the title",
+ ["test_file.json", true, undefined],
+ "test file.json"
+ ],
+ [
+ "when filePath ends with one of the extra handled endings (.md), it should strip the file extension from the title",
+ ["test_file.md", false, undefined],
+ "test_file"
+ ],
+ [
+ "when filePath ends with one of the extra handled endings (.md) and replaceUnderscoresWithSpaces is true, it should strip the file extension from the title and replace underscores",
+ ["test_file.md", true, undefined],
+ "test file"
+ ],
+ [
+ "when filepath contains a full path, it should only return the basename of the file",
+ ["Trilium Demo/Scripting examples/Statistics/Most cloned notes/template.zip", true, undefined],
+ "template.zip"
+ ],
+ [
+ "when filepath contains a full path and has extra handled ending (.html), it should only return the basename of the file and strip the file extension",
+ ["Trilium Demo/Scripting examples/Statistics/Most cloned notes/template.html", true, undefined],
+ "template"
+ ],
+ [
+ "when a noteMeta object is passed, it should use the title from the noteMeta, if present",
+ //@ts-expect-error - passing in incomplete noteMeta - but we only care about the title prop here
+ ["test_file.md", true, { title: "some other title"}],
+ "some other title"
+ ],
+ [
+ "when a noteMeta object is passed, but the title prop is empty, it should try to handle the filename as if no noteMeta was passed",
+ //@ts-expect-error - passing in incomplete noteMeta - but we only care about the title prop here
+ ["test_file.md", true, { title: ""}],
+ "test file"
+ ]
+ ];
+
+ testCases.forEach(testCase => {
+ const [desc, fnParams, expected] = testCase;
+ it(desc, () => {
+ const result = utils.getNoteTitle(...fnParams);
+ expect(result).toStrictEqual(expected);
+ });
+ });
+
+});
describe("#timeLimit", () => {
From 65be2cf04876da50f561b62c3e25bc5c8179f772 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Fri, 31 Jan 2025 21:38:23 +0100
Subject: [PATCH 29/50] refactor(server/utils): simplify getNoteTitle
---
src/services/utils.ts | 12 +++---------
1 file changed, 3 insertions(+), 9 deletions(-)
diff --git a/src/services/utils.ts b/src/services/utils.ts
index a24698a1c..b2204b337 100644
--- a/src/services/utils.ts
+++ b/src/services/utils.ts
@@ -182,15 +182,9 @@ export function removeTextFileExtension(filePath: string) {
}
export function getNoteTitle(filePath: string, replaceUnderscoresWithSpaces: boolean, noteMeta?: NoteMeta) {
- if (noteMeta?.title) {
- return noteMeta.title;
- } else {
- const basename = path.basename(removeTextFileExtension(filePath));
- if (replaceUnderscoresWithSpaces) {
- return basename.replace(/_/g, " ").trim();
- }
- return basename;
- }
+ if (noteMeta?.title) return noteMeta.title;
+ const basename = path.basename(removeTextFileExtension(filePath));
+ return replaceUnderscoresWithSpaces ? basename.replace(/_/g, " ").trim() : basename;
}
export function timeLimit(promise: Promise, limitMs: number, errorMessage?: string): Promise {
From 46f28f4f09ed12f69f0379bbc6e7f21b37c2b185 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Fri, 31 Jan 2025 21:39:42 +0100
Subject: [PATCH 30/50] test(server/utils): add edge case test for getNoteTitle
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
when the noteMeta title consists of just spaces, it should fall back to "normal" handling again → currently this fails
---
src/services/utils.spec.ts | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index 9fbb6c545..68cb940cf 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -270,6 +270,12 @@ describe("#getNoteTitle", () => {
//@ts-expect-error - passing in incomplete noteMeta - but we only care about the title prop here
["test_file.md", true, { title: ""}],
"test file"
+ ],
+ [
+ "when a noteMeta object is passed, but the title prop is empty, it should try to handle the filename as if no noteMeta was passed",
+ //@ts-expect-error - passing in incomplete noteMeta - but we only care about the title prop here
+ ["test_file.json", false, { title: " "}],
+ "test_file.json"
]
];
From 6e5e6989ed72db0b8391f930e161eb07394fd3e9 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Fri, 31 Jan 2025 21:52:26 +0100
Subject: [PATCH 31/50] fix(server/utils): fix potentially "empty looking"
title from getNoteTitle
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
when the noteMeta title consists of just spaces, it will fall back to "normal" handling again → instead of showing " " as title, which would be perceived as "empty"
---
src/services/utils.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/services/utils.ts b/src/services/utils.ts
index b2204b337..964f0708b 100644
--- a/src/services/utils.ts
+++ b/src/services/utils.ts
@@ -182,7 +182,9 @@ export function removeTextFileExtension(filePath: string) {
}
export function getNoteTitle(filePath: string, replaceUnderscoresWithSpaces: boolean, noteMeta?: NoteMeta) {
- if (noteMeta?.title) return noteMeta.title;
+ const trimmedNoteMeta = noteMeta?.title?.trim();
+ if (trimmedNoteMeta) return trimmedNoteMeta;
+
const basename = path.basename(removeTextFileExtension(filePath));
return replaceUnderscoresWithSpaces ? basename.replace(/_/g, " ").trim() : basename;
}
From a4ce2ddd5e494cdee2967b002ae90f283312f6c6 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Fri, 31 Jan 2025 21:55:37 +0100
Subject: [PATCH 32/50] refactor(server/utils): simplify getResourceDir
get rid of unnecessary else branch here
---
src/services/utils.ts | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/src/services/utils.ts b/src/services/utils.ts
index 964f0708b..bc35fe10f 100644
--- a/src/services/utils.ts
+++ b/src/services/utils.ts
@@ -280,11 +280,8 @@ export function envToBoolean(val: string | undefined) {
* @returns the resource dir.
*/
export function getResourceDir() {
- if (isElectron && !isDev) {
- return process.resourcesPath;
- } else {
- return join(dirname(fileURLToPath(import.meta.url)), "..", "..");
- }
+ if (isElectron && !isDev) return process.resourcesPath;
+ return join(dirname(fileURLToPath(import.meta.url)), "..", "..");
}
export default {
From 9f2dd21865cb20bfe9cc209bafbd1d0e568e8a8a Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Fri, 31 Jan 2025 22:55:58 +0100
Subject: [PATCH 33/50] test(server/utils): add tests for randomSecureToken
(bit ugly I have to say, as we are essentially partially testing "crypto" module here,
probably should be instead replaced by a version that mocks crypto module and checks, if the called functions match the expectations)
---
src/services/utils.spec.ts | 20 +++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index 68cb940cf..272a03b8c 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -24,7 +24,25 @@ describe("#randomString", () => {
});
-describe.todo("#randomSecureToken", () => {});
+describe("#randomSecureToken", () => {
+ // base64 -> 4 * (bytes/3) length -> if padding and rounding up is ignored for simplicity
+ // https://stackoverflow.com/a/13378842
+ const byteToBase64Length = (bytes: number) => 4 * (bytes / 3);
+
+ it("should return a string and use 32 bytes by default", () => {
+ const result = utils.randomSecureToken();
+ expect(result).toBeTypeOf("string");
+ expect(result.length).toBeGreaterThanOrEqual(byteToBase64Length(32));
+ });
+
+ it("should return a string and use passed byte length", () => {
+ const bytes = 16;
+ const result = utils.randomSecureToken(bytes);
+ expect(result).toBeTypeOf("string");
+ expect(result.length).toBeGreaterThanOrEqual(byteToBase64Length(bytes));
+ expect(result.length).toBeLessThan(44); // default argument uses 32 bytes -> which translates to 44 base64 legal chars
+ });
+});
describe.todo("#md5", () => {});
From 72f0de6b78938f5550327d08e69579038f38f434 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Fri, 31 Jan 2025 23:23:07 +0100
Subject: [PATCH 34/50] test(server/utils): add todo remarks
---
src/services/utils.spec.ts | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index 272a03b8c..9669acfd0 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -24,6 +24,7 @@ describe("#randomString", () => {
});
+// TriliumNextTODO: should use mocks and assert that functions get called
describe("#randomSecureToken", () => {
// base64 -> 4 * (bytes/3) length -> if padding and rounding up is ignored for simplicity
// https://stackoverflow.com/a/13378842
@@ -44,16 +45,22 @@ describe("#randomSecureToken", () => {
});
});
+// TriliumNextTODO: should use mocks and assert that functions get called
describe.todo("#md5", () => {});
+// TriliumNextTODO: should use mocks and assert that functions get called
describe.todo("#hashedBlobId", () => {});
+// TriliumNextTODO: should use mocks and assert that functions get called
describe.todo("#toBase64", () => {});
+// TriliumNextTODO: should use mocks and assert that functions get called
describe.todo("#fromBase64", () => {});
+// TriliumNextTODO: should use mocks and assert that functions get called
describe.todo("#hmac", () => {});
+// TriliumNextTODO: should use mocks and assert that functions get called
describe.todo("#hash", () => {});
describe("#isEmptyOrWhitespace", () => {
@@ -101,8 +108,10 @@ describe("#sanitizeSqlIdentifier", () => {
});
+// TriliumNextTODO: should use mocks and assert that functions get called
describe.todo("#escapeHtml", () => {});
+// TriliumNextTODO: should use mocks and assert that functions get called
describe.todo("#unescapeHtml", () => {});
describe.todo("#toObject", () => {});
From d71e127828f7d9bc29c2d6f1e47b7dd435b24920 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Fri, 31 Jan 2025 23:32:44 +0100
Subject: [PATCH 35/50] refactor(server/utils): re-export escape/unescape
instead of wrapping them in function
-> since the functions did not do *anything* other than calling the escape/unescape module -> let's just re-export them directly
---
src/services/utils.ts | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/src/services/utils.ts b/src/services/utils.ts
index bc35fe10f..ba9751965 100644
--- a/src/services/utils.ts
+++ b/src/services/utils.ts
@@ -81,13 +81,9 @@ export function sanitizeSqlIdentifier(str: string) {
return str.replace(/[^A-Za-z0-9_]/g, "");
}
-export function escapeHtml(str: string) {
- return escape(str);
-}
+export const escapeHtml = escape;
-export function unescapeHtml(str: string) {
- return unescape(str);
-}
+export const unescapeHtml = unescape;
export function toObject(array: T[], fn: (item: T) => [K, V]): Record {
const obj: Record = {} as Record; // TODO: unsafe?
From f0ba056bb755c42ffe5bf2a690905775559fc4f5 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Fri, 31 Jan 2025 23:47:55 +0100
Subject: [PATCH 36/50] test(server/utils): add tests for escapeHtml &
unescapeHtml
---
src/services/utils.spec.ts | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index 9669acfd0..ad5c0841c 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -108,11 +108,17 @@ describe("#sanitizeSqlIdentifier", () => {
});
-// TriliumNextTODO: should use mocks and assert that functions get called
-describe.todo("#escapeHtml", () => {});
+describe("#escapeHtml", () => {
+ it("should re-export 'escape-html' npm module as escapeHtml", () => {
+ expect(utils.escapeHtml).toBeTypeOf("function");
+ });
+});
-// TriliumNextTODO: should use mocks and assert that functions get called
-describe.todo("#unescapeHtml", () => {});
+describe("#unescapeHtml", () => {
+ it("should re-export 'unescape' npm module as unescapeHtml", () => {
+ expect(utils.unescapeHtml).toBeTypeOf("function");
+ });
+});
describe.todo("#toObject", () => {});
From ab0c84a57eea12e970ab4c6ae312b08ee5c9fd94 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Sat, 1 Feb 2025 14:11:17 +0100
Subject: [PATCH 37/50] refactor(server/utils): use a "real" Map for toMap
---
src/services/notes.ts | 6 +++---
src/services/utils.ts | 12 +++++++-----
2 files changed, 10 insertions(+), 8 deletions(-)
diff --git a/src/services/notes.ts b/src/services/notes.ts
index 0faf08d74..26e51675e 100644
--- a/src/services/notes.ts
+++ b/src/services/notes.ts
@@ -731,13 +731,13 @@ function updateNoteData(noteId: string, content: string, attachments: Attachment
note.setContent(newContent, { forceFrontendReload });
if (attachments?.length > 0) {
- const existingAttachmentsByTitle = toMap(note.getAttachments({ includeContentLength: false }), "title");
+ const existingAttachmentsByTitle = toMap(note.getAttachments({ includeContentLength: false }), "title");
for (const { attachmentId, role, mime, title, position, content } of attachments) {
- if (attachmentId || !(title in existingAttachmentsByTitle)) {
+ const existingAttachment = existingAttachmentsByTitle.get(title);
+ if (attachmentId || !existingAttachment) {
note.saveAttachment({ attachmentId, role, mime, title, content, position });
} else {
- const existingAttachment = existingAttachmentsByTitle[title];
existingAttachment.role = role;
existingAttachment.mime = mime;
existingAttachment.position = position;
diff --git a/src/services/utils.ts b/src/services/utils.ts
index ba9751965..09e3964e5 100644
--- a/src/services/utils.ts
+++ b/src/services/utils.ts
@@ -247,13 +247,15 @@ export function normalize(str: string) {
return removeDiacritic(str).toLowerCase();
}
-export function toMap>(list: T[], key: keyof T): Record {
- const map: Record = {};
-
+export function toMap>(list: T[], key: keyof T) {
+ const map = new Map();
for (const el of list) {
- map[el[key]] = el;
+ const keyForMap = el[key];
+ if (!keyForMap) continue;
+ // TriliumNextTODO: do we need to handle the case when the same key is used?
+ // currently this will overwrite the existing entry in the map
+ map.set(keyForMap, el);
}
-
return map;
}
From e1795a0ad18732c52d227f2a00ea396ede45280c Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Sat, 1 Feb 2025 14:18:54 +0100
Subject: [PATCH 38/50] test(server/utils): add tests for toMap
---
src/services/utils.spec.ts | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index ad5c0841c..db4b94165 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -423,7 +423,28 @@ describe("#normalize", () => {
});
-describe.todo("#toMap", () => {});
+describe("#toMap", () => {
+ it("should return an instace of Map, with the correct size and keys, when supplied with a list and existing keys", () => {
+ const testList = [{title: "test", propA: "text", propB: 123 }, {title: "test2", propA: "prop2", propB: 456 }];
+ const result = utils.toMap(testList, "title");
+ expect(result).toBeInstanceOf(Map);
+ expect(result.size).toBe(2);
+ expect(Array.from(result.keys())).toStrictEqual(["test", "test2"]);
+ });
+ it("should return an instace of Map, with an empty size, when the supplied list does not contain the supplied key", () => {
+ const testList = [{title: "test", propA: "text", propB: 123 }, {title: "test2", propA: "prop2", propB: 456 }];
+ //@ts-expect-error - key is non-existing on supplied list type
+ const result = utils.toMap(testList, "nonExistingKey");
+ expect(result).toBeInstanceOf(Map);
+ expect(result.size).toBe(0);
+ });
+ it.fails("should correctly handle duplicate keys? (currently it will overwrite the entry, so returned size will be 1 instead of 2)", () => {
+ const testList = [{title: "testDupeTitle", propA: "text", propB: 123 }, {title: "testDupeTitle", propA: "prop2", propB: 456 }];
+ const result = utils.toMap(testList, "title");
+ expect(result).toBeInstanceOf(Map);
+ expect(result.size).toBe(2);
+ });
+});
describe("#envToBoolean", () => {
const testCases: TestCase[] = [
From 4917296d96fb31dd49cb6f08812d8c593096ad0b Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Sat, 1 Feb 2025 14:33:47 +0100
Subject: [PATCH 39/50] test(server/utils): add basic test for deferred
this needs to be expanded, but I don't fully understand
what the exact purpose is of this deferred fn
---
src/services/utils.spec.ts | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index db4b94165..9c1fd045b 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -381,7 +381,13 @@ describe("#timeLimit", () => {
});
-describe.todo("#deferred", () => {});
+describe("#deferred", () => {
+ it("should return a promise", () => {
+ const result = utils.deferred();
+ expect(result).toBeInstanceOf(Promise)
+ })
+ // TriliumNextTODO: Add further tests!
+});
describe("#removeDiacritic", () => {
From 5c904f100a3fabb0a0eddcce6fe7a70a83ac7ef2 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Sat, 1 Feb 2025 14:36:24 +0100
Subject: [PATCH 40/50] test(server/utils): add prettier ignore statement to
avoid newlines getting mangled
---
src/services/utils.spec.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index 9c1fd045b..ef4745517 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -124,7 +124,7 @@ describe.todo("#toObject", () => {});
describe("#stripTags", () => {
- //pre
+ //prettier-ignore
const htmlWithNewlines =
`abc
def
From 13e72c5e0a2aad8228eab8dcd4e15d6a96ae1f20 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Sat, 1 Feb 2025 15:00:47 +0100
Subject: [PATCH 41/50] test(server/utils): add basic test for toObject
---
src/services/utils.spec.ts | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index ef4745517..98cc5bfab 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -120,7 +120,20 @@ describe("#unescapeHtml", () => {
});
});
-describe.todo("#toObject", () => {});
+describe("#toObject", () => {
+ it("should return an object with keys and value being set from the supplied Function", () => {
+ type TestListEntry = { testPropA: string, testPropB: string };
+ type TestListFn = (testListEntry: TestListEntry) => [string, string];
+ const testList: [TestListEntry, TestListEntry] = [{ testPropA: "keyA", testPropB: "valueA" }, { testPropA: "keyB", testPropB: "valueB" }];
+ const fn: TestListFn = (testListEntry: TestListEntry) => [testListEntry.testPropA + "_fn", testListEntry.testPropB + "_fn"];
+
+ const result = utils.toObject(testList, fn);
+ expect(result).toStrictEqual({
+ "keyA_fn": "valueA_fn",
+ "keyB_fn": "valueB_fn"
+ });
+ });
+});
describe("#stripTags", () => {
From 08c32da0d2c11014178bd59ed144be2a34b992a6 Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Sat, 1 Feb 2025 15:06:07 +0100
Subject: [PATCH 42/50] test(server/utils): fix warnings and explicitly await
timeLimit tests
---
src/services/utils.spec.ts | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index 98cc5bfab..9198ce9c7 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -345,7 +345,7 @@ describe("#timeLimit", () => {
}, 200);
//rej("rejected!");
});
- expect(utils.timeLimit(testPromise, 1_000)).resolves.toBe(resolvedValue);
+ await expect(utils.timeLimit(testPromise, 1_000)).resolves.toBe(resolvedValue);
});
it("when promise execution rejects within timeout, it should return the original promises' rejected value, not the custom set one", async () => {
@@ -356,7 +356,7 @@ describe("#timeLimit", () => {
rej(rejectedValue);
}, 100);
});
- expect(utils.timeLimit(testPromise, 200, "Custom Error")).rejects.toThrow(rejectedValue)
+ await expect(utils.timeLimit(testPromise, 200, "Custom Error")).rejects.toThrow(rejectedValue)
});
it("when promise execution exceeds the set timeout, and 'errorMessage' is NOT set, it should reject the promise and display default error message", async () => {
@@ -366,7 +366,7 @@ describe("#timeLimit", () => {
}, 500);
//rej("rejected!");
});
- expect(utils.timeLimit(testPromise, 200)).rejects.toThrow(`Process exceeded time limit 200`)
+ await expect(utils.timeLimit(testPromise, 200)).rejects.toThrow(`Process exceeded time limit 200`)
});
it("when promise execution exceeds the set timeout, and 'errorMessage' is set, it should reject the promise and display set error message", async () => {
@@ -377,7 +377,7 @@ describe("#timeLimit", () => {
}, 500);
//rej("rejected!");
});
- expect(utils.timeLimit(testPromise, 200, customErrorMsg)).rejects.toThrow(customErrorMsg)
+ await expect(utils.timeLimit(testPromise, 200, customErrorMsg)).rejects.toThrow(customErrorMsg)
});
// TriliumNextTODO: since TS avoids this from ever happening – do we need this check?
From e710409134ae20879ca6b25591654f444a96533f Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Sat, 1 Feb 2025 15:10:31 +0100
Subject: [PATCH 43/50] test(server/utils): remove completed TODO
---
src/services/utils.spec.ts | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts
index 9198ce9c7..3451a881d 100644
--- a/src/services/utils.spec.ts
+++ b/src/services/utils.spec.ts
@@ -246,9 +246,6 @@ describe.todo("#quoteRegex", () => {});
describe.todo("#replaceAll", () => {});
-// TriliumNextTODO move existing formatDownloadTitle in here
-// describe.todo("#formatDownloadTitle", () => {});
-
describe("#removeTextFileExtension", () => {
const testCases: TestCase[] = [
["w/ 'test.md' it should strip '.md'", ["test.md"], "test"],
From 61e56023d42dcb92ae0742ea973dca307a2a6c6f Mon Sep 17 00:00:00 2001
From: Panagiotis Papadopoulos
Date: Sun, 2 Feb 2025 12:47:27 +0100
Subject: [PATCH 44/50] refactor(electron-forge/deb): make desktop.ejs easier
to read
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
in theory we could use variables in ejs, but unfortunately electron uses lodash template to create the file, which itself DOES NOT like any let/const/var assignments – wasted a bit of time finding this out, before coming up with this solution
---
bin/electron-forge/desktop.ejs | 27 ++++++++++++++++-----------
1 file changed, 16 insertions(+), 11 deletions(-)
diff --git a/bin/electron-forge/desktop.ejs b/bin/electron-forge/desktop.ejs
index f803f37b2..32430cd2a 100644
--- a/bin/electron-forge/desktop.ejs
+++ b/bin/electron-forge/desktop.ejs
@@ -1,12 +1,17 @@
[Desktop Entry]
-<% if (productName) { %>Name=<%= productName %>
-<% } %><% if (description) { %>Comment=<%= description %>
-<% } %><% if (genericName) { %>GenericName=<%= genericName %>
-<% } %><% if (name) { %>Exec=<%= name %> %U
-Icon=<%= name %>
-<% } %>Type=Application
-StartupNotify=true
-<% if (productName) { %>StartupWMClass=<%= productName %>
-<% } if (categories && categories.length) { %>Categories=<%= categories.join(';') %>;
-<% } %><% if (mimeType && mimeType.length) { %>MimeType=<%= mimeType.join(';') %>;
-<% } %>
\ No newline at end of file
+<%=
+Object.entries({
+ "Name": productName,
+ "Comment": description,
+ "GenericName": genericName,
+ "Exec": name ? `${name} %U` : undefined,
+ "Icon": name,
+ "Type": "Application",
+ "StartupNotify": "true",
+ "StartupWMClass": productName,
+ "Categories": categories?.length ? `${categories.join(";")};` : undefined,
+ "MimeType": mimeType?.length ? `${mimeType.join(";")};` : undefined
+})
+.map(line => line[1] ? line.join("=") : undefined)
+.filter(line => !!line)
+.join("\n")%>
\ No newline at end of file
From 528fe1904cabdb73bdfd046e3343515dee22582f Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 3 Feb 2025 01:09:17 +0000
Subject: [PATCH 45/50] chore(deps): update dependency @playwright/test to
v1.50.1
---
package-lock.json | 24 ++++++++++++------------
package.json | 2 +-
2 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 3aac1a975..bc3f77601 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -113,7 +113,7 @@
"@electron-forge/maker-zip": "7.6.1",
"@electron-forge/plugin-auto-unpack-natives": "7.6.1",
"@electron/rebuild": "3.7.1",
- "@playwright/test": "1.50.0",
+ "@playwright/test": "1.50.1",
"@types/archiver": "6.0.3",
"@types/better-sqlite3": "7.6.12",
"@types/bootstrap": "5.2.10",
@@ -2880,13 +2880,13 @@
}
},
"node_modules/@playwright/test": {
- "version": "1.50.0",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.50.0.tgz",
- "integrity": "sha512-ZGNXbt+d65EGjBORQHuYKj+XhCewlwpnSd/EDuLPZGSiEWmgOJB5RmMCCYGy5aMfTs9wx61RivfDKi8H/hcMvw==",
+ "version": "1.50.1",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.50.1.tgz",
+ "integrity": "sha512-Jii3aBg+CEDpgnuDxEp/h7BimHcUTDlpEtce89xEumlJ5ef2hqepZ+PWp1DDpYC/VO9fmWVI1IlEaoI5fK9FXQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "playwright": "1.50.0"
+ "playwright": "1.50.1"
},
"bin": {
"playwright": "cli.js"
@@ -13320,13 +13320,13 @@
}
},
"node_modules/playwright": {
- "version": "1.50.0",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.0.tgz",
- "integrity": "sha512-+GinGfGTrd2IfX1TA4N2gNmeIksSb+IAe589ZH+FlmpV3MYTx6+buChGIuDLQwrGNCw2lWibqV50fU510N7S+w==",
+ "version": "1.50.1",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.1.tgz",
+ "integrity": "sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "playwright-core": "1.50.0"
+ "playwright-core": "1.50.1"
},
"bin": {
"playwright": "cli.js"
@@ -13339,9 +13339,9 @@
}
},
"node_modules/playwright-core": {
- "version": "1.50.0",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.0.tgz",
- "integrity": "sha512-CXkSSlr4JaZs2tZHI40DsZUN/NIwgaUPsyLuOAaIZp2CyF2sN5MM5NJsyB188lFSSozFxQ5fPT4qM+f0tH/6wQ==",
+ "version": "1.50.1",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.1.tgz",
+ "integrity": "sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
diff --git a/package.json b/package.json
index fa88fd4fd..6ec4319d9 100644
--- a/package.json
+++ b/package.json
@@ -155,7 +155,7 @@
"@electron-forge/maker-zip": "7.6.1",
"@electron-forge/plugin-auto-unpack-natives": "7.6.1",
"@electron/rebuild": "3.7.1",
- "@playwright/test": "1.50.0",
+ "@playwright/test": "1.50.1",
"@types/archiver": "6.0.3",
"@types/better-sqlite3": "7.6.12",
"@types/bootstrap": "5.2.10",
From 6facc3d05ce8c3f58483ca1b10fb51205e23ad9f Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 4 Feb 2025 01:45:13 +0000
Subject: [PATCH 46/50] chore(deps): update vitest monorepo to v3.0.5
---
package-lock.json | 100 +++++++++++++++++++++++-----------------------
package.json | 4 +-
2 files changed, 52 insertions(+), 52 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 3aac1a975..6ca644c9a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -151,7 +151,7 @@
"@types/ws": "8.5.14",
"@types/xml2js": "0.4.14",
"@types/yargs": "17.0.33",
- "@vitest/coverage-v8": "3.0.4",
+ "@vitest/coverage-v8": "3.0.5",
"cross-env": "7.0.3",
"electron": "34.0.2",
"esm": "3.2.25",
@@ -166,7 +166,7 @@
"tsx": "4.19.2",
"typedoc": "0.27.6",
"typescript": "5.7.3",
- "vitest": "3.0.4",
+ "vitest": "3.0.5",
"webpack": "5.97.1",
"webpack-cli": "6.0.1",
"webpack-dev-middleware": "7.4.2"
@@ -4163,9 +4163,9 @@
}
},
"node_modules/@vitest/coverage-v8": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.4.tgz",
- "integrity": "sha512-f0twgRCHgbs24Dp8cLWagzcObXMcuKtAwgxjJV/nnysPAJJk1JiKu/W0gIehZLmkljhJXU/E0/dmuQzsA/4jhA==",
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.5.tgz",
+ "integrity": "sha512-zOOWIsj5fHh3jjGwQg+P+J1FW3s4jBu1Zqga0qW60yutsBtqEqNEJKWYh7cYn1yGD+1bdPsPdC/eL4eVK56xMg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4186,8 +4186,8 @@
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
- "@vitest/browser": "3.0.4",
- "vitest": "3.0.4"
+ "@vitest/browser": "3.0.5",
+ "vitest": "3.0.5"
},
"peerDependenciesMeta": {
"@vitest/browser": {
@@ -4196,14 +4196,14 @@
}
},
"node_modules/@vitest/expect": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.4.tgz",
- "integrity": "sha512-Nm5kJmYw6P2BxhJPkO3eKKhGYKRsnqJqf+r0yOGRKpEP+bSCBDsjXgiu1/5QFrnPMEgzfC38ZEjvCFgaNBC0Eg==",
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.5.tgz",
+ "integrity": "sha512-nNIOqupgZ4v5jWuQx2DSlHLEs7Q4Oh/7AYwNyE+k0UQzG7tSmjPXShUikn1mpNGzYEN2jJbTvLejwShMitovBA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/spy": "3.0.4",
- "@vitest/utils": "3.0.4",
+ "@vitest/spy": "3.0.5",
+ "@vitest/utils": "3.0.5",
"chai": "^5.1.2",
"tinyrainbow": "^2.0.0"
},
@@ -4212,13 +4212,13 @@
}
},
"node_modules/@vitest/mocker": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.4.tgz",
- "integrity": "sha512-gEef35vKafJlfQbnyOXZ0Gcr9IBUsMTyTLXsEQwuyYAerpHqvXhzdBnDFuHLpFqth3F7b6BaFr4qV/Cs1ULx5A==",
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.5.tgz",
+ "integrity": "sha512-CLPNBFBIE7x6aEGbIjaQAX03ZZlBMaWwAjBdMkIf/cAn6xzLTiM3zYqO/WAbieEjsAZir6tO71mzeHZoodThvw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/spy": "3.0.4",
+ "@vitest/spy": "3.0.5",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.17"
},
@@ -4239,9 +4239,9 @@
}
},
"node_modules/@vitest/pretty-format": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.4.tgz",
- "integrity": "sha512-ts0fba+dEhK2aC9PFuZ9LTpULHpY/nd6jhAQ5IMU7Gaj7crPCTdCFfgvXxruRBLFS+MLraicCuFXxISEq8C93g==",
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.5.tgz",
+ "integrity": "sha512-CjUtdmpOcm4RVtB+up8r2vVDLR16Mgm/bYdkGFe3Yj/scRfCpbSi2W/BDSDcFK7ohw8UXvjMbOp9H4fByd/cOA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4252,13 +4252,13 @@
}
},
"node_modules/@vitest/runner": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.4.tgz",
- "integrity": "sha512-dKHzTQ7n9sExAcWH/0sh1elVgwc7OJ2lMOBrAm73J7AH6Pf9T12Zh3lNE1TETZaqrWFXtLlx3NVrLRb5hCK+iw==",
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.5.tgz",
+ "integrity": "sha512-BAiZFityFexZQi2yN4OX3OkJC6scwRo8EhRB0Z5HIGGgd2q+Nq29LgHU/+ovCtd0fOfXj5ZI6pwdlUmC5bpi8A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/utils": "3.0.4",
+ "@vitest/utils": "3.0.5",
"pathe": "^2.0.2"
},
"funding": {
@@ -4273,13 +4273,13 @@
"license": "MIT"
},
"node_modules/@vitest/snapshot": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.4.tgz",
- "integrity": "sha512-+p5knMLwIk7lTQkM3NonZ9zBewzVp9EVkVpvNta0/PlFWpiqLaRcF4+33L1it3uRUCh0BGLOaXPPGEjNKfWb4w==",
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.5.tgz",
+ "integrity": "sha512-GJPZYcd7v8QNUJ7vRvLDmRwl+a1fGg4T/54lZXe+UOGy47F9yUfE18hRCtXL5aHN/AONu29NGzIXSVFh9K0feA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "3.0.4",
+ "@vitest/pretty-format": "3.0.5",
"magic-string": "^0.30.17",
"pathe": "^2.0.2"
},
@@ -4295,9 +4295,9 @@
"license": "MIT"
},
"node_modules/@vitest/spy": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.4.tgz",
- "integrity": "sha512-sXIMF0oauYyUy2hN49VFTYodzEAu744MmGcPR3ZBsPM20G+1/cSW/n1U+3Yu/zHxX2bIDe1oJASOkml+osTU6Q==",
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.5.tgz",
+ "integrity": "sha512-5fOzHj0WbUNqPK6blI/8VzZdkBlQLnT25knX0r4dbZI9qoZDf3qAdjoMmDcLG5A83W6oUUFJgUd0EYBc2P5xqg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4308,13 +4308,13 @@
}
},
"node_modules/@vitest/utils": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.4.tgz",
- "integrity": "sha512-8BqC1ksYsHtbWH+DfpOAKrFw3jl3Uf9J7yeFh85Pz52IWuh1hBBtyfEbRNNZNjl8H8A5yMLH9/t+k7HIKzQcZQ==",
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.5.tgz",
+ "integrity": "sha512-N9AX0NUoUtVwKwy21JtwzaqR5L5R5A99GAbrHfCCXK1lp593i/3AZAXhSP43wRQuxYsflrdzEfXZFo1reR1Nkg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "3.0.4",
+ "@vitest/pretty-format": "3.0.5",
"loupe": "^3.1.2",
"tinyrainbow": "^2.0.0"
},
@@ -16856,9 +16856,9 @@
}
},
"node_modules/vite-node": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.4.tgz",
- "integrity": "sha512-7JZKEzcYV2Nx3u6rlvN8qdo3QV7Fxyt6hx+CCKz9fbWxdX5IvUOmTWEAxMrWxaiSf7CKGLJQ5rFu8prb/jBjOA==",
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.5.tgz",
+ "integrity": "sha512-02JEJl7SbtwSDJdYS537nU6l+ktdvcREfLksk/NDAqtdKWGqHl+joXzEubHROmS3E6pip+Xgu2tFezMu75jH7A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -17350,19 +17350,19 @@
}
},
"node_modules/vitest": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.4.tgz",
- "integrity": "sha512-6XG8oTKy2gnJIFTHP6LD7ExFeNLxiTkK3CfMvT7IfR8IN+BYICCf0lXUQmX7i7JoxUP8QmeP4mTnWXgflu4yjw==",
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.5.tgz",
+ "integrity": "sha512-4dof+HvqONw9bvsYxtkfUp2uHsTN9bV2CZIi1pWgoFpL1Lld8LA1ka9q/ONSsoScAKG7NVGf2stJTI7XRkXb2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/expect": "3.0.4",
- "@vitest/mocker": "3.0.4",
- "@vitest/pretty-format": "^3.0.4",
- "@vitest/runner": "3.0.4",
- "@vitest/snapshot": "3.0.4",
- "@vitest/spy": "3.0.4",
- "@vitest/utils": "3.0.4",
+ "@vitest/expect": "3.0.5",
+ "@vitest/mocker": "3.0.5",
+ "@vitest/pretty-format": "^3.0.5",
+ "@vitest/runner": "3.0.5",
+ "@vitest/snapshot": "3.0.5",
+ "@vitest/spy": "3.0.5",
+ "@vitest/utils": "3.0.5",
"chai": "^5.1.2",
"debug": "^4.4.0",
"expect-type": "^1.1.0",
@@ -17374,7 +17374,7 @@
"tinypool": "^1.0.2",
"tinyrainbow": "^2.0.0",
"vite": "^5.0.0 || ^6.0.0",
- "vite-node": "3.0.4",
+ "vite-node": "3.0.5",
"why-is-node-running": "^2.3.0"
},
"bin": {
@@ -17390,8 +17390,8 @@
"@edge-runtime/vm": "*",
"@types/debug": "^4.1.12",
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
- "@vitest/browser": "3.0.4",
- "@vitest/ui": "3.0.4",
+ "@vitest/browser": "3.0.5",
+ "@vitest/ui": "3.0.5",
"happy-dom": "*",
"jsdom": "*"
},
diff --git a/package.json b/package.json
index fa88fd4fd..d92ce0dad 100644
--- a/package.json
+++ b/package.json
@@ -193,7 +193,7 @@
"@types/ws": "8.5.14",
"@types/xml2js": "0.4.14",
"@types/yargs": "17.0.33",
- "@vitest/coverage-v8": "3.0.4",
+ "@vitest/coverage-v8": "3.0.5",
"cross-env": "7.0.3",
"electron": "34.0.2",
"esm": "3.2.25",
@@ -208,7 +208,7 @@
"tsx": "4.19.2",
"typedoc": "0.27.6",
"typescript": "5.7.3",
- "vitest": "3.0.4",
+ "vitest": "3.0.5",
"webpack": "5.97.1",
"webpack-cli": "6.0.1",
"webpack-dev-middleware": "7.4.2"
From d908c9044b63468d9b93e3b6618b079475180fea Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 4 Feb 2025 01:45:21 +0000
Subject: [PATCH 47/50] fix(deps): update dependency semver to v7.7.1
---
package-lock.json | 8 ++++----
package.json | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 3aac1a975..a62688b2e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -86,7 +86,7 @@
"sanitize-filename": "1.6.3",
"sanitize-html": "2.14.0",
"sax": "1.4.1",
- "semver": "7.7.0",
+ "semver": "7.7.1",
"serve-favicon": "2.5.0",
"session-file-store": "1.5.0",
"source-map-support": "0.5.21",
@@ -14721,9 +14721,9 @@
}
},
"node_modules/semver": {
- "version": "7.7.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.0.tgz",
- "integrity": "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ==",
+ "version": "7.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
+ "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
diff --git a/package.json b/package.json
index fa88fd4fd..01cd709bd 100644
--- a/package.json
+++ b/package.json
@@ -131,7 +131,7 @@
"sanitize-filename": "1.6.3",
"sanitize-html": "2.14.0",
"sax": "1.4.1",
- "semver": "7.7.0",
+ "semver": "7.7.1",
"serve-favicon": "2.5.0",
"session-file-store": "1.5.0",
"source-map-support": "0.5.21",
From e126d7be34daf0efa0b33085d07bad300381ec8f Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 4 Feb 2025 01:45:31 +0000
Subject: [PATCH 48/50] chore(deps): update dependency @types/node to v22.13.1
---
package-lock.json | 8 ++++----
package.json | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 3aac1a975..4753df223 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -136,7 +136,7 @@
"@types/leaflet-gpx": "1.3.7",
"@types/mime-types": "2.1.4",
"@types/multer": "1.4.12",
- "@types/node": "22.12.0",
+ "@types/node": "22.13.1",
"@types/react": "18.3.18",
"@types/safe-compare": "1.1.2",
"@types/sanitize-html": "2.13.0",
@@ -3909,9 +3909,9 @@
}
},
"node_modules/@types/node": {
- "version": "22.12.0",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.12.0.tgz",
- "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==",
+ "version": "22.13.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.1.tgz",
+ "integrity": "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
diff --git a/package.json b/package.json
index fa88fd4fd..119781197 100644
--- a/package.json
+++ b/package.json
@@ -178,7 +178,7 @@
"@types/leaflet-gpx": "1.3.7",
"@types/mime-types": "2.1.4",
"@types/multer": "1.4.12",
- "@types/node": "22.12.0",
+ "@types/node": "22.13.1",
"@types/react": "18.3.18",
"@types/safe-compare": "1.1.2",
"@types/sanitize-html": "2.13.0",
From 0af08c2a7bcd36081320492d88e9c06c2090dcc7 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 4 Feb 2025 18:56:44 +0000
Subject: [PATCH 49/50] chore(deps): update dependency vitest to v3.0.5
[security]
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index fa88fd4fd..ad59d0051 100644
--- a/package.json
+++ b/package.json
@@ -208,7 +208,7 @@
"tsx": "4.19.2",
"typedoc": "0.27.6",
"typescript": "5.7.3",
- "vitest": "3.0.4",
+ "vitest": "3.0.5",
"webpack": "5.97.1",
"webpack-cli": "6.0.1",
"webpack-dev-middleware": "7.4.2"
From ce453098189f700e64a6be582f69f955e22332dc Mon Sep 17 00:00:00 2001
From: Elian Doran
Date: Tue, 4 Feb 2025 21:15:47 +0200
Subject: [PATCH 50/50] feat(deps): remove dependency on semver
---
package-lock.json | 9 ---------
package.json | 2 --
src/services/utils.ts | 45 ++++++++++++++++++++++++++++++++++++++++++-
src/www.ts | 7 ++++---
4 files changed, 48 insertions(+), 15 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index a62688b2e..382a2088a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -86,7 +86,6 @@
"sanitize-filename": "1.6.3",
"sanitize-html": "2.14.0",
"sax": "1.4.1",
- "semver": "7.7.1",
"serve-favicon": "2.5.0",
"session-file-store": "1.5.0",
"source-map-support": "0.5.21",
@@ -141,7 +140,6 @@
"@types/safe-compare": "1.1.2",
"@types/sanitize-html": "2.13.0",
"@types/sax": "1.2.7",
- "@types/semver": "7.5.8",
"@types/serve-favicon": "2.5.7",
"@types/session-file-store": "1.2.5",
"@types/source-map-support": "0.5.10",
@@ -4002,13 +4000,6 @@
"@types/node": "*"
}
},
- "node_modules/@types/semver": {
- "version": "7.5.8",
- "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
- "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@types/send": {
"version": "0.17.4",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
diff --git a/package.json b/package.json
index 01cd709bd..edbe5beb3 100644
--- a/package.json
+++ b/package.json
@@ -131,7 +131,6 @@
"sanitize-filename": "1.6.3",
"sanitize-html": "2.14.0",
"sax": "1.4.1",
- "semver": "7.7.1",
"serve-favicon": "2.5.0",
"session-file-store": "1.5.0",
"source-map-support": "0.5.21",
@@ -183,7 +182,6 @@
"@types/safe-compare": "1.1.2",
"@types/sanitize-html": "2.13.0",
"@types/sax": "1.2.7",
- "@types/semver": "7.5.8",
"@types/serve-favicon": "2.5.7",
"@types/session-file-store": "1.2.5",
"@types/source-map-support": "0.5.10",
diff --git a/src/services/utils.ts b/src/services/utils.ts
index b00c1e488..b4d7ad67c 100644
--- a/src/services/utils.ts
+++ b/src/services/utils.ts
@@ -324,6 +324,48 @@ export function getResourceDir() {
}
}
+// TODO: Deduplicate with src/public/app/services/utils.ts
+/**
+ * Compares two semantic version strings.
+ * Returns:
+ * 1 if v1 is greater than v2
+ * 0 if v1 is equal to v2
+ * -1 if v1 is less than v2
+ *
+ * @param v1 First version string
+ * @param v2 Second version string
+ * @returns
+ */
+function compareVersions(v1: string, v2: string): number {
+ // Remove 'v' prefix and everything after dash if present
+ v1 = v1.replace(/^v/, "").split("-")[0];
+ v2 = v2.replace(/^v/, "").split("-")[0];
+
+ const v1parts = v1.split(".").map(Number);
+ const v2parts = v2.split(".").map(Number);
+
+ // Pad shorter version with zeros
+ while (v1parts.length < 3) v1parts.push(0);
+ while (v2parts.length < 3) v2parts.push(0);
+
+ // Compare major version
+ if (v1parts[0] !== v2parts[0]) {
+ return v1parts[0] > v2parts[0] ? 1 : -1;
+ }
+
+ // Compare minor version
+ if (v1parts[1] !== v2parts[1]) {
+ return v1parts[1] > v2parts[1] ? 1 : -1;
+ }
+
+ // Compare patch version
+ if (v1parts[2] !== v2parts[2]) {
+ return v1parts[2] > v2parts[2] ? 1 : -1;
+ }
+
+ return 0;
+}
+
export default {
randomSecureToken,
randomString,
@@ -360,5 +402,6 @@ export default {
getResourceDir,
isMac,
isWindows,
- envToBoolean
+ envToBoolean,
+ compareVersions
};
diff --git a/src/www.ts b/src/www.ts
index 258f0f272..d442ba35f 100644
--- a/src/www.ts
+++ b/src/www.ts
@@ -12,7 +12,8 @@ import ws from "./services/ws.js";
import utils from "./services/utils.js";
import port from "./services/port.js";
import host from "./services/host.js";
-import semver from "semver";
+
+const MINIMUM_NODE_VERSION = "22.0.0";
// setup basic error handling even before requiring dependencies, since those can produce errors as well
@@ -32,8 +33,8 @@ function exit() {
process.on("SIGINT", exit);
process.on("SIGTERM", exit);
-if (!semver.satisfies(process.version, ">=10.5.0")) {
- console.error("Trilium only supports node.js 10.5 and later");
+if (utils.compareVersions(process.version, MINIMUM_NODE_VERSION) < 0) {
+ console.error(`\nTrilium requires Node.js ${MINIMUM_NODE_VERSION} and later.\n`);
process.exit(1);
}