From 31fb8eb3d3531506ebdabe6f96d70821fb596d1f Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sat, 18 Jan 2025 12:42:55 +0100 Subject: [PATCH 1/3] test(services/data_dir): add getTriliumDataDir tests since we *need* mocks to tests getTriliumDataDir, I had to change a bit the setup structure of the tests as well -> functions needs to be imported dynamically *after* the mocks are registered --- spec-es6/data_dir.spec.ts | 258 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 250 insertions(+), 8 deletions(-) diff --git a/spec-es6/data_dir.spec.ts b/spec-es6/data_dir.spec.ts index 4ef4f30d9..be16b9bea 100644 --- a/spec-es6/data_dir.spec.ts +++ b/spec-es6/data_dir.spec.ts @@ -1,9 +1,74 @@ -import { describe, it, expect } from "vitest"; +import { describe, it, expect, beforeEach, vi } from "vitest"; + +import type { + getTriliumDataDir as getTriliumDataDirType, + getDataDirs as getDataDirsType, + getPlatformAppDataDir as getPlatformAppDataDirType, +} from "../src/services/data_dir"; + + +describe("data_dir.ts unit tests", async () => { + + + let getTriliumDataDir: typeof getTriliumDataDirType; + let getPlatformAppDataDir: typeof getPlatformAppDataDirType; + let getDataDirs: typeof getDataDirsType; + + + + const mockFn = { + existsSyncMock: vi.fn(), + mkdirSyncMock: vi.fn(), + osHomedirMock: vi.fn(), + osPlatformMock: vi.fn(), + pathJoinMock: vi.fn() + }; + + // using doMock, to avoid hoisting, so that we can use the mockFn object + // to collect all mocked Fns + vi.doMock("node:fs", () => { + return { + default: { + existsSync: mockFn.existsSyncMock, + mkdirSync: mockFn.mkdirSyncMock + } + }; + }); + + vi.doMock("node:os", () => { + return { + default: { + homedir: mockFn.osHomedirMock, + platform: mockFn.osPlatformMock + } + }; + }); + + vi.doMock("path", () => { + return { + join: mockFn.pathJoinMock + }; + }); + + // import function to test now, after creating the mocks + ({ getTriliumDataDir } = await import("../src/services/data_dir.ts")); + ({ getPlatformAppDataDir } = await import("../src/services/data_dir.ts")); + ({ getDataDirs } = await import("../src/services/data_dir.ts")); + + // helper to reset call counts + const resetAllMocks = () => { + Object.values(mockFn).forEach((mockedFn) => { + mockedFn.mockReset() + }); + }; + + // helper to set mocked Platform + const setMockPlatform = (osPlatform: string, homedir: string, pathJoin: string) => { + mockFn.osPlatformMock.mockImplementation(() => osPlatform); + mockFn.osHomedirMock.mockImplementation(() => homedir); + mockFn.pathJoinMock.mockImplementation(() => pathJoin); + }; -import { getPlatformAppDataDir, getDataDirs} from "../src/services/data_dir.ts" - - -describe("data_dir.ts unit tests", () => { describe("#getPlatformAppDataDir()", () => { @@ -64,8 +129,179 @@ describe("data_dir.ts unit tests", () => { }) - describe.todo("#getTriliumDataDir", () => { - // TODO + describe("#getTriliumDataDir", async () => { + + + beforeEach(() => { + // make sure these are not set + delete process.env.TRILIUM_DATA_DIR; + delete process.env.APPDATA; + + resetAllMocks(); + }); + + + describe("case A – process.env.TRILIUM_DATA_DIR is set", () => { + it("when folder exists – it should return the path, without attempting to create the folder", async () => { + const mockTriliumDataPath = "/home/mock/trilium-data-ENV-A1"; + process.env.TRILIUM_DATA_DIR = mockTriliumDataPath; + + // set fs.existsSync to true, i.e. the folder does exist + mockFn.existsSyncMock.mockImplementation(() => true); + + const result = getTriliumDataDir("trilium-data"); + + // createDirIfNotExisting should call existsync 1 time and mkdirSync 0 times -> as it does not need to create the folder + // and return value should be TRILIUM_DATA_DIR value from process.env + expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(1); + expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(0); + expect(result).toEqual(process.env.TRILIUM_DATA_DIR); + }); + + it("when folder does not exist – it should attempt to create the folder and return the path", async () => { + const mockTriliumDataPath = "/home/mock/trilium-data-ENV-A2"; + process.env.TRILIUM_DATA_DIR = mockTriliumDataPath; + + // set fs.existsSync mock to return false, i.e. the folder does not exist + mockFn.existsSyncMock.mockImplementation(() => false); + + const result = getTriliumDataDir("trilium-data"); + + // createDirIfNotExisting should call existsync 1 time and mkdirSync 1 times -> as it has to create the folder + // and return value should be TRILIUM_DATA_DIR value from process.env + expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(1); + expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1); + expect(result).toEqual(process.env.TRILIUM_DATA_DIR); + }); + }); + + describe("case B – process.env.TRILIUM_DATA_DIR is not set and Trilium folder is existing in platform's home dir", () => { + it("it should check if folder exists and return it", async () => { + const homedir = "/home/mock"; + const dataDirName = "trilium-data"; + const mockTriliumDataPath = `${homedir}/${dataDirName}`; + + mockFn.pathJoinMock.mockImplementation(() => mockTriliumDataPath); + + // set fs.existsSync to true, i.e. the folder does exist + mockFn.existsSyncMock.mockImplementation(() => true); + + const result = getTriliumDataDir(dataDirName); + + expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(1); + expect(result).toEqual(mockTriliumDataPath); + }); + }); + + describe("case C – process.env.TRILIUM_DATA_DIR is not set and Trilium folder is not existing in platform's home dir", () => { + it("w/ Platform 'Linux', an existing App Data Folder (~/.local/share) but non-existing Trilium dir (~/.local/share/trilium-data) – it should attempt to create the dir", async () => { + const homedir = "/home/mock"; + const dataDirName = "trilium-data"; + const mockPlatformDataPath = `${homedir}/.local/share/${dataDirName}`; + + // mock set: os.platform, os.homedir and pathJoin return values + setMockPlatform("linux", homedir, mockPlatformDataPath); + + // use Generator to precisely control order of fs.existSync return values + const existsSyncMockGen = (function* () { + // 1) fs.existSync -> case B + yield false; + // 2) fs.existSync -> case C -> checking if default OS PlatformAppDataDir exists + yield true; + // 3) fs.existSync -> case C -> checking if Trilium Data folder exists + yield false; + })(); + + mockFn.existsSyncMock.mockImplementation(() => existsSyncMockGen.next().value); + + const result = getTriliumDataDir(dataDirName); + + expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(3); + expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1); + expect(result).toEqual(mockPlatformDataPath); + }); + + it("w/ Platform Linux, an existing App Data Folder (~/.local/share) AND an existing Trilium Data dir – it should return path to the dir", async () => { + const homedir = "/home/mock"; + const dataDirName = "trilium-data"; + const mockPlatformDataPath = `${homedir}/.local/share/${dataDirName}`; + + // mock set: os.platform, os.homedir and pathJoin return values + setMockPlatform("linux", homedir, mockPlatformDataPath); + + // use Generator to precisely control order of fs.existSync return values + const existsSyncMockGen = (function* () { + // 1) fs.existSync -> case B + yield false; + // 2) fs.existSync -> case C -> checking if default OS PlatformAppDataDir exists + yield true; + // 3) fs.existSync -> case C -> checking if Trilium Data folder exists + yield true; + })(); + + mockFn.existsSyncMock.mockImplementation(() => existsSyncMockGen.next().value); + + const result = getTriliumDataDir(dataDirName); + + expect(result).toEqual(mockPlatformDataPath); + expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(3); + expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(0); + }); + + it("w/ Platform 'win32' and set process.env.APPDATA behaviour", async () => { + const homedir = "C:\\Users\\mock"; + const dataDirName = "trilium-data"; + const appDataDir = `${homedir}\\AppData\\Roaming`; + const mockPlatformDataPath = `${appDataDir}\\${dataDirName}`; + process.env.APPDATA = `${appDataDir}`; + + // mock set: os.platform, os.homedir and pathJoin return values + setMockPlatform("win32", homedir, mockPlatformDataPath); + + // use Generator to precisely control order of fs.existSync return values + const existsSyncMockGen = (function* () { + // 1) fs.existSync -> case B + yield false; + // 2) fs.existSync -> case C -> checking if default OS PlatformAppDataDir exists + yield true; + // 3) fs.existSync -> case C -> checking if Trilium Data folder exists + yield false; + })(); + + mockFn.existsSyncMock.mockImplementation(() => existsSyncMockGen.next().value); + + const result = getTriliumDataDir(dataDirName); + + expect(result).toEqual(mockPlatformDataPath); + expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(3); + expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1); + }); + }); + + describe("case D – fallback to creating Trilium folder in home dir", () => { + it("w/ unknown PlatformAppDataDir it should attempt to create the folder in the homefolder", async () => { + const homedir = "/home/mock"; + const dataDirName = "trilium-data"; + const mockPlatformDataPath = `${homedir}/${dataDirName}`; + + setMockPlatform("aix", homedir, mockPlatformDataPath); + + const existsSyncMockGen = (function* () { + // first fs.existSync -> case B -> checking if folder exists in home folder + yield false; + // second fs.existSync -> case D -> triggered by createDirIfNotExisting + yield false; + })(); + + mockFn.existsSyncMock.mockImplementation(() => existsSyncMockGen.next().value); + + const result = getTriliumDataDir(dataDirName); + + expect(result).toEqual(mockPlatformDataPath); + expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(2); + expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1); + }); + }); }) describe("#getDataDirs()", () => { @@ -107,12 +343,18 @@ describe("data_dir.ts unit tests", () => { // make sure values are undefined setMockedEnv(null); - const mockDataDir = "/home/test/MOCK_TRILIUM_DATA_DIR" + // mock pathJoin implementation to just return mockDataDir + const mockDataDir = "/home/test/MOCK_TRILIUM_DATA_DIR"; + mockFn.pathJoinMock.mockImplementation(() => mockDataDir); + const result = getDataDirs(mockDataDir); for (const key in result) { expect(result[key].startsWith(mockDataDir)).toBeTruthy() } + + mockFn.pathJoinMock.mockReset() + }) it("should ignore attempts to change a property on the returned object", () => { From dba0ef4945b01c3a565146fd3cb5d25dc760766f Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sat, 18 Jan 2025 12:46:38 +0100 Subject: [PATCH 2/3] test(services/data_dir): make test descriptions a bit more readable --- spec-es6/data_dir.spec.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/spec-es6/data_dir.spec.ts b/spec-es6/data_dir.spec.ts index be16b9bea..81e9bc501 100644 --- a/spec-es6/data_dir.spec.ts +++ b/spec-es6/data_dir.spec.ts @@ -140,8 +140,15 @@ describe("data_dir.ts unit tests", async () => { resetAllMocks(); }); + /** + * case A – process.env.TRILIUM_DATA_DIR is set + * case B – process.env.TRILIUM_DATA_DIR is not set and Trilium folder is existing in platform + * case C – process.env.TRILIUM_DATA_DIR is not set and Trilium folder is not existing in platform's home dir + * case D – fallback to creating Trilium folder in home dir + */ - describe("case A – process.env.TRILIUM_DATA_DIR is set", () => { + + describe("case A", () => { it("when folder exists – it should return the path, without attempting to create the folder", async () => { const mockTriliumDataPath = "/home/mock/trilium-data-ENV-A1"; process.env.TRILIUM_DATA_DIR = mockTriliumDataPath; @@ -175,7 +182,7 @@ describe("data_dir.ts unit tests", async () => { }); }); - describe("case B – process.env.TRILIUM_DATA_DIR is not set and Trilium folder is existing in platform's home dir", () => { + describe("case B", () => { it("it should check if folder exists and return it", async () => { const homedir = "/home/mock"; const dataDirName = "trilium-data"; @@ -193,7 +200,7 @@ describe("data_dir.ts unit tests", async () => { }); }); - describe("case C – process.env.TRILIUM_DATA_DIR is not set and Trilium folder is not existing in platform's home dir", () => { + describe("case C", () => { it("w/ Platform 'Linux', an existing App Data Folder (~/.local/share) but non-existing Trilium dir (~/.local/share/trilium-data) – it should attempt to create the dir", async () => { const homedir = "/home/mock"; const dataDirName = "trilium-data"; @@ -278,7 +285,7 @@ describe("data_dir.ts unit tests", async () => { }); }); - describe("case D – fallback to creating Trilium folder in home dir", () => { + describe("case D", () => { it("w/ unknown PlatformAppDataDir it should attempt to create the folder in the homefolder", async () => { const homedir = "/home/mock"; const dataDirName = "trilium-data"; From cbc10e1f15d4b776919f1fefa773cee48de69f14 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sat, 18 Jan 2025 12:48:05 +0100 Subject: [PATCH 3/3] chore(prettier): fix data_dir.spec.ts --- spec-es6/data_dir.spec.ts | 652 +++++++++++++++++--------------------- 1 file changed, 296 insertions(+), 356 deletions(-) diff --git a/spec-es6/data_dir.spec.ts b/spec-es6/data_dir.spec.ts index 81e9bc501..ded078cb8 100644 --- a/spec-es6/data_dir.spec.ts +++ b/spec-es6/data_dir.spec.ts @@ -1,405 +1,345 @@ import { describe, it, expect, beforeEach, vi } from "vitest"; -import type { - getTriliumDataDir as getTriliumDataDirType, - getDataDirs as getDataDirsType, - getPlatformAppDataDir as getPlatformAppDataDirType, -} from "../src/services/data_dir"; - +import type { getTriliumDataDir as getTriliumDataDirType, getDataDirs as getDataDirsType, getPlatformAppDataDir as getPlatformAppDataDirType } from "../src/services/data_dir"; describe("data_dir.ts unit tests", async () => { + let getTriliumDataDir: typeof getTriliumDataDirType; + let getPlatformAppDataDir: typeof getPlatformAppDataDirType; + let getDataDirs: typeof getDataDirsType; + + const mockFn = { + existsSyncMock: vi.fn(), + mkdirSyncMock: vi.fn(), + osHomedirMock: vi.fn(), + osPlatformMock: vi.fn(), + pathJoinMock: vi.fn() + }; - - let getTriliumDataDir: typeof getTriliumDataDirType; - let getPlatformAppDataDir: typeof getPlatformAppDataDirType; - let getDataDirs: typeof getDataDirsType; - - - - const mockFn = { - existsSyncMock: vi.fn(), - mkdirSyncMock: vi.fn(), - osHomedirMock: vi.fn(), - osPlatformMock: vi.fn(), - pathJoinMock: vi.fn() - }; - - // using doMock, to avoid hoisting, so that we can use the mockFn object - // to collect all mocked Fns - vi.doMock("node:fs", () => { - return { - default: { - existsSync: mockFn.existsSyncMock, - mkdirSync: mockFn.mkdirSyncMock - } - }; - }); - - vi.doMock("node:os", () => { - return { - default: { - homedir: mockFn.osHomedirMock, - platform: mockFn.osPlatformMock - } - }; - }); - - vi.doMock("path", () => { - return { - join: mockFn.pathJoinMock - }; - }); - - // import function to test now, after creating the mocks - ({ getTriliumDataDir } = await import("../src/services/data_dir.ts")); - ({ getPlatformAppDataDir } = await import("../src/services/data_dir.ts")); - ({ getDataDirs } = await import("../src/services/data_dir.ts")); - - // helper to reset call counts - const resetAllMocks = () => { - Object.values(mockFn).forEach((mockedFn) => { - mockedFn.mockReset() - }); - }; - - // helper to set mocked Platform - const setMockPlatform = (osPlatform: string, homedir: string, pathJoin: string) => { - mockFn.osPlatformMock.mockImplementation(() => osPlatform); - mockFn.osHomedirMock.mockImplementation(() => homedir); - mockFn.pathJoinMock.mockImplementation(() => pathJoin); - }; - - - describe("#getPlatformAppDataDir()", () => { - - type TestCaseGetPlatformAppDataDir = [ - description: string, - fnValue: Parameters, - expectedValueFn: (val: ReturnType) => boolean - ] - const testCases: TestCaseGetPlatformAppDataDir[] = [ - - [ - "w/ unsupported OS it should return 'null'", - ["aix", undefined], - (val) => val === null - ], - - [ - "w/ win32 and no APPDATA set it should return 'null'", - ["win32", undefined], - (val) => val === null - ], - - [ - "w/ win32 and set APPDATA it should return set 'APPDATA'", - ["win32", "AppData"], - (val) => val === "AppData" - ], - - [ - "w/ linux it should return '/.local/share'", - ["linux", undefined], - (val) => val !== null && val.endsWith("/.local/share") - ], - - [ - "w/ linux and wrongly set APPDATA it should ignore APPDATA and return /.local/share", - ["linux", "FakeAppData"], - (val) => val !== null && val.endsWith("/.local/share") - ], - - [ - "w/ darwin it should return /Library/Application Support", - ["darwin", undefined], - (val) => val !== null && val.endsWith("/Library/Application Support") - ], - ]; - - testCases.forEach(testCase => { - const [testDescription, value, isExpected] = testCase; - return it(testDescription, () => { - const actual = getPlatformAppDataDir(...value); - const result = isExpected(actual); - expect(result).toBeTruthy() - - }) - }) - - - }) - - describe("#getTriliumDataDir", async () => { - - - beforeEach(() => { - // make sure these are not set - delete process.env.TRILIUM_DATA_DIR; - delete process.env.APPDATA; - - resetAllMocks(); + // using doMock, to avoid hoisting, so that we can use the mockFn object + // to collect all mocked Fns + vi.doMock("node:fs", () => { + return { + default: { + existsSync: mockFn.existsSyncMock, + mkdirSync: mockFn.mkdirSyncMock + } + }; }); - /** - * case A – process.env.TRILIUM_DATA_DIR is set - * case B – process.env.TRILIUM_DATA_DIR is not set and Trilium folder is existing in platform - * case C – process.env.TRILIUM_DATA_DIR is not set and Trilium folder is not existing in platform's home dir - * case D – fallback to creating Trilium folder in home dir - */ - - - describe("case A", () => { - it("when folder exists – it should return the path, without attempting to create the folder", async () => { - const mockTriliumDataPath = "/home/mock/trilium-data-ENV-A1"; - process.env.TRILIUM_DATA_DIR = mockTriliumDataPath; + vi.doMock("node:os", () => { + return { + default: { + homedir: mockFn.osHomedirMock, + platform: mockFn.osPlatformMock + } + }; + }); - // set fs.existsSync to true, i.e. the folder does exist - mockFn.existsSyncMock.mockImplementation(() => true); + vi.doMock("path", () => { + return { + join: mockFn.pathJoinMock + }; + }); - const result = getTriliumDataDir("trilium-data"); + // import function to test now, after creating the mocks + ({ getTriliumDataDir } = await import("../src/services/data_dir.ts")); + ({ getPlatformAppDataDir } = await import("../src/services/data_dir.ts")); + ({ getDataDirs } = await import("../src/services/data_dir.ts")); - // createDirIfNotExisting should call existsync 1 time and mkdirSync 0 times -> as it does not need to create the folder - // and return value should be TRILIUM_DATA_DIR value from process.env - expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(1); - expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(0); - expect(result).toEqual(process.env.TRILIUM_DATA_DIR); + // helper to reset call counts + const resetAllMocks = () => { + Object.values(mockFn).forEach((mockedFn) => { + mockedFn.mockReset(); }); + }; - it("when folder does not exist – it should attempt to create the folder and return the path", async () => { - const mockTriliumDataPath = "/home/mock/trilium-data-ENV-A2"; - process.env.TRILIUM_DATA_DIR = mockTriliumDataPath; - - // set fs.existsSync mock to return false, i.e. the folder does not exist - mockFn.existsSyncMock.mockImplementation(() => false); + // helper to set mocked Platform + const setMockPlatform = (osPlatform: string, homedir: string, pathJoin: string) => { + mockFn.osPlatformMock.mockImplementation(() => osPlatform); + mockFn.osHomedirMock.mockImplementation(() => homedir); + mockFn.pathJoinMock.mockImplementation(() => pathJoin); + }; - const result = getTriliumDataDir("trilium-data"); + describe("#getPlatformAppDataDir()", () => { + type TestCaseGetPlatformAppDataDir = [description: string, fnValue: Parameters, expectedValueFn: (val: ReturnType) => boolean]; + const testCases: TestCaseGetPlatformAppDataDir[] = [ + ["w/ unsupported OS it should return 'null'", ["aix", undefined], (val) => val === null], - // createDirIfNotExisting should call existsync 1 time and mkdirSync 1 times -> as it has to create the folder - // and return value should be TRILIUM_DATA_DIR value from process.env - expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(1); - expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1); - expect(result).toEqual(process.env.TRILIUM_DATA_DIR); - }); - }); + ["w/ win32 and no APPDATA set it should return 'null'", ["win32", undefined], (val) => val === null], - describe("case B", () => { - it("it should check if folder exists and return it", async () => { - const homedir = "/home/mock"; - const dataDirName = "trilium-data"; - const mockTriliumDataPath = `${homedir}/${dataDirName}`; + ["w/ win32 and set APPDATA it should return set 'APPDATA'", ["win32", "AppData"], (val) => val === "AppData"], - mockFn.pathJoinMock.mockImplementation(() => mockTriliumDataPath); + ["w/ linux it should return '/.local/share'", ["linux", undefined], (val) => val !== null && val.endsWith("/.local/share")], - // set fs.existsSync to true, i.e. the folder does exist - mockFn.existsSyncMock.mockImplementation(() => true); + ["w/ linux and wrongly set APPDATA it should ignore APPDATA and return /.local/share", ["linux", "FakeAppData"], (val) => val !== null && val.endsWith("/.local/share")], - const result = getTriliumDataDir(dataDirName); + ["w/ darwin it should return /Library/Application Support", ["darwin", undefined], (val) => val !== null && val.endsWith("/Library/Application Support")] + ]; - expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(1); - expect(result).toEqual(mockTriliumDataPath); + testCases.forEach((testCase) => { + const [testDescription, value, isExpected] = testCase; + return it(testDescription, () => { + const actual = getPlatformAppDataDir(...value); + const result = isExpected(actual); + expect(result).toBeTruthy(); + }); }); }); - describe("case C", () => { - it("w/ Platform 'Linux', an existing App Data Folder (~/.local/share) but non-existing Trilium dir (~/.local/share/trilium-data) – it should attempt to create the dir", async () => { - const homedir = "/home/mock"; - const dataDirName = "trilium-data"; - const mockPlatformDataPath = `${homedir}/.local/share/${dataDirName}`; - - // mock set: os.platform, os.homedir and pathJoin return values - setMockPlatform("linux", homedir, mockPlatformDataPath); - - // use Generator to precisely control order of fs.existSync return values - const existsSyncMockGen = (function* () { - // 1) fs.existSync -> case B - yield false; - // 2) fs.existSync -> case C -> checking if default OS PlatformAppDataDir exists - yield true; - // 3) fs.existSync -> case C -> checking if Trilium Data folder exists - yield false; - })(); + describe("#getTriliumDataDir", async () => { + beforeEach(() => { + // make sure these are not set + delete process.env.TRILIUM_DATA_DIR; + delete process.env.APPDATA; - mockFn.existsSyncMock.mockImplementation(() => existsSyncMockGen.next().value); - - const result = getTriliumDataDir(dataDirName); - - expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(3); - expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1); - expect(result).toEqual(mockPlatformDataPath); + resetAllMocks(); }); - it("w/ Platform Linux, an existing App Data Folder (~/.local/share) AND an existing Trilium Data dir – it should return path to the dir", async () => { - const homedir = "/home/mock"; - const dataDirName = "trilium-data"; - const mockPlatformDataPath = `${homedir}/.local/share/${dataDirName}`; + /** + * case A – process.env.TRILIUM_DATA_DIR is set + * case B – process.env.TRILIUM_DATA_DIR is not set and Trilium folder is existing in platform + * case C – process.env.TRILIUM_DATA_DIR is not set and Trilium folder is not existing in platform's home dir + * case D – fallback to creating Trilium folder in home dir + */ + + describe("case A", () => { + it("when folder exists – it should return the path, without attempting to create the folder", async () => { + const mockTriliumDataPath = "/home/mock/trilium-data-ENV-A1"; + process.env.TRILIUM_DATA_DIR = mockTriliumDataPath; + + // set fs.existsSync to true, i.e. the folder does exist + mockFn.existsSyncMock.mockImplementation(() => true); + + const result = getTriliumDataDir("trilium-data"); + + // createDirIfNotExisting should call existsync 1 time and mkdirSync 0 times -> as it does not need to create the folder + // and return value should be TRILIUM_DATA_DIR value from process.env + expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(1); + expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(0); + expect(result).toEqual(process.env.TRILIUM_DATA_DIR); + }); + + it("when folder does not exist – it should attempt to create the folder and return the path", async () => { + const mockTriliumDataPath = "/home/mock/trilium-data-ENV-A2"; + process.env.TRILIUM_DATA_DIR = mockTriliumDataPath; + + // set fs.existsSync mock to return false, i.e. the folder does not exist + mockFn.existsSyncMock.mockImplementation(() => false); + + const result = getTriliumDataDir("trilium-data"); + + // createDirIfNotExisting should call existsync 1 time and mkdirSync 1 times -> as it has to create the folder + // and return value should be TRILIUM_DATA_DIR value from process.env + expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(1); + expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1); + expect(result).toEqual(process.env.TRILIUM_DATA_DIR); + }); + }); - // mock set: os.platform, os.homedir and pathJoin return values - setMockPlatform("linux", homedir, mockPlatformDataPath); + describe("case B", () => { + it("it should check if folder exists and return it", async () => { + const homedir = "/home/mock"; + const dataDirName = "trilium-data"; + const mockTriliumDataPath = `${homedir}/${dataDirName}`; - // use Generator to precisely control order of fs.existSync return values - const existsSyncMockGen = (function* () { - // 1) fs.existSync -> case B - yield false; - // 2) fs.existSync -> case C -> checking if default OS PlatformAppDataDir exists - yield true; - // 3) fs.existSync -> case C -> checking if Trilium Data folder exists - yield true; - })(); + mockFn.pathJoinMock.mockImplementation(() => mockTriliumDataPath); - mockFn.existsSyncMock.mockImplementation(() => existsSyncMockGen.next().value); + // set fs.existsSync to true, i.e. the folder does exist + mockFn.existsSyncMock.mockImplementation(() => true); - const result = getTriliumDataDir(dataDirName); + const result = getTriliumDataDir(dataDirName); - expect(result).toEqual(mockPlatformDataPath); - expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(3); - expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(0); + expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(1); + expect(result).toEqual(mockTriliumDataPath); + }); }); - it("w/ Platform 'win32' and set process.env.APPDATA behaviour", async () => { - const homedir = "C:\\Users\\mock"; - const dataDirName = "trilium-data"; - const appDataDir = `${homedir}\\AppData\\Roaming`; - const mockPlatformDataPath = `${appDataDir}\\${dataDirName}`; - process.env.APPDATA = `${appDataDir}`; - - // mock set: os.platform, os.homedir and pathJoin return values - setMockPlatform("win32", homedir, mockPlatformDataPath); - - // use Generator to precisely control order of fs.existSync return values - const existsSyncMockGen = (function* () { - // 1) fs.existSync -> case B - yield false; - // 2) fs.existSync -> case C -> checking if default OS PlatformAppDataDir exists - yield true; - // 3) fs.existSync -> case C -> checking if Trilium Data folder exists - yield false; - })(); - - mockFn.existsSyncMock.mockImplementation(() => existsSyncMockGen.next().value); - - const result = getTriliumDataDir(dataDirName); - - expect(result).toEqual(mockPlatformDataPath); - expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(3); - expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1); + describe("case C", () => { + it("w/ Platform 'Linux', an existing App Data Folder (~/.local/share) but non-existing Trilium dir (~/.local/share/trilium-data) – it should attempt to create the dir", async () => { + const homedir = "/home/mock"; + const dataDirName = "trilium-data"; + const mockPlatformDataPath = `${homedir}/.local/share/${dataDirName}`; + + // mock set: os.platform, os.homedir and pathJoin return values + setMockPlatform("linux", homedir, mockPlatformDataPath); + + // use Generator to precisely control order of fs.existSync return values + const existsSyncMockGen = (function* () { + // 1) fs.existSync -> case B + yield false; + // 2) fs.existSync -> case C -> checking if default OS PlatformAppDataDir exists + yield true; + // 3) fs.existSync -> case C -> checking if Trilium Data folder exists + yield false; + })(); + + mockFn.existsSyncMock.mockImplementation(() => existsSyncMockGen.next().value); + + const result = getTriliumDataDir(dataDirName); + + expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(3); + expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1); + expect(result).toEqual(mockPlatformDataPath); + }); + + it("w/ Platform Linux, an existing App Data Folder (~/.local/share) AND an existing Trilium Data dir – it should return path to the dir", async () => { + const homedir = "/home/mock"; + const dataDirName = "trilium-data"; + const mockPlatformDataPath = `${homedir}/.local/share/${dataDirName}`; + + // mock set: os.platform, os.homedir and pathJoin return values + setMockPlatform("linux", homedir, mockPlatformDataPath); + + // use Generator to precisely control order of fs.existSync return values + const existsSyncMockGen = (function* () { + // 1) fs.existSync -> case B + yield false; + // 2) fs.existSync -> case C -> checking if default OS PlatformAppDataDir exists + yield true; + // 3) fs.existSync -> case C -> checking if Trilium Data folder exists + yield true; + })(); + + mockFn.existsSyncMock.mockImplementation(() => existsSyncMockGen.next().value); + + const result = getTriliumDataDir(dataDirName); + + expect(result).toEqual(mockPlatformDataPath); + expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(3); + expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(0); + }); + + it("w/ Platform 'win32' and set process.env.APPDATA behaviour", async () => { + const homedir = "C:\\Users\\mock"; + const dataDirName = "trilium-data"; + const appDataDir = `${homedir}\\AppData\\Roaming`; + const mockPlatformDataPath = `${appDataDir}\\${dataDirName}`; + process.env.APPDATA = `${appDataDir}`; + + // mock set: os.platform, os.homedir and pathJoin return values + setMockPlatform("win32", homedir, mockPlatformDataPath); + + // use Generator to precisely control order of fs.existSync return values + const existsSyncMockGen = (function* () { + // 1) fs.existSync -> case B + yield false; + // 2) fs.existSync -> case C -> checking if default OS PlatformAppDataDir exists + yield true; + // 3) fs.existSync -> case C -> checking if Trilium Data folder exists + yield false; + })(); + + mockFn.existsSyncMock.mockImplementation(() => existsSyncMockGen.next().value); + + const result = getTriliumDataDir(dataDirName); + + expect(result).toEqual(mockPlatformDataPath); + expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(3); + expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1); + }); }); - }); - describe("case D", () => { - it("w/ unknown PlatformAppDataDir it should attempt to create the folder in the homefolder", async () => { - const homedir = "/home/mock"; - const dataDirName = "trilium-data"; - const mockPlatformDataPath = `${homedir}/${dataDirName}`; + describe("case D", () => { + it("w/ unknown PlatformAppDataDir it should attempt to create the folder in the homefolder", async () => { + const homedir = "/home/mock"; + const dataDirName = "trilium-data"; + const mockPlatformDataPath = `${homedir}/${dataDirName}`; - setMockPlatform("aix", homedir, mockPlatformDataPath); + setMockPlatform("aix", homedir, mockPlatformDataPath); - const existsSyncMockGen = (function* () { - // first fs.existSync -> case B -> checking if folder exists in home folder - yield false; - // second fs.existSync -> case D -> triggered by createDirIfNotExisting - yield false; - })(); + const existsSyncMockGen = (function* () { + // first fs.existSync -> case B -> checking if folder exists in home folder + yield false; + // second fs.existSync -> case D -> triggered by createDirIfNotExisting + yield false; + })(); - mockFn.existsSyncMock.mockImplementation(() => existsSyncMockGen.next().value); + mockFn.existsSyncMock.mockImplementation(() => existsSyncMockGen.next().value); - const result = getTriliumDataDir(dataDirName); + const result = getTriliumDataDir(dataDirName); - expect(result).toEqual(mockPlatformDataPath); - expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(2); - expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1); + expect(result).toEqual(mockPlatformDataPath); + expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(2); + expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1); + }); }); }); - }) - - describe("#getDataDirs()", () => { - - const envKeys: Omit, "TRILIUM_DATA_DIR">[] = [ - "DOCUMENT_PATH", - "BACKUP_DIR", - "LOG_DIR", - "ANONYMIZED_DB_DIR", - "CONFIG_INI_PATH", - ]; - - const setMockedEnv = (prefix: string | null) => { - envKeys.forEach(key => { - if (prefix) { - process.env[`TRILIUM_${key}`] = `${prefix}_${key}` - } else { - delete process.env[`TRILIUM_${key}`] - } - }) - }; - - it("w/ process.env values present, it should return an object using values from process.env", () => { - - // set mocked values - const mockValuePrefix = "MOCK"; - setMockedEnv(mockValuePrefix); - - // get result - const result = getDataDirs(`${mockValuePrefix}_TRILIUM_DATA_DIR`); - - for (const key in result) { - expect(result[key]).toEqual(`${mockValuePrefix}_${key}`) - } - }) - - it("w/ NO process.env values present, it should return an object using supplied TRILIUM_DATA_DIR as base", () => { - - // make sure values are undefined - setMockedEnv(null); - - // mock pathJoin implementation to just return mockDataDir - const mockDataDir = "/home/test/MOCK_TRILIUM_DATA_DIR"; - mockFn.pathJoinMock.mockImplementation(() => mockDataDir); - - const result = getDataDirs(mockDataDir); - for (const key in result) { - expect(result[key].startsWith(mockDataDir)).toBeTruthy() - } - - mockFn.pathJoinMock.mockReset() - - }) - - it("should ignore attempts to change a property on the returned object", () => { - - // make sure values are undefined - setMockedEnv(null); - - const mockDataDirBase = "/home/test/MOCK_TRILIUM_DATA_DIR" - const result = getDataDirs(mockDataDirBase); + describe("#getDataDirs()", () => { + const envKeys: Omit, "TRILIUM_DATA_DIR">[] = ["DOCUMENT_PATH", "BACKUP_DIR", "LOG_DIR", "ANONYMIZED_DB_DIR", "CONFIG_INI_PATH"]; + + const setMockedEnv = (prefix: string | null) => { + envKeys.forEach((key) => { + if (prefix) { + process.env[`TRILIUM_${key}`] = `${prefix}_${key}`; + } else { + delete process.env[`TRILIUM_${key}`]; + } + }); + }; + + it("w/ process.env values present, it should return an object using values from process.env", () => { + // set mocked values + const mockValuePrefix = "MOCK"; + setMockedEnv(mockValuePrefix); + + // get result + const result = getDataDirs(`${mockValuePrefix}_TRILIUM_DATA_DIR`); + + for (const key in result) { + expect(result[key]).toEqual(`${mockValuePrefix}_${key}`); + } + }); - // as per MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#description - // Any attempt to change a frozen object will, either silently be ignored or - // throw a TypeError exception (most commonly, but not exclusively, when in strict mode). - // so be safe and check for both, even though it looks weird + it("w/ NO process.env values present, it should return an object using supplied TRILIUM_DATA_DIR as base", () => { + // make sure values are undefined + setMockedEnv(null); - const getChangeAttemptResult = () => { - try { - //@ts-expect-error - attempt to change value of readonly property - result.BACKUP_DIR = "attempt to change"; - return result.BACKUP_DIR; - } - catch(error) { - return error - } - } + // mock pathJoin implementation to just return mockDataDir + const mockDataDir = "/home/test/MOCK_TRILIUM_DATA_DIR"; + mockFn.pathJoinMock.mockImplementation(() => mockDataDir); - const changeAttemptResult = getChangeAttemptResult(); + const result = getDataDirs(mockDataDir); - if (typeof changeAttemptResult === "string") { - // if it didn't throw above: assert that it did not change the value of it or any other keys of the object - for (const key in result) { - expect(result[key].startsWith(mockDataDirBase)).toBeTruthy() - } - } else { - expect(changeAttemptResult).toBeInstanceOf(TypeError) - } + for (const key in result) { + expect(result[key].startsWith(mockDataDir)).toBeTruthy(); + } - }) - }) + mockFn.pathJoinMock.mockReset(); + }); + it("should ignore attempts to change a property on the returned object", () => { + // make sure values are undefined + setMockedEnv(null); + + const mockDataDirBase = "/home/test/MOCK_TRILIUM_DATA_DIR"; + const result = getDataDirs(mockDataDirBase); + + // as per MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#description + // Any attempt to change a frozen object will, either silently be ignored or + // throw a TypeError exception (most commonly, but not exclusively, when in strict mode). + // so be safe and check for both, even though it looks weird + + const getChangeAttemptResult = () => { + try { + //@ts-expect-error - attempt to change value of readonly property + result.BACKUP_DIR = "attempt to change"; + return result.BACKUP_DIR; + } catch (error) { + return error; + } + }; + + const changeAttemptResult = getChangeAttemptResult(); + + if (typeof changeAttemptResult === "string") { + // if it didn't throw above: assert that it did not change the value of it or any other keys of the object + for (const key in result) { + expect(result[key].startsWith(mockDataDirBase)).toBeTruthy(); + } + } else { + expect(changeAttemptResult).toBeInstanceOf(TypeError); + } + }); + }); });