refactor: replace uuid with incremental counters

This commit is contained in:
Jacobtread 2026-05-17 11:27:56 +12:00
parent fa1fedbb01
commit 36e14676dc
No known key found for this signature in database
GPG key ID: AB9B37C42B33D9C6
35 changed files with 256 additions and 187 deletions

View file

@ -22,7 +22,6 @@
"moment": "^2.30.1",
"p-limit": "^7.3.0",
"pdfmake": "^0.3.7",
"uuid": "^11.0.5",
"valibot": "^1.4.0"
},
"devDependencies": {

View file

@ -20,9 +20,6 @@ importers:
pdfmake:
specifier: ^0.3.7
version: 0.3.8
uuid:
specifier: ^11.0.5
version: 11.1.1
valibot:
specifier: ^1.4.0
version: 1.4.0(typescript@6.0.2)
@ -927,10 +924,6 @@ packages:
unicode-trie@2.0.0:
resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==}
uuid@11.1.1:
resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==}
hasBin: true
valibot@1.4.0:
resolution: {integrity: sha512-iC/x7fVcSyOwlm/VSt7RlHnzNGLGvR9GnxdifUeWoCJo0q4ZZvrVkIHC6faTlkxG47I2Y4UrFquPuVHCrOnrLg==}
peerDependencies:
@ -1756,8 +1749,6 @@ snapshots:
pako: 0.2.9
tiny-inflate: 1.0.3
uuid@11.1.1: {}
valibot@1.4.0(typescript@6.0.2):
optionalDependencies:
typescript: 6.0.2

View file

@ -0,0 +1,31 @@
import { beforeEach, vi } from "vitest";
vi.mock(import("@/timekeep/id"), () => {
const mockStore = () => {
let nextId: number = 1;
function next() {
const value = nextId;
nextId++;
return value;
}
return {
next: vi.fn().mockImplementation(next),
};
};
let timekeepId = mockStore();
let timekeepMergerEntries = mockStore();
// Reset IDs for every test
beforeEach(() => {
timekeepId = mockStore();
timekeepMergerEntries = mockStore();
});
return {
timekeepId,
timekeepMergerEntries,
};
});

View file

@ -1,7 +1,6 @@
// @vitest-environment happy-dom
import moment from "moment";
import { v4 } from "uuid";
import { describe, it, expect, vi, beforeEach, afterEach, assert } from "vitest";
import { createMockContainer } from "@/__mocks__/obsidian";
@ -113,7 +112,7 @@ describe("TimesheetCounters", () => {
timekeepStore.setState({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(start),
endTime: null,
@ -146,7 +145,7 @@ describe("TimesheetCounters", () => {
entries: [
// 1h elapsed entry
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(start),
endTime: null,
@ -154,7 +153,7 @@ describe("TimesheetCounters", () => {
},
// 2h entry
{
id: v4(),
id: 2,
name: "Test",
startTime: moment(start).subtract(1, "hour"),
endTime: moment(oneHourLater),

View file

@ -2,7 +2,6 @@
import moment from "moment";
import { App } from "obsidian";
import { v4 } from "uuid";
import { beforeEach, describe, expect, it, Mock, vi } from "vitest";
import { createMockContainer, MockNotice } from "@/__mocks__/obsidian";
@ -185,7 +184,7 @@ describe("TimesheetExportActions", () => {
timekeep.setState({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(systemTime),
endTime: null,
@ -228,7 +227,7 @@ describe("TimesheetExportActions", () => {
timekeep.setState({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(systemTime),
endTime: moment(systemTime).add(1, "h"),
@ -282,7 +281,7 @@ describe("TimesheetExportActions", () => {
timekeep.setState({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(systemTime),
endTime: moment(systemTime).add(1, "h"),
@ -336,7 +335,7 @@ describe("TimesheetExportActions", () => {
timekeep.setState({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(systemTime),
endTime: moment(systemTime).add(1, "h"),
@ -376,7 +375,7 @@ describe("TimesheetExportActions", () => {
timekeep.setState({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(systemTime),
endTime: moment(systemTime).add(1, "h"),
@ -441,7 +440,7 @@ describe("TimesheetExportActions", () => {
timekeep.setState({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(systemTime),
endTime: moment(systemTime).add(1, "h"),

View file

@ -3,7 +3,6 @@
import type { App } from "obsidian";
import moment from "moment";
import { v4 } from "uuid";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createMockContainer } from "@/__mocks__/obsidian";
@ -25,7 +24,7 @@ describe("TimesheetRowContainer", () => {
const start = moment();
const entry: TimeEntry = {
id: v4(),
id: 1,
name: "Test",
startTime: moment(start),
endTime: null,

View file

@ -2,7 +2,6 @@
import moment from "moment";
import { App } from "obsidian";
import { v4 } from "uuid";
import { beforeEach, describe, expect, it, Mock, vi } from "vitest";
import { createMockContainer } from "@/__mocks__/obsidian";
@ -17,7 +16,7 @@ describe("TimesheetRowContent", () => {
const start = moment();
const entry: TimeEntry = {
id: v4(),
id: 1,
name: "Test",
startTime: moment(start),
endTime: null,
@ -54,7 +53,7 @@ describe("TimesheetRowContent", () => {
it("folder row should have a folder icon", () => {
const entry: TimeEntry = {
id: v4(),
id: 1,
name: "Test",
startTime: null,
endTime: null,
@ -79,7 +78,7 @@ describe("TimesheetRowContent", () => {
it("folder row or groups should be collapsible", () => {
const entry: TimeEntry = {
id: v4(),
id: 1,
name: "Test",
startTime: null,
endTime: null,
@ -131,7 +130,7 @@ describe("TimesheetRowContent", () => {
it("folder row or groups should be expandable", () => {
const entry: TimeEntry = {
id: v4(),
id: 1,
name: "Test",
startTime: null,
endTime: null,
@ -183,7 +182,7 @@ describe("TimesheetRowContent", () => {
it("item should be able to be started from clicking the start icon", () => {
const entry: TimeEntry = {
id: v4(),
id: 1,
name: "Test",
startTime: null,
endTime: null,

View file

@ -3,7 +3,6 @@
import type { App } from "obsidian";
import moment from "moment";
import { v4 } from "uuid";
import { beforeEach, it, describe, Mock, vi, expect, afterEach } from "vitest";
import type { TimekeepSettings } from "@/settings";
@ -41,7 +40,7 @@ describe("TimesheetRowContentEditing", () => {
const start = moment();
const entry: TimeEntry = {
id: v4(),
id: 1,
name: "Test",
startTime: moment(start),
endTime: null,
@ -63,7 +62,7 @@ describe("TimesheetRowContentEditing", () => {
const start = moment();
const entry: TimeEntry = {
id: v4(),
id: 1,
name: "Test",
startTime: moment(start),
endTime: null,
@ -89,7 +88,7 @@ describe("TimesheetRowContentEditing", () => {
const start = moment();
const entry: TimeEntry = {
id: v4(),
id: 1,
name: "Test",
startTime: moment(start),
endTime: moment(start),
@ -122,7 +121,7 @@ describe("TimesheetRowContentEditing", () => {
it("clicking the save button on a group should update the timekeep state and call onFinishEditing", () => {
const entry: TimeEntry = {
id: v4(),
id: 1,
name: "Test",
startTime: null,
endTime: null,
@ -155,7 +154,7 @@ describe("TimesheetRowContentEditing", () => {
it("clicking the save button on a unstarted entry should update the timekeep state and call onFinishEditing", () => {
const entry: TimeEntry = {
id: v4(),
id: 1,
name: "Test",
startTime: null,
endTime: null,
@ -188,7 +187,7 @@ describe("TimesheetRowContentEditing", () => {
it("editing a group entry should hide the start and end time inputs", () => {
const entry: TimeEntry = {
id: v4(),
id: 1,
name: "Test",
startTime: null,
endTime: null,
@ -218,7 +217,7 @@ describe("TimesheetRowContentEditing", () => {
it("clicking delete on an entry should open a modal for confirmation", () => {
const entry: TimeEntry = {
id: v4(),
id: 1,
name: "Test",
startTime: null,
endTime: null,
@ -248,7 +247,7 @@ describe("TimesheetRowContentEditing", () => {
it("cancelling deletion should do nothing", () => {
const entry: TimeEntry = {
id: v4(),
id: 1,
name: "Test",
startTime: null,
endTime: null,
@ -292,7 +291,7 @@ describe("TimesheetRowContentEditing", () => {
it("confirming deletion should remove the entry from the timekeep", () => {
const entry: TimeEntry = {
id: v4(),
id: 1,
name: "Test",
startTime: null,
endTime: null,
@ -337,7 +336,7 @@ describe("TimesheetRowContentEditing", () => {
it("editing a group entry should hide the start and end time inputs", () => {
const start = moment();
const entry: TimeEntry = {
id: v4(),
id: 1,
name: "Test",
startTime: moment(start),
endTime: moment(start),

View file

@ -1,7 +1,6 @@
// @vitest-environment happy-dom
import moment from "moment";
import { v4 } from "uuid";
import { beforeEach, it, describe, expect, afterEach } from "vitest";
import type { TimekeepSettings } from "@/settings";
@ -50,7 +49,7 @@ describe("TimesheetRunningEntry", () => {
timekeep.setState({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(start),
endTime: null,
@ -70,7 +69,7 @@ describe("TimesheetRunningEntry", () => {
timekeep.setState({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(start),
endTime: null,
@ -93,7 +92,7 @@ describe("TimesheetRunningEntry", () => {
timekeep.setState({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(start),
endTime: null,
@ -120,7 +119,7 @@ describe("TimesheetRunningEntry", () => {
timekeep.setState({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(start),
endTime: null,

View file

@ -1,7 +1,6 @@
// @vitest-environment happy-dom
import moment from "moment";
import { v4 } from "uuid";
import { beforeEach, it, describe, vi, expect } from "vitest";
import type { TimekeepSettings } from "@/settings";
@ -65,7 +64,7 @@ describe("TimesheetRunningEntryEditing", () => {
vi.setSystemTime(end.toDate());
const entry = {
id: v4(),
id: 1,
name: "Test",
startTime: moment(start),
endTime: null,

View file

@ -1,7 +1,6 @@
// @vitest-environment happy-dom
import moment from "moment";
import { v4 } from "uuid";
import { describe, it, vi, beforeEach, afterEach, expect } from "vitest";
import { createMockContainer } from "@/__mocks__/obsidian";
@ -21,13 +20,13 @@ describe("TimesheetRunningEntry", () => {
const start = moment();
const entry: TimeEntry = {
id: v4(),
id: 1,
name: "Group",
startTime: null,
endTime: null,
subEntries: [
{
id: v4(),
id: 2,
name: "Test",
startTime: moment(start),
endTime: null,
@ -63,7 +62,7 @@ describe("TimesheetRunningEntry", () => {
const end = moment().add(1, "hour");
vi.setSystemTime(end.toDate());
const id = v4();
const id = 1;
timekeep.setState({
entries: [
@ -116,7 +115,7 @@ describe("TimesheetRunningEntry", () => {
vi.setSystemTime(end.toDate());
const entry = {
id: v4(),
id: 3,
name: "Test",
startTime: moment(start),
endTime: null,
@ -126,13 +125,13 @@ describe("TimesheetRunningEntry", () => {
timekeep.setState({
entries: [
{
id: v4(),
id: 1,
name: "Outer",
startTime: null,
endTime: null,
subEntries: [
{
id: v4(),
id: 2,
name: "Inner",
startTime: null,
endTime: null,

View file

@ -17,13 +17,6 @@ import { defaultTimekeep, type Timekeep } from "@/timekeep/schema";
import { TimekeepAutocomplete } from "@/service/autocomplete";
import { TimekeepRegistry } from "@/service/registry";
vi.mock(import("uuid"), async (importOriginal) => {
return {
...(await importOriginal()),
v4: vi.fn(() => "mocked-uuid"),
};
});
describe("TimesheetStart", () => {
let containerEl: HTMLElement;
let vault: MockVault;
@ -73,7 +66,7 @@ describe("TimesheetStart", () => {
expect(timekeep.getState()).toEqual({
entries: [
{
id: "mocked-uuid",
id: 1,
name: "Test",
startTime: moment(start),
endTime: null,

View file

@ -3,7 +3,6 @@
import type { App } from "obsidian";
import moment from "moment";
import { v4 } from "uuid";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createMockContainer, MockVault } from "@/__mocks__/obsidian";
@ -59,7 +58,7 @@ describe("TimesheetStatusBar", () => {
it("should be able to render a file based status item", () => {
const start = moment();
const entry: TimeEntry = {
id: v4(),
id: 1,
name: "Test",
startTime: moment(start),
endTime: null,
@ -77,7 +76,7 @@ describe("TimesheetStatusBar", () => {
it("should be able to render a markdown based status item", () => {
const start = moment();
const entry: TimeEntry = {
id: v4(),
id: 1,
name: "Test",
startTime: moment(start),
endTime: null,

View file

@ -2,7 +2,6 @@
import moment from "moment";
import { App } from "obsidian";
import { v4 } from "uuid";
import { describe, beforeEach, it, expect, vi, afterEach } from "vitest";
import { createMockContainer, MockVault } from "@/__mocks__/obsidian";
@ -20,7 +19,7 @@ describe("TimesheetStatusBarItem", () => {
const oneHourLater = start.add(1, "hour");
const entry: TimeEntry = {
id: v4(),
id: 1,
name: "Test",
startTime: moment(start),
endTime: null,

View file

@ -2,7 +2,6 @@
import moment from "moment";
import { type App } from "obsidian";
import { v4 } from "uuid";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createMockContainer } from "@/__mocks__/obsidian";
@ -38,7 +37,7 @@ describe("TimesheetTable", () => {
timekeep.setState({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(start),
endTime: moment(start),
@ -54,13 +53,13 @@ describe("TimesheetTable", () => {
timekeep.setState({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: null,
endTime: null,
subEntries: [
{
id: v4(),
id: 2,
name: "Test",
startTime: moment(start),
endTime: moment(start),
@ -89,13 +88,13 @@ describe("TimesheetTable", () => {
timekeep.setState({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: null,
endTime: null,
subEntries: [
{
id: v4(),
id: 2,
name: "Test",
startTime: moment(start),
endTime: moment(start),
@ -112,7 +111,7 @@ describe("TimesheetTable", () => {
timekeep.setState({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: null,
endTime: null,

View file

@ -1,6 +1,5 @@
import fs from "fs/promises";
import moment from "moment";
import { v4 } from "uuid";
export const output = await fs.readFile("src/export/__fixtures__/csv/tableRows.csv", "utf-8");
@ -9,27 +8,27 @@ export const currentTime = moment(start).add(2, "hours");
export const entries = [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(start),
endTime: moment(start).add(1, "hour"),
subEntries: null,
},
{
id: v4(),
id: 2,
name: "Test 2",
startTime: moment(start),
endTime: moment(start).add(2, "hour"),
subEntries: null,
},
{
id: v4(),
id: 3,
name: "Test Group",
startTime: null,
endTime: null,
subEntries: [
{
id: v4(),
id: 4,
name: "Test 3",
startTime: moment(start),
endTime: null,

View file

@ -1,6 +1,5 @@
import fs from "fs/promises";
import moment from "moment";
import { v4 } from "uuid";
export const output = await fs.readFile(
"src/export/__fixtures__/csv/flattenGroupEntries.csv",
@ -11,27 +10,27 @@ export const currentTime = moment("2020-01-01T00:00:00Z");
export const entries = [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(currentTime),
endTime: moment(currentTime).add(1, "hour"),
subEntries: null,
},
{
id: v4(),
id: 2,
name: "Test 2",
startTime: moment(currentTime),
endTime: moment(currentTime).add(2, "hour"),
subEntries: null,
},
{
id: v4(),
id: 3,
name: "Test Group",
startTime: null,
endTime: null,
subEntries: [
{
id: v4(),
id: 4,
name: "Test 3",
startTime: moment(currentTime),
endTime: moment(currentTime).add(2, "hour"),

View file

@ -1,6 +1,5 @@
import fs from "fs/promises";
import moment from "moment";
import { v4 } from "uuid";
export const output = await fs.readFile("src/export/__fixtures__/csv/tableRows.csv", "utf-8");
@ -8,14 +7,14 @@ export const currentTime = moment("2020-01-01T00:00:00Z");
export const entries = [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(currentTime),
endTime: moment(currentTime).add(1, "hour"),
subEntries: null,
},
{
id: v4(),
id: 2,
name: "Test 2",
startTime: moment(currentTime),
endTime: moment(currentTime).add(2, "hour"),

View file

@ -1,6 +1,5 @@
import fs from "fs/promises";
import moment from "moment";
import { v4 } from "uuid";
export const output = await fs.readFile("src/export/__fixtures__/markdown/tableRows.md", "utf-8");
@ -9,27 +8,27 @@ export const currentTime = moment(start).add(2, "hours");
export const entries = [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(start),
endTime: moment(start).add(1, "hour"),
subEntries: null,
},
{
id: v4(),
id: 2,
name: "Test 2",
startTime: moment(start),
endTime: moment(start).add(2, "hour"),
subEntries: null,
},
{
id: v4(),
id: 3,
name: "Test Group",
startTime: null,
endTime: null,
subEntries: [
{
id: v4(),
id: 4,
name: "Test 3",
startTime: moment(start),
endTime: null,

View file

@ -1,6 +1,5 @@
import fs from "fs/promises";
import moment from "moment";
import { v4 } from "uuid";
export const output = await fs.readFile(
"src/export/__fixtures__/markdown/flattenGroupEntries.md",
@ -11,27 +10,27 @@ export const currentTime = moment("2020-01-01T00:00:00Z");
export const entries = [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(currentTime),
endTime: moment(currentTime).add(1, "hour"),
subEntries: null,
},
{
id: v4(),
id: 2,
name: "Test 2",
startTime: moment(currentTime),
endTime: moment(currentTime).add(2, "hour"),
subEntries: null,
},
{
id: v4(),
id: 3,
name: "Test Group",
startTime: null,
endTime: null,
subEntries: [
{
id: v4(),
id: 4,
name: "Test 3",
startTime: moment(currentTime),
endTime: moment(currentTime).add(2, "hour"),

View file

@ -1,6 +1,5 @@
import fs from "fs/promises";
import moment from "moment";
import { v4 } from "uuid";
export const output = await fs.readFile("src/export/__fixtures__/markdown/tableRows.md", "utf-8");
@ -8,14 +7,14 @@ export const currentTime = moment("2020-01-01T00:00:00Z");
export const entries = [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(currentTime),
endTime: moment(currentTime).add(1, "hour"),
subEntries: null,
},
{
id: v4(),
id: 2,
name: "Test 2",
startTime: moment(currentTime),
endTime: moment(currentTime).add(2, "hour"),

View file

@ -1,5 +1,4 @@
import moment from "moment";
import { v4 } from "uuid";
export const output = [
["Test", "20-01-01 13:00:00", "20-01-01 14:00:00", "1.00h"],
@ -13,27 +12,27 @@ export const currentTime = moment(start).add(2, "hours");
export const entries = [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(start),
endTime: moment(start).add(1, "hour"),
subEntries: null,
},
{
id: v4(),
id: 2,
name: "Test 2",
startTime: moment(start),
endTime: moment(start).add(2, "hour"),
subEntries: null,
},
{
id: v4(),
id: 3,
name: "Test Group",
startTime: null,
endTime: null,
subEntries: [
{
id: v4(),
id: 4,
name: "Test 3",
startTime: moment(start),
endTime: null,

View file

@ -1,5 +1,4 @@
import moment from "moment";
import { v4 } from "uuid";
export const output = [
["Test", "20-01-01 13:00:00", "20-01-01 14:00:00", "1.00h"],
@ -12,27 +11,27 @@ export const currentTime = moment("2020-01-01T00:00:00Z");
export const entries = [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(currentTime),
endTime: moment(currentTime).add(1, "hour"),
subEntries: null,
},
{
id: v4(),
id: 2,
name: "Test 2",
startTime: moment(currentTime),
endTime: moment(currentTime).add(2, "hour"),
subEntries: null,
},
{
id: v4(),
id: 3,
name: "Test Group",
startTime: null,
endTime: null,
subEntries: [
{
id: v4(),
id: 4,
name: "Test 3",
startTime: moment(currentTime),
endTime: moment(currentTime).add(2, "hour"),

View file

@ -1,5 +1,4 @@
import moment from "moment";
import { v4 } from "uuid";
export const output = [
["Test", "20-01-01 13:00:00", "20-01-01 14:00:00", "1.00h"],
@ -10,14 +9,14 @@ export const currentTime = moment("2020-01-01T00:00:00Z");
export const entries = [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment(currentTime),
endTime: moment(currentTime).add(1, "hour"),
subEntries: null,
},
{
id: v4(),
id: 2,
name: "Test 2",
startTime: moment(currentTime),
endTime: moment(currentTime).add(2, "hour"),

View file

@ -1,6 +1,5 @@
import moment from "moment";
import { Content } from "pdfmake";
import { v4 } from "uuid";
import { describe, it, expect } from "vitest";
import { defaultSettings, FontFamily, type TimekeepSettings } from "@/settings";
@ -52,14 +51,14 @@ describe("createPdfDefinition", () => {
const timekeep: Timekeep = {
entries: [
{
id: v4(),
id: 1,
name: "Test Entry 1",
startTime: moment("2024-01-01T10:00:00"),
endTime: moment("2024-01-01T11:00:00"),
subEntries: null,
},
{
id: v4(),
id: 2,
name: "Test Entry 2",
startTime: moment("2024-01-01T10:00:00"),
endTime: moment("2024-01-01T11:00:00"),
@ -84,7 +83,7 @@ describe("createPdfDefinition", () => {
it("handles nested subEntries with increased depth (margin)", () => {
const child = {
id: v4(),
id: 2,
name: "Child",
startTime: moment("2024-01-01T10:00:00"),
endTime: moment("2024-01-01T11:00:00"),
@ -92,7 +91,7 @@ describe("createPdfDefinition", () => {
};
const parent = {
id: v4(),
id: 1,
name: "Test Entry 1",
startTime: null,
endTime: null,
@ -124,7 +123,7 @@ describe("createPdfDefinition", () => {
it("handles entries ", () => {
const entry = {
id: v4(),
id: 1,
name: "Test Entry 1",
startTime: moment("2024-01-01T10:00:00"),
endTime: moment("2024-01-01T11:00:00"),
@ -149,7 +148,7 @@ describe("createPdfDefinition", () => {
it("handles entries without startTime", () => {
const entry = {
id: v4(),
id: 1,
name: "Test Entry 1",
startTime: null,
endTime: null,
@ -174,7 +173,7 @@ describe("createPdfDefinition", () => {
it("handles entries without endTime", () => {
const entry = {
id: v4(),
id: 1,
name: "Test Entry 1",
startTime: moment("2024-01-01T10:00:00"),
endTime: null,
@ -217,20 +216,20 @@ describe("createPdfDefinition", () => {
it("applies alternating/group row background logic", () => {
const parent = {
id: v4(),
id: 1,
name: "Test Entry 1",
startTime: null,
endTime: null,
subEntries: [
{
id: v4(),
id: 2,
name: "Child",
startTime: moment("2024-01-01T10:00:00"),
endTime: moment("2024-01-01T11:00:00"),
subEntries: null,
},
{
id: v4(),
id: 3,
name: "Child",
startTime: moment("2024-01-01T10:00:00"),
endTime: moment("2024-01-01T11:00:00"),
@ -302,7 +301,7 @@ describe("createPdfDefinition", () => {
it("renders links correctly inside the entry name", () => {
const entry: TimeEntry = {
id: "1",
id: 1,
name: "Check this [[https://example.com]]",
startTime: null,
endTime: null,

View file

@ -1,5 +1,4 @@
import { App, TFile, Modal, TextComponent, ButtonComponent } from "obsidian";
import { v4 as uuid } from "uuid";
import { exportPdf } from "@/export/pdf";
import { TimekeepSettings } from "@/settings";
@ -7,6 +6,7 @@ import { Store, Unsubscribe } from "@/store";
import { assert } from "@/utils/assert";
import { createCodeBlock } from "@/utils/codeblock";
import { timekeepMergerEntries } from "@/timekeep/id";
import { Timekeep, stripTimekeepRuntimeData } from "@/timekeep/schema";
import { TimekeepEntryItemType, TimekeepRegistry, TimekeepRegistryEntry } from "@/service/registry";
@ -15,7 +15,7 @@ interface TimekeepResult {
timekeep: Timekeep;
file: TFile;
index?: number;
id: string;
id: number;
}
export class TimekeepMergerModal extends Modal {
@ -117,7 +117,7 @@ export class TimekeepMergerModal extends Modal {
this.loadingEl && this.mergeButton && this.searchInput,
"Required elements should be defined"
);
this.loadingEl.removeClass("timekeep-merger-loading--loaded")
this.loadingEl.removeClass("timekeep-merger-loading--loaded");
this.loadingEl.hidden = false;
this.mergeButton.setDisabled(true);
@ -150,7 +150,7 @@ export class TimekeepMergerModal extends Modal {
} catch (err) {
console.error(err);
this.loadingEl.setText("Failed to load timekeep entries.");
this.loadingEl.addClass("timekeep-merger-loading--loaded")
this.loadingEl.addClass("timekeep-merger-loading--loaded");
} finally {
this.mergeButton.setDisabled(false);
this.searchInput.setDisabled(false);
@ -191,7 +191,10 @@ export class TimekeepMergerModal extends Modal {
updateSelectAll() {
if (this.selectContainer) {
this.selectContainer.toggleClass('timekeep-merge-select-container--visible', this.filteredResults.length > 0);
this.selectContainer.toggleClass(
"timekeep-merge-select-container--visible",
this.filteredResults.length > 0
);
}
const isAllSelected = this.isAllSelected();
@ -304,14 +307,14 @@ export class TimekeepMergerModal extends Modal {
cls: "timekeep-merge-item-label",
});
const title = label.createSpan( {
const title = label.createSpan({
cls: "timekeep-merge-item-title",
});
title.textContent = result.index
? `${result.file.basename}: Timekeep ${result.index + 1}`
: `${result.file.basename}`;
const path = label.createSpan( {
const path = label.createSpan({
cls: "timekeep-merge-item-path",
});
path.textContent = `${result.file.path}`;
@ -338,7 +341,7 @@ export class TimekeepMergerModal extends Modal {
switch (entry.type) {
case TimekeepEntryItemType.FILE: {
const timekeep = entry.timekeep;
results.push({ id: uuid(), file: entry.file, timekeep });
results.push({ id: timekeepMergerEntries.next(), file: entry.file, timekeep });
break;
}
case TimekeepEntryItemType.MARKDOWN: {
@ -347,7 +350,7 @@ export class TimekeepMergerModal extends Modal {
const timekeep = timekeepWithPosition.timekeep;
results.push({
id: uuid(),
id: timekeepMergerEntries.next(),
file: entry.file,
timekeep,
index: i,

View file

@ -1,5 +1,4 @@
import moment from "moment";
import { v4 } from "uuid";
import { describe, expect, it, vi } from "vitest";
import { MockVault } from "@/__mocks__/obsidian";
@ -35,7 +34,7 @@ describe("TimekeepAutocomplete", () => {
JSON.stringify({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
@ -64,7 +63,7 @@ describe("TimekeepAutocomplete", () => {
JSON.stringify({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
@ -93,7 +92,7 @@ describe("TimekeepAutocomplete", () => {
JSON.stringify({
entries: [
{
id: v4(),
id: 1,
name: "Test 2",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
@ -116,7 +115,7 @@ describe("TimekeepAutocomplete", () => {
JSON.stringify({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
@ -131,7 +130,7 @@ describe("TimekeepAutocomplete", () => {
JSON.stringify({
entries: [
{
id: v4(),
id: 1,
name: "Test 2",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
@ -170,35 +169,35 @@ describe("TimekeepAutocomplete", () => {
JSON.stringify({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
subEntries: null,
},
{
id: v4(),
id: 2,
name: "Block 1",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
subEntries: null,
},
{
id: v4(),
id: 3,
name: "Part",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
subEntries: null,
},
{
id: v4(),
id: 4,
name: "Part X",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
subEntries: null,
},
{
id: v4(),
id: 5,
name: "",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
@ -234,14 +233,14 @@ describe("TimekeepAutocomplete", () => {
JSON.stringify({
entries: [
{
id: v4(),
id: 1,
name: "Block 1",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
subEntries: null,
},
{
id: v4(),
id: 2,
name: "Block 2",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
@ -293,7 +292,7 @@ describe("TimekeepAutocomplete", () => {
JSON.stringify({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,

View file

@ -1,7 +1,6 @@
import type { Workspace } from "obsidian";
import moment from "moment";
import { v4 } from "uuid";
import { describe, vi, it, expect } from "vitest";
import { MockMarkdownView, MockVault, MockWorkspaceLeaf } from "@/__mocks__/obsidian";
@ -264,7 +263,7 @@ describe("TimekeepRegistry", () => {
const inputTimekeep: Timekeep = {
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
@ -294,7 +293,7 @@ describe("TimekeepRegistry", () => {
const inputTimekeep: Timekeep = {
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
@ -347,7 +346,7 @@ describe("TimekeepRegistry", () => {
JSON.stringify({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
@ -386,7 +385,7 @@ describe("TimekeepRegistry", () => {
JSON.stringify({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
@ -503,7 +502,7 @@ describe("TimekeepRegistry", () => {
JSON.stringify({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
@ -573,7 +572,7 @@ describe("TimekeepRegistry", () => {
JSON.stringify({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
@ -602,7 +601,7 @@ describe("TimekeepRegistry", () => {
JSON.stringify({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
@ -617,7 +616,7 @@ describe("TimekeepRegistry", () => {
JSON.stringify({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
@ -650,7 +649,7 @@ describe("TimekeepRegistry", () => {
JSON.stringify({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
@ -677,7 +676,7 @@ describe("TimekeepRegistry", () => {
JSON.stringify({
entries: [
{
id: v4(),
id: 1,
name: "Test",
startTime: moment("2020-01-01T00:00:00Z"),
endTime: null,
@ -899,14 +898,14 @@ describe("TimekeepRegistry", () => {
const start = moment();
const entry1: TimeEntry = {
id: v4(),
id: 1,
name: "Test 1",
startTime: moment(start),
endTime: null,
subEntries: null,
};
const entry2: TimeEntry = {
id: v4(),
id: 2,
name: "Test 2",
startTime: moment(start),
endTime: null,

View file

@ -1,9 +1,8 @@
import type { Moment } from "moment";
import { v4 as uuid } from "uuid";
import { isEmptyString } from "@/utils/text";
import { timekeepId } from "@/timekeep/id";
import { TimeEntry, TimeEntryGroup } from "@/timekeep/schema";
/**
@ -15,7 +14,7 @@ import { TimeEntry, TimeEntryGroup } from "@/timekeep/schema";
*/
export function createEntry(name: string, startTime: Moment): TimeEntry {
return {
id: uuid(),
id: timekeepId.next(),
name,
startTime,
endTime: null,
@ -112,7 +111,7 @@ function makeGroupEntry(entry: TimeEntry): TimeEntryGroup {
}
return {
id: uuid(),
id: timekeepId.next(),
name: entry.name,
subEntries: [{ ...entry, name: "Part 1" }],
startTime: null,

51
src/timekeep/id.test.ts Normal file
View file

@ -0,0 +1,51 @@
import { describe, it, expect, vi } from "vitest";
import { createIdStore, timekeepId, timekeepMergerEntries } from "@/timekeep/id";
vi.unmock("@/timekeep/id");
describe("createIdStore", () => {
it("should return 1 on first call and increment on subsequent calls", () => {
const store = createIdStore();
expect(store.next()).toBe(1);
expect(store.next()).toBe(2);
expect(store.next()).toBe(3);
});
it("different stores should have independent counters", () => {
const storeA = createIdStore();
const storeB = createIdStore();
expect(storeA.next()).toBe(1);
expect(storeA.next()).toBe(2);
expect(storeB.next()).toBe(1);
expect(storeB.next()).toBe(2);
expect(storeA.next()).toBe(3);
});
});
describe("timekeepId store", () => {
it("should generate incrementing IDs independently", () => {
const id1 = timekeepId.next();
const id2 = timekeepId.next();
const id3 = timekeepId.next();
expect(id2).toBe(id1 + 1);
expect(id3).toBe(id2 + 1);
});
});
describe("timekeepMergerEntries store", () => {
it("should generate incrementing IDs independently from timekeepId", () => {
const idStoreValue = timekeepId.next(); // advance timekeepId
const mergerId1 = timekeepMergerEntries.next();
const mergerId2 = timekeepMergerEntries.next();
expect(mergerId1).toBe(1);
expect(mergerId2).toBe(2);
expect(timekeepId.next()).toBe(idStoreValue + 1);
});
});

28
src/timekeep/id.ts Normal file
View file

@ -0,0 +1,28 @@
/**
* ID store for timekeep entries
*/
export const timekeepId = createIdStore();
/**
* ID store for entries within the timekeep merger modal
*/
export const timekeepMergerEntries = createIdStore();
/**
* Store helper for tracking runtime IDs
*
* @returns
*/
export function createIdStore() {
let nextId: number = 1;
function next() {
const value = nextId;
nextId++;
return value;
}
return {
next,
};
}

View file

@ -1,19 +1,14 @@
import moment from "moment";
import { v4 as uuid } from "uuid";
import { parse } from "valibot";
import { expect, it, describe, vi } from "vitest";
import { expect, it, describe, Mock } from "vitest";
import { timekeepId } from "@/timekeep/id";
import { TIMEKEEP } from "@/timekeep/schema";
vi.mock(import("uuid"), async (importOriginal) => {
return {
...(await importOriginal()),
v4: vi.fn(() => "mocked-uuid"),
};
});
describe("schema transform", () => {
it("transforms input with an added id", () => {
(timekeepId.next as Mock).mockReturnValue(1);
const input = {
entries: [
{
@ -42,20 +37,20 @@ describe("schema transform", () => {
expect(result).toEqual({
entries: [
{
id: "mocked-uuid",
id: 1,
name: "Block 2",
startTime: moment("2024-03-17T01:33:51.630Z"),
endTime: moment("2024-03-17T01:33:55.151Z"),
subEntries: null,
},
{
id: "mocked-uuid",
id: 1,
name: "Block 2",
startTime: null,
endTime: null,
subEntries: [
{
id: "mocked-uuid",
id: 1,
name: "Block 2",
startTime: moment("2024-03-17T01:33:51.630Z"),
endTime: moment("2024-03-17T01:33:55.151Z"),
@ -66,6 +61,6 @@ describe("schema transform", () => {
],
});
expect(uuid).toHaveBeenCalled();
expect(timekeepId.next).toHaveBeenCalled();
});
});

View file

@ -1,7 +1,8 @@
import moment, { Moment } from "moment";
import { v4 as uuid } from "uuid";
import * as v from "valibot";
import { timekeepId } from "@/timekeep/id";
/*
* This file contains the strict schema for parsing timekeep data
* it also contains the types for each timekeep structure.
@ -13,7 +14,7 @@ type RawTimeEntryGroupBase = v.InferOutput<typeof TIME_ENTRY_GROUP_BASE>;
// Type aliases from inferred zod types
export type TimeEntrySingle = RawTimeEntrySingle;
export type TimeEntryGroup = RawTimeEntryGroupBase & {
id: string;
id: number;
subEntries: TimeEntry[];
};
export type TimeEntry = TimeEntrySingle | TimeEntryGroup;
@ -41,7 +42,7 @@ const TIME_ENTRY_SINGLE = v.pipe(
// At runtime a unique ID is inserted
v.transform((entry) => ({
...entry,
id: uuid(),
id: timekeepId.next(),
}))
);
@ -65,7 +66,7 @@ const TIME_ENTRY_GROUP = v.pipe(
// At runtime a unique ID is inserted
v.transform((entry) => ({
...entry,
id: uuid(),
id: timekeepId.next(),
}))
);

View file

@ -39,10 +39,8 @@ export function parseNameSegments(input: string): NameSegment[] {
text: match[1],
url: match[1],
});
} else if (
/* v8 ignore start -- @preserve */ match[2] &&
match[3] /* v8 ignore stop -- @preserve */
) {
} /* v8 ignore start -- @preserve */ else if (match[2] && match[3]) {
/* v8 ignore stop -- @preserve */
// Markdown link ([text](url))
segments.push({
type: NameSegmentType.Link,

View file

@ -64,7 +64,10 @@ export default defineConfig((env) => {
},
test: {
setupFiles: path.resolve(__dirname, "src", "__mocks__", "setupObsidianMocks.ts"),
setupFiles: [
path.resolve(__dirname, "src", "__mocks__", "setupObsidianMocks.ts"),
path.resolve(__dirname, "src", "__mocks__", "setupMocks.ts"),
],
coverage: {
// Exclude mocks and fixtures from coverage
exclude: [