From aaa93c3371d2def0515da8c7aefee3dd8e04107f Mon Sep 17 00:00:00 2001 From: Jacobtread Date: Tue, 14 Apr 2026 20:09:43 +1200 Subject: [PATCH] tests: more tests for remaining parser, queries, and update logic --- .../checking/findEntryByIdMissingNested.ts | 32 +++++++++ .../checking/findEntryByIdNestedSecond.ts | 49 +++++++++++++ .../extracting/codeblockContentsPosition.ts | 47 +++++++++++++ .../invalidCodeBlockContentPosition.ts | 6 ++ .../extracting/unclosedCodeBlockPosition.ts | 27 ++++++++ .../remove_entry/removeEntrySuccess.ts | 68 +++++++++++++++++++ src/timekeep/__fixtures__/path/pathChild.ts | 27 ++++++++ .../__fixtures__/path/pathDeepChild.ts | 52 ++++++++++++++ .../__fixtures__/path/pathNotFoundDeep.ts | 39 +++++++++++ .../__fixtures__/path/pathTopLevel.ts | 21 ++++++ .../__fixtures__/startTime/earlyStartTime.ts | 42 ++++++++++++ src/timekeep/parser.test.ts | 45 ++++++++++++ src/timekeep/queries.test.ts | 51 +++++++++++++- src/timekeep/update.ts | 4 +- 14 files changed, 506 insertions(+), 4 deletions(-) create mode 100644 src/timekeep/__fixtures__/checking/findEntryByIdMissingNested.ts create mode 100644 src/timekeep/__fixtures__/checking/findEntryByIdNestedSecond.ts create mode 100644 src/timekeep/__fixtures__/extracting/codeblockContentsPosition.ts create mode 100644 src/timekeep/__fixtures__/extracting/invalidCodeBlockContentPosition.ts create mode 100644 src/timekeep/__fixtures__/extracting/unclosedCodeBlockPosition.ts create mode 100644 src/timekeep/__fixtures__/path/pathChild.ts create mode 100644 src/timekeep/__fixtures__/path/pathDeepChild.ts create mode 100644 src/timekeep/__fixtures__/path/pathNotFoundDeep.ts create mode 100644 src/timekeep/__fixtures__/path/pathTopLevel.ts create mode 100644 src/timekeep/__fixtures__/startTime/earlyStartTime.ts diff --git a/src/timekeep/__fixtures__/checking/findEntryByIdMissingNested.ts b/src/timekeep/__fixtures__/checking/findEntryByIdMissingNested.ts new file mode 100644 index 0000000..dcb5dee --- /dev/null +++ b/src/timekeep/__fixtures__/checking/findEntryByIdMissingNested.ts @@ -0,0 +1,32 @@ +import moment from "moment"; + +import { TimeEntry } from "@/timekeep/schema"; + +export const currentTime = moment(); + +export const targetEntryId = "9054dee3-8c15-493b-ad31-f070e08c2699"; + +export const input: TimeEntry[] = [ + { + id: "7054dee3-8c15-493b-ad31-f070e08c2699", + name: "Part 1", + startTime: null, + endTime: null, + subEntries: [ + { + id: "7054dee3-8c15-493b-ad31-f070e08c2699", + name: "Part 1", + startTime: currentTime, + endTime: currentTime, + subEntries: null, + }, + { + id: "7054dee3-8c15-493b-ad31-f070e08c269a", + name: "Part 2", + startTime: null, + endTime: null, + subEntries: [], + }, + ], + }, +]; diff --git a/src/timekeep/__fixtures__/checking/findEntryByIdNestedSecond.ts b/src/timekeep/__fixtures__/checking/findEntryByIdNestedSecond.ts new file mode 100644 index 0000000..3443f5f --- /dev/null +++ b/src/timekeep/__fixtures__/checking/findEntryByIdNestedSecond.ts @@ -0,0 +1,49 @@ +import moment from "moment"; + +import { TimeEntry } from "@/timekeep/schema"; + +export const currentTime = moment(); + +export const targetEntryId = "9054dee3-8c15-493b-ad31-f070e08c2699"; + +export const targetEntry: TimeEntry = { + id: "9054dee3-8c15-493b-ad31-f070e08c2699", + name: "Running Entry", + startTime: currentTime, + endTime: null, + subEntries: null, +}; + +export const input: TimeEntry[] = [ + { + id: "8054dee3-8c15-493b-ad31-f070e08c2699", + name: "Block 1", + startTime: null, + endTime: null, + subEntries: [ + { + id: "7054dee3-8c15-493b-ad31-f070e08c2699", + name: "Part 1", + startTime: currentTime, + endTime: currentTime, + subEntries: null, + }, + { + id: "7054dee3-8c15-493b-ad31-f070e08c269a", + name: "Part 2", + startTime: null, + endTime: null, + subEntries: [ + { + id: "dcd7dca3-2471-4855-a49b-397e388c3204", + name: "Part 1", + startTime: currentTime, + endTime: currentTime, + subEntries: null, + }, + targetEntry, + ], + }, + ], + }, +]; diff --git a/src/timekeep/__fixtures__/extracting/codeblockContentsPosition.ts b/src/timekeep/__fixtures__/extracting/codeblockContentsPosition.ts new file mode 100644 index 0000000..b259dfb --- /dev/null +++ b/src/timekeep/__fixtures__/extracting/codeblockContentsPosition.ts @@ -0,0 +1,47 @@ +import moment from "moment"; + +import { createCodeBlock } from "@/utils/codeblock"; + +import { Timekeep } from "@/timekeep/schema"; + +// Input data to find +export const input1 = createCodeBlock( + `{"entries":[{"name":"Block 1","startTime":"2024-03-17T01:33:51.630Z","endTime":"2024-03-17T01:33:55.151Z","subEntries":null}]}`, + 4, + 4 +); + +// Input data to find +export const input2 = createCodeBlock( + `{"entries":[{"name":"Block 2","startTime":"2024-03-17T01:33:51.630Z","endTime":"2024-03-17T01:33:55.151Z","subEntries":null}]}`, + 4, + 4 +); + +// Timekeep with a renamed block +export const inputTimekeep1: Timekeep = { + entries: [ + { + id: "9054dee3-8c15-493b-ad31-f070e08c2699", + name: "Block 1", + startTime: moment("2024-03-17T01:33:51.630Z"), + endTime: moment("2024-03-17T01:33:55.151Z"), + subEntries: null, + }, + ], +}; + +// Timekeep with a renamed block +export const inputTimekeep2: Timekeep = { + entries: [ + { + id: "9054dee3-8c15-493b-ad31-f070e08c2699", + name: "Block 2", + startTime: moment("2024-03-17T01:33:51.630Z"), + endTime: moment("2024-03-17T01:33:55.151Z"), + subEntries: null, + }, + ], +}; + +export const text = "\n\n\n" + input1 + "\n\n\n\n" + input2; diff --git a/src/timekeep/__fixtures__/extracting/invalidCodeBlockContentPosition.ts b/src/timekeep/__fixtures__/extracting/invalidCodeBlockContentPosition.ts new file mode 100644 index 0000000..c51b5ac --- /dev/null +++ b/src/timekeep/__fixtures__/extracting/invalidCodeBlockContentPosition.ts @@ -0,0 +1,6 @@ +import { createCodeBlock } from "@/utils/codeblock"; + +// Input data to find +export const input1 = createCodeBlock(`{"invalid_json}]}`, 4, 4); + +export const text = "\n\n\n" + input1 + "\n\n\n\n"; diff --git a/src/timekeep/__fixtures__/extracting/unclosedCodeBlockPosition.ts b/src/timekeep/__fixtures__/extracting/unclosedCodeBlockPosition.ts new file mode 100644 index 0000000..c4c7ab2 --- /dev/null +++ b/src/timekeep/__fixtures__/extracting/unclosedCodeBlockPosition.ts @@ -0,0 +1,27 @@ +import moment from "moment"; + +import { createCodeBlock } from "@/utils/codeblock"; + +import { Timekeep } from "@/timekeep/schema"; + +// Input data to find +export const input1 = createCodeBlock( + `{"entries":[{"name":"Block 1","startTime":"2024-03-17T01:33:51.630Z","endTime":"2024-03-17T01:33:55.151Z","subEntries":null}]}`, + 4, + 4 +); + +// Timekeep with a renamed block +export const inputTimekeep1: Timekeep = { + entries: [ + { + id: "9054dee3-8c15-493b-ad31-f070e08c2699", + name: "Block 1", + startTime: moment("2024-03-17T01:33:51.630Z"), + endTime: moment("2024-03-17T01:33:55.151Z"), + subEntries: null, + }, + ], +}; + +export const text = "\n\n\n" + input1 + "\n\n\n\n```timekeep\n\n"; diff --git a/src/timekeep/__fixtures__/manipulating/remove_entry/removeEntrySuccess.ts b/src/timekeep/__fixtures__/manipulating/remove_entry/removeEntrySuccess.ts index a5bd408..88512f1 100644 --- a/src/timekeep/__fixtures__/manipulating/remove_entry/removeEntrySuccess.ts +++ b/src/timekeep/__fixtures__/manipulating/remove_entry/removeEntrySuccess.ts @@ -19,6 +19,44 @@ export const entries: TimeEntry[] = [ endTime: currentTime, subEntries: null, }, + { + id: "1f683adc-0205-4a18-9b1b-ada64a4b0553", + name: "Block Should be removed", + startTime: null, + endTime: null, + subEntries: [], + }, + { + id: "36c3a34a-208c-4975-bae7-ff833e8017ac", + name: "Block 4", + startTime: null, + endTime: null, + subEntries: [ + { + id: "cbfb3b78-dd7f-4ee2-99ea-34d6b0626e23", + name: "Block 5", + startTime: currentTime, + endTime: currentTime, + subEntries: null, + }, + + { + id: "35ff21e3-af83-45b6-86a2-7e141a12c37f", + name: "Block 12", + startTime: currentTime, + endTime: currentTime, + subEntries: null, + }, + ], + }, + { + id: "a9bda6f2-568c-4d11-a3be-e8586bde8b1c", + name: "Block 6", + startTime: null, + endTime: null, + subEntries: [], + folder: true, + }, entryToRemove, { id: "b8fbcb98-f1e9-4d80-8867-994f58191046", @@ -37,6 +75,36 @@ export const expectedEntries: TimeEntry[] = [ endTime: currentTime, subEntries: null, }, + { + id: "36c3a34a-208c-4975-bae7-ff833e8017ac", + name: "Block 4", + startTime: null, + endTime: null, + subEntries: [ + { + id: "cbfb3b78-dd7f-4ee2-99ea-34d6b0626e23", + name: "Block 5", + startTime: currentTime, + endTime: currentTime, + subEntries: null, + }, + { + id: "35ff21e3-af83-45b6-86a2-7e141a12c37f", + name: "Block 12", + startTime: currentTime, + endTime: currentTime, + subEntries: null, + }, + ], + }, + { + id: "a9bda6f2-568c-4d11-a3be-e8586bde8b1c", + name: "Block 6", + startTime: null, + endTime: null, + subEntries: [], + folder: true, + }, { id: "b8fbcb98-f1e9-4d80-8867-994f58191046", name: "Block 2", diff --git a/src/timekeep/__fixtures__/path/pathChild.ts b/src/timekeep/__fixtures__/path/pathChild.ts new file mode 100644 index 0000000..d007be1 --- /dev/null +++ b/src/timekeep/__fixtures__/path/pathChild.ts @@ -0,0 +1,27 @@ +import moment from "moment"; + +import { TimeEntry } from "@/timekeep/schema"; + +export const currentTime = moment(); + +export const targetEntry: TimeEntry = { + id: "9054dee3-8c15-493b-ad31-f070e08c2699", + name: "Part 1", + startTime: currentTime, + endTime: currentTime, + subEntries: null, +}; + +export const entries = [ + { + id: "7054dee3-8c15-493b-ad31-f070e08c2699", + name: "Part 1", + startTime: null, + endTime: null, + subEntries: [targetEntry], + }, +]; +export const expected = [ + { id: "7054dee3-8c15-493b-ad31-f070e08c2699", name: "Part 1" }, + { id: "9054dee3-8c15-493b-ad31-f070e08c2699", name: "Part 1" }, +]; diff --git a/src/timekeep/__fixtures__/path/pathDeepChild.ts b/src/timekeep/__fixtures__/path/pathDeepChild.ts new file mode 100644 index 0000000..c6da6d9 --- /dev/null +++ b/src/timekeep/__fixtures__/path/pathDeepChild.ts @@ -0,0 +1,52 @@ +import moment from "moment"; + +import { TimeEntry } from "@/timekeep/schema"; + +export const currentTime = moment(); + +export const targetEntry: TimeEntry = { + id: "9054dee3-8c15-493b-ad31-f070e08c2699", + name: "Part 1", + startTime: currentTime, + endTime: currentTime, + subEntries: null, +}; + +export const entries = [ + { + id: "7054dee3-8c15-493b-ad31-f070e08c2699", + name: "Part 1", + startTime: null, + endTime: null, + subEntries: [ + { + id: "7054dee3-8c15-493b-ad31-f070e08c2699", + name: "Part 1", + startTime: currentTime, + endTime: currentTime, + subEntries: null, + }, + { + id: "7054dee3-8c15-493b-ad31-f070e08c269a", + name: "Part 2", + startTime: null, + endTime: null, + subEntries: [targetEntry], + }, + ], + }, +]; +export const expected = [ + { + id: "7054dee3-8c15-493b-ad31-f070e08c2699", + name: "Part 1", + }, + { + id: "7054dee3-8c15-493b-ad31-f070e08c269a", + name: "Part 2", + }, + { + id: "9054dee3-8c15-493b-ad31-f070e08c2699", + name: "Part 1", + }, +]; diff --git a/src/timekeep/__fixtures__/path/pathNotFoundDeep.ts b/src/timekeep/__fixtures__/path/pathNotFoundDeep.ts new file mode 100644 index 0000000..caafd59 --- /dev/null +++ b/src/timekeep/__fixtures__/path/pathNotFoundDeep.ts @@ -0,0 +1,39 @@ +import moment from "moment"; + +import { TimeEntry } from "@/timekeep/schema"; + +export const currentTime = moment(); + +export const targetEntry: TimeEntry = { + id: "9054dee3-8c15-493b-ad31-f070e08c2699", + name: "Part 1", + startTime: currentTime, + endTime: currentTime, + subEntries: null, +}; + +export const entries = [ + { + id: "7054dee3-8c15-493b-ad31-f070e08c2699", + name: "Part 1", + startTime: null, + endTime: null, + subEntries: [ + { + id: "7054dee3-8c15-493b-ad31-f070e08c2699", + name: "Part 1", + startTime: currentTime, + endTime: currentTime, + subEntries: null, + }, + { + id: "7054dee3-8c15-493b-ad31-f070e08c269a", + name: "Part 2", + startTime: null, + endTime: null, + subEntries: [], + }, + ], + }, +]; +export const expected = []; diff --git a/src/timekeep/__fixtures__/path/pathTopLevel.ts b/src/timekeep/__fixtures__/path/pathTopLevel.ts new file mode 100644 index 0000000..605d860 --- /dev/null +++ b/src/timekeep/__fixtures__/path/pathTopLevel.ts @@ -0,0 +1,21 @@ +import moment from "moment"; + +import { TimeEntry } from "@/timekeep/schema"; + +export const currentTime = moment(); + +export const targetEntry: TimeEntry = { + id: "9054dee3-8c15-493b-ad31-f070e08c2699", + name: "Part 1", + startTime: currentTime, + endTime: currentTime, + subEntries: null, +}; + +export const entries = [targetEntry]; +export const expected = [ + { + id: "9054dee3-8c15-493b-ad31-f070e08c2699", + name: "Part 1", + }, +]; diff --git a/src/timekeep/__fixtures__/startTime/earlyStartTime.ts b/src/timekeep/__fixtures__/startTime/earlyStartTime.ts new file mode 100644 index 0000000..0c4c11f --- /dev/null +++ b/src/timekeep/__fixtures__/startTime/earlyStartTime.ts @@ -0,0 +1,42 @@ +import moment from "moment"; + +export const currentTime = moment(); + +export const entry = { + id: "9054dee3-8c15-493b-ad31-f070e08c2699", + name: "Part 1", + startTime: null, + endTime: null, + subEntries: [ + { + id: "d7afcb74-a6f2-4cb7-80ed-48a2f71aa7a6", + name: "Part 1", + startTime: currentTime.add(1, "hours"), + endTime: currentTime.add(1, "hours"), + subEntries: null, + }, + { + id: "da783535-08d3-4f65-97f3-e331273da934", + name: "Part 1", + startTime: currentTime, + endTime: currentTime, + subEntries: null, + }, + { + id: "5c305879-61ba-4bc9-8373-568948abf27f", + name: "Part 1", + startTime: null, + endTime: null, + subEntries: null, + }, + { + id: "8d491c4c-2441-4f87-bd29-4758b8108500", + name: "Part 1", + startTime: currentTime.add(2, "hours"), + endTime: currentTime.add(2, "hours"), + subEntries: null, + }, + ], +}; + +export const output = currentTime; diff --git a/src/timekeep/parser.test.ts b/src/timekeep/parser.test.ts index 09ec85e..1a91dd8 100644 --- a/src/timekeep/parser.test.ts +++ b/src/timekeep/parser.test.ts @@ -9,6 +9,7 @@ import { LoadSuccess, replaceTimekeepCodeblock, extractTimekeepCodeblocks, + extractTimekeepCodeblocksWithPosition, } from "./parser"; import { Timekeep, stripTimekeepRuntimeData } from "./schema"; @@ -40,6 +41,50 @@ describe("extracting code blocks", () => { }); }); +describe("extracting code blocks with position", () => { + it("should extract codeblock contents", async () => { + const { text, inputTimekeep1, inputTimekeep2 } = + await import("./__fixtures__/extracting/codeblockContentsPosition"); + + const output = extractTimekeepCodeblocksWithPosition(text); + + expect(stripTimekeepRuntimeData(output[0].timekeep)).toStrictEqual( + stripTimekeepRuntimeData(inputTimekeep1) + ); + expect(output[0].startLine).toEqual(7); + expect(output[0].endLine).toEqual(9); + expect(stripTimekeepRuntimeData(output[1].timekeep)).toStrictEqual( + stripTimekeepRuntimeData(inputTimekeep2) + ); + + expect(output[1].startLine).toEqual(21); + expect(output[1].endLine).toEqual(23); + expect(output.length).toBe(2); + }); + + it("should ignore codeblocks that are not closed", async () => { + const { text, inputTimekeep1 } = + await import("./__fixtures__/extracting/unclosedCodeBlockPosition"); + const output = extractTimekeepCodeblocksWithPosition(text); + + expect(stripTimekeepRuntimeData(output[0].timekeep)).toStrictEqual( + stripTimekeepRuntimeData(inputTimekeep1) + ); + expect(output[0].startLine).toEqual(7); + expect(output[0].endLine).toEqual(9); + expect(output.length).toBe(1); + }); + + it("should ignore codeblocks that are not closed", async () => { + const consoleError = vi.spyOn(console, "error").mockImplementation(() => {}); + const { text } = await import("./__fixtures__/extracting/invalidCodeBlockContentPosition"); + const output = extractTimekeepCodeblocksWithPosition(text); + + expect(output.length).toBe(0); + expect(consoleError).toHaveBeenCalled(); + }); +}); + describe("replacing content", () => { it("should replace codeblock contents", () => { const lineStart = 4; // Line the codeblock should start on diff --git a/src/timekeep/queries.test.ts b/src/timekeep/queries.test.ts index b4d15e7..302bcd0 100644 --- a/src/timekeep/queries.test.ts +++ b/src/timekeep/queries.test.ts @@ -9,6 +9,7 @@ import { getEntryDuration, getTotalDuration, getEntriesNames, + getStartTime, } from "./queries"; import { TimeEntry } from "./schema"; @@ -29,6 +30,14 @@ describe("getEntryById", () => { expect(output).toEqual(targetEntry); }); + it("find nested entry second element", async () => { + const { input, targetEntry, targetEntryId } = + await import("./__fixtures__/checking/findEntryByIdNestedSecond"); + + const output = getEntryById(targetEntryId, input); + expect(output).toEqual(targetEntry); + }); + it("find entry non existent", async () => { const { input, targetEntryId } = await import("./__fixtures__/checking/findEntryByIdMissing"); @@ -36,6 +45,14 @@ describe("getEntryById", () => { const output = getEntryById(targetEntryId, input); expect(output).toBeUndefined(); }); + + it("find entry non existent nested", async () => { + const { input, targetEntryId } = + await import("./__fixtures__/checking/findEntryByIdMissingNested"); + + const output = getEntryById(targetEntryId, input); + expect(output).toBeUndefined(); + }); }); describe("getPathToEntry", () => { @@ -45,9 +62,31 @@ describe("getPathToEntry", () => { expect(output).toEqual(expected); }); - it("top level path found", () => {}); + it("top level path found", async () => { + const { targetEntry, entries, expected } = await import("./__fixtures__/path/pathTopLevel"); + const output = getPathToEntry(entries, targetEntry); + expect(output).toEqual(expected); + }); - it("deep path found", () => {}); + it("child path found", async () => { + const { targetEntry, entries, expected } = await import("./__fixtures__/path/pathChild"); + const output = getPathToEntry(entries, targetEntry); + expect(output).toEqual(expected); + }); + + it("deep child path found", async () => { + const { targetEntry, entries, expected } = + await import("./__fixtures__/path/pathDeepChild"); + const output = getPathToEntry(entries, targetEntry); + expect(output).toEqual(expected); + }); + + it("deep child path not found", async () => { + const { targetEntry, entries, expected } = + await import("./__fixtures__/path/pathNotFoundDeep"); + const output = getPathToEntry(entries, targetEntry); + expect(output).toEqual(expected); + }); it("should find running entry path", async () => { const { input, runningEntry, path } = @@ -203,3 +242,11 @@ describe("getEntriesNames", () => { expect(outputSet).toEqual(expected); }); }); + +describe("getStartTime", () => { + it("should pick the earliest start time", async () => { + const { entry, output } = await import("./__fixtures__/startTime/earlyStartTime"); + + expect(getStartTime(entry, false)).toEqual(output); + }); +}); diff --git a/src/timekeep/update.ts b/src/timekeep/update.ts index bb4ef86..c8d7a65 100644 --- a/src/timekeep/update.ts +++ b/src/timekeep/update.ts @@ -170,8 +170,8 @@ function makeEntrySingle(target: TimeEntry): TimeEntry { return target; } - // Don't collapse if more than 1 entry - if (target.subEntries.length > 1) { + // Don't collapse if more than 1 entry or no entries at all + if (target.subEntries.length != 1) { return target; }