diff --git a/src/__mocks__/setupObsidianMocks.ts b/src/__mocks__/setupObsidianMocks.ts index 20b3d24..d2626e6 100644 --- a/src/__mocks__/setupObsidianMocks.ts +++ b/src/__mocks__/setupObsidianMocks.ts @@ -17,5 +17,8 @@ vi.mock("obsidian", () => { Vault: MockVault, Component: MockComponent, Notice: MockNotice, + setIcon: vi.fn((containerEl: HTMLElement, icon: string) => { + containerEl.innerHTML += ``; + }), }; }); diff --git a/src/components/domComponent.test.ts b/src/components/domComponent.test.ts index e2a3710..526e7da 100644 --- a/src/components/domComponent.test.ts +++ b/src/components/domComponent.test.ts @@ -72,7 +72,7 @@ describe("DomComponent", () => { comp.wrapperEl = wrapper; container.appendChild(wrapper); - comp.onunload(); + comp.unload(); expect(comp.unmounted).toBe(true); // only unload sets unmounted expect(wrapper.parentElement).toBeNull(); @@ -89,7 +89,7 @@ describe("DomComponent", () => { child.wrapperEl = wrapper; container.appendChild(wrapper); - child.onunload(); + child.unload(); expect(child.unmounted).toBe(true); expect(wrapper.parentElement).toBe(container); diff --git a/src/components/obsidianIcon.test.ts b/src/components/obsidianIcon.test.ts index 283afbf..c369880 100644 --- a/src/components/obsidianIcon.test.ts +++ b/src/components/obsidianIcon.test.ts @@ -7,12 +7,6 @@ import { createMockContainer } from "@/__mocks__/obsidian"; import { createObsidianIcon } from "./obsidianIcon"; -vi.mock("obsidian", () => ({ - setIcon: vi.fn((containerEl: HTMLElement, icon: string) => { - containerEl.innerHTML += ``; - }), -})); - describe("createObsidianIcon", () => { let container: HTMLElement; diff --git a/src/components/timesheet.test.ts b/src/components/timesheet.test.ts new file mode 100644 index 0000000..10137f6 --- /dev/null +++ b/src/components/timesheet.test.ts @@ -0,0 +1,5 @@ +// @vitest-environment happy-dom + +import { describe } from "vitest"; + +describe("Timesheet", () => {}); diff --git a/src/components/timesheetApp.test.ts b/src/components/timesheetApp.test.ts new file mode 100644 index 0000000..70c0fce --- /dev/null +++ b/src/components/timesheetApp.test.ts @@ -0,0 +1,5 @@ +// @vitest-environment happy-dom + +import { describe } from "vitest"; + +describe("TimesheetApp", () => {}); diff --git a/src/components/timesheetCounters.test.ts b/src/components/timesheetCounters.test.ts index bb7b38e..2a13900 100644 --- a/src/components/timesheetCounters.test.ts +++ b/src/components/timesheetCounters.test.ts @@ -35,7 +35,7 @@ describe("TimesheetCounters", () => { }); it("should initialize wrapper and timers on load", () => { - component.onload(); + component.load(); expect(component.wrapperEl).toBeInstanceOf(HTMLDivElement); expect(component.wrapperEl?.className).toBe("timekeep-timers"); @@ -47,13 +47,13 @@ describe("TimesheetCounters", () => { it("should call updateTimers on load", () => { const spy = vi.spyOn(component, "updateTimers"); - component.onload(); + component.load(); expect(spy).toHaveBeenCalled(); }); it("should set timer values using getEntryDuration and getTotalDuration", () => { - component.onload(); + component.load(); const currentSetValues = vi.spyOn(component.currentTimer, "setValues"); const totalSetValues = vi.spyOn(component.totalTimer, "setValues"); @@ -73,7 +73,7 @@ describe("TimesheetCounters", () => { const registerSpy = vi.spyOn(component, "registerInterval"); - component.onload(); + component.load(); expect(component.currentContentInterval).toBeDefined(); expect(registerSpy).toHaveBeenCalledWith(component.currentContentInterval); @@ -87,14 +87,14 @@ describe("TimesheetCounters", () => { const clearSpy = vi.spyOn(global, "clearInterval"); isKeepRunning.mockReturnValue(true); - component.onload(); + component.load(); expect(clearSpy).toHaveBeenCalledWith(fakeIntervalID); }); it("should show the current and total duration if theres a running entry", () => { const start = moment(); - const oneHourLater = moment().add(1, "hour"); + const oneHourLater = moment(start).add(1, "hour"); vi.useFakeTimers(); vi.setSystemTime(oneHourLater.toDate()); @@ -124,7 +124,7 @@ describe("TimesheetCounters", () => { it("should show total duration as the sum of all entry durations", () => { const start = moment(); - const oneHourLater = moment().add(1, "hour"); + const oneHourLater = moment(start).add(1, "hour"); vi.useFakeTimers(); vi.setSystemTime(oneHourLater.toDate()); diff --git a/src/components/timesheetExportActions.test.ts b/src/components/timesheetExportActions.test.ts new file mode 100644 index 0000000..fac1c80 --- /dev/null +++ b/src/components/timesheetExportActions.test.ts @@ -0,0 +1,5 @@ +// @vitest-environment happy-dom + +import { describe } from "vitest"; + +describe("TimesheetExportActions", () => {}); diff --git a/src/components/timesheetFileEntry.test.ts b/src/components/timesheetFileEntry.test.ts new file mode 100644 index 0000000..ceb021d --- /dev/null +++ b/src/components/timesheetFileEntry.test.ts @@ -0,0 +1,5 @@ +// @vitest-environment happy-dom + +import { describe } from "vitest"; + +describe("TimesheetFileEntry", () => {}); diff --git a/src/components/timesheetLoadError.test.ts b/src/components/timesheetLoadError.test.ts index 237a477..16a6c62 100644 --- a/src/components/timesheetLoadError.test.ts +++ b/src/components/timesheetLoadError.test.ts @@ -34,7 +34,7 @@ describe("TimesheetLoadError", () => { expect(container.children.length).toBe(1); - component.onunload(); + component.unload(); expect(container.children.length).toBe(0); }); }); diff --git a/src/components/timesheetNameInput.test.ts b/src/components/timesheetNameInput.test.ts new file mode 100644 index 0000000..1866b59 --- /dev/null +++ b/src/components/timesheetNameInput.test.ts @@ -0,0 +1,5 @@ +// @vitest-environment happy-dom + +import { describe } from "vitest"; + +describe("TimesheetNameInput", () => {}); diff --git a/src/components/timesheetRowContainer.test.ts b/src/components/timesheetRowContainer.test.ts new file mode 100644 index 0000000..600eb40 --- /dev/null +++ b/src/components/timesheetRowContainer.test.ts @@ -0,0 +1,5 @@ +// @vitest-environment happy-dom + +import { describe } from "vitest"; + +describe("TimesheetRowContainer", () => {}); diff --git a/src/components/timesheetRowContent.test.ts b/src/components/timesheetRowContent.test.ts new file mode 100644 index 0000000..805b896 --- /dev/null +++ b/src/components/timesheetRowContent.test.ts @@ -0,0 +1,5 @@ +// @vitest-environment happy-dom + +import { describe } from "vitest"; + +describe("TimesheetRowContent", () => {}); diff --git a/src/components/timesheetRowContent.ts b/src/components/timesheetRowContent.ts index 6c04280..e123c72 100644 --- a/src/components/timesheetRowContent.ts +++ b/src/components/timesheetRowContent.ts @@ -14,7 +14,7 @@ import { formatTimestamp } from "@/utils/time"; import { DomComponent } from "./domComponent"; import { createObsidianIcon } from "./obsidianIcon"; import { TimekeepName } from "./timesheetName"; -import { TimesheetRowDurationComponent } from "./timesheetRowDuration"; +import { TimesheetRowDuration } from "./timesheetRowDuration"; /** * Component for the contents of a timesheet row @@ -123,7 +123,7 @@ export class TimesheetRowContent extends DomComponent { cls: ["timekeep-col", "timekeep-col--duration"], }); - const duration = new TimesheetRowDurationComponent(durationColEl, entry); + const duration = new TimesheetRowDuration(durationColEl, entry); this.addChild(duration); diff --git a/src/components/timesheetRowContentEditing.test.ts b/src/components/timesheetRowContentEditing.test.ts new file mode 100644 index 0000000..b35dafc --- /dev/null +++ b/src/components/timesheetRowContentEditing.test.ts @@ -0,0 +1,5 @@ +// @vitest-environment happy-dom + +import { describe } from "vitest"; + +describe("TimesheetRowContentEditing", () => {}); diff --git a/src/components/timesheetRowDuration.test.ts b/src/components/timesheetRowDuration.test.ts index 37fd65c..39b307a 100644 --- a/src/components/timesheetRowDuration.test.ts +++ b/src/components/timesheetRowDuration.test.ts @@ -7,12 +7,12 @@ import { createMockContainer } from "@/__mocks__/obsidian"; import * as queries from "@/timekeep/queries"; import * as timeUtils from "@/utils/time"; -import { TimesheetRowDurationComponent } from "./timesheetRowDuration"; +import { TimesheetRowDuration } from "./timesheetRowDuration"; -describe("TimesheetRowDurationComponent", () => { +describe("TimesheetRowDuration", () => { let container: HTMLElement; let entry: any; - let component: TimesheetRowDurationComponent; + let component: TimesheetRowDuration; beforeEach(() => { container = createMockContainer(); @@ -30,7 +30,7 @@ describe("TimesheetRowDurationComponent", () => { // Mock setInterval / clearInterval vi.useFakeTimers(); - component = new TimesheetRowDurationComponent(container, entry); + component = new TimesheetRowDuration(container, entry); }); afterEach(() => { @@ -38,7 +38,7 @@ describe("TimesheetRowDurationComponent", () => { }); it("should create a wrapper span with the correct class", () => { - component.onload(); + component.load(); expect(component.wrapperEl).toBeInstanceOf(HTMLSpanElement); expect(component.wrapperEl?.className).toBe("timekeep-time"); @@ -47,13 +47,13 @@ describe("TimesheetRowDurationComponent", () => { it("should call updateTime on load", () => { const spy = vi.spyOn(component, "updateTime"); - component.onload(); + component.load(); expect(spy).toHaveBeenCalled(); }); it("should set textContent based on getEntryDuration and formatDurationLong", () => { - component.onload(); + component.load(); expect(queries.getEntryDuration).toHaveBeenCalledWith(entry, expect.any(moment)); expect(timeUtils.formatDurationLong).toHaveBeenCalledWith(42); @@ -64,7 +64,7 @@ describe("TimesheetRowDurationComponent", () => { (queries.isEntryRunning as any).mockReturnValue(true); const registerSpy = vi.spyOn(component, "registerInterval"); - component.onload(); + component.load(); expect(component.currentContentInterval).toBeDefined(); expect(registerSpy).toHaveBeenCalledWith(component.currentContentInterval); @@ -76,7 +76,7 @@ describe("TimesheetRowDurationComponent", () => { const clearSpy = vi.spyOn(global, "clearInterval"); (queries.isEntryRunning as any).mockReturnValue(true); - component.onload(); + component.load(); expect(clearSpy).toHaveBeenCalledWith(fakeInterval); }); @@ -84,7 +84,7 @@ describe("TimesheetRowDurationComponent", () => { it("should not schedule interval if entry is not running", () => { (queries.isEntryRunning as any).mockReturnValue(false); const registerSpy = vi.spyOn(component, "registerInterval"); - component.onload(); + component.load(); expect(component.currentContentInterval).not.toBeDefined(); expect(registerSpy).not.toHaveBeenCalled(); diff --git a/src/components/timesheetRowDuration.ts b/src/components/timesheetRowDuration.ts index 1cc0f8c..2bdf3a2 100644 --- a/src/components/timesheetRowDuration.ts +++ b/src/components/timesheetRowDuration.ts @@ -10,7 +10,7 @@ import { DomComponent } from "./domComponent"; * Component for rendering the live-updating duration on * an entry row */ -export class TimesheetRowDurationComponent extends DomComponent { +export class TimesheetRowDuration extends DomComponent { /** The entry this duration belongs to */ entry: TimeEntry; diff --git a/src/components/timesheetSaveError.test.ts b/src/components/timesheetSaveError.test.ts index cda3b46..c10eb70 100644 --- a/src/components/timesheetSaveError.test.ts +++ b/src/components/timesheetSaveError.test.ts @@ -34,7 +34,7 @@ describe("TimesheetSaveError", () => { }); it("renders wrapper, error message, and buttons on load", () => { - component.onload(); + component.load(); const wrapper = container.querySelector(".timekeep-container"); expect(wrapper).not.toBeNull(); @@ -54,13 +54,13 @@ describe("TimesheetSaveError", () => { }); it("calls handleSaveTimekeep on retry button click", () => { - component.onload(); + component.load(); component.onRetrySave(); expect(handleSaveTimekeep).toHaveBeenCalledWith(timekeepStore.getState()); }); it("writes JSON to clipboard on copy button click", async () => { - component.onload(); + component.load(); await component.onCopy(); @@ -70,8 +70,8 @@ describe("TimesheetSaveError", () => { }); it("removes wrapper on unload", () => { - component.onload(); - component.onunload(); + component.load(); + component.unload(); const wrapper = container.querySelector(".timekeep-container"); expect(wrapper).toBeNull(); }); diff --git a/src/components/timesheetStart.test.ts b/src/components/timesheetStart.test.ts new file mode 100644 index 0000000..6883353 --- /dev/null +++ b/src/components/timesheetStart.test.ts @@ -0,0 +1,5 @@ +// @vitest-environment happy-dom + +import { describe } from "vitest"; + +describe("TimesheetStart", () => {}); diff --git a/src/components/timesheetStartEditing.test.ts b/src/components/timesheetStartEditing.test.ts new file mode 100644 index 0000000..2aa84eb --- /dev/null +++ b/src/components/timesheetStartEditing.test.ts @@ -0,0 +1,5 @@ +// @vitest-environment happy-dom + +import { describe } from "vitest"; + +describe("TimesheetStartEditing", () => {}); diff --git a/src/components/timesheetStartRunning.test.ts b/src/components/timesheetStartRunning.test.ts new file mode 100644 index 0000000..0054a64 --- /dev/null +++ b/src/components/timesheetStartRunning.test.ts @@ -0,0 +1,5 @@ +// @vitest-environment happy-dom + +import { describe } from "vitest"; + +describe("TimesheetStartRunning", () => {}); diff --git a/src/components/timesheetStatusBarItem.test.ts b/src/components/timesheetStatusBarItem.test.ts new file mode 100644 index 0000000..4da5d7f --- /dev/null +++ b/src/components/timesheetStatusBarItem.test.ts @@ -0,0 +1,76 @@ +// @vitest-environment happy-dom + +import moment from "moment"; +import { v4 } from "uuid"; +import { describe, beforeEach, it, expect, vi, afterEach } from "vitest"; + +import { createMockContainer } from "@/__mocks__/obsidian"; +import { TimeEntry } from "@/timekeep/schema"; + +import { TimesheetStatusBarItem } from "./timesheetStatusBarItem"; + +describe("TimesheetStatusBarItem", () => { + const start = moment(); + const oneHourLater = start.add(1, "hour"); + + const entry: TimeEntry = { + id: v4(), + name: "Test", + startTime: moment(start), + endTime: null, + subEntries: null, + }; + let containerEl: HTMLElement; + let onOpen: VoidFunction; + let onStop: VoidFunction; + + beforeEach(() => { + onOpen = vi.fn(); + onStop = vi.fn(); + + vi.useFakeTimers(); + vi.setSystemTime(oneHourLater.toDate()); + + containerEl = createMockContainer(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("should initialize with the correct entry and callback functions", () => { + const statusBarItem = new TimesheetStatusBarItem(containerEl, entry, onOpen, onStop); + + expect(statusBarItem.entry).toBe(entry); + expect(statusBarItem.onOpen).toBe(onOpen); + expect(statusBarItem.onStop).toBe(onStop); + }); + + it("should call onStop when the stop icon is clicked", () => { + const statusBarItem = new TimesheetStatusBarItem(containerEl, entry, onOpen, onStop); + statusBarItem.load(); + + // Simulate click on the stop icon + const stopIcon = containerEl.querySelector(".button-icon"); + const event = new MouseEvent("click", { bubbles: true, cancelable: true }); + if (stopIcon) { + stopIcon.dispatchEvent(event); + } + + expect(onStop).toHaveBeenCalledTimes(1); + }); + + it("should call onOpen when the content area is clicked", () => { + const statusBarItem = new TimesheetStatusBarItem(containerEl, entry, onOpen, onStop); + statusBarItem.load(); + + // Simulate click on the content element + const contentEl = containerEl.querySelector(".timekeep-status-item__content"); + const event = new MouseEvent("click", { bubbles: true, cancelable: true }); + if (contentEl) { + contentEl.dispatchEvent(event); + } + + expect(onOpen).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/components/timesheetStatusBarItem.ts b/src/components/timesheetStatusBarItem.ts index 0e381bf..fed8464 100644 --- a/src/components/timesheetStatusBarItem.ts +++ b/src/components/timesheetStatusBarItem.ts @@ -2,7 +2,7 @@ import { TimeEntry } from "@/timekeep/schema"; import { DomComponent } from "./domComponent"; import { createObsidianIcon } from "./obsidianIcon"; -import { TimesheetRowDurationComponent } from "./timesheetRowDuration"; +import { TimesheetRowDuration } from "./timesheetRowDuration"; export class TimesheetStatusBarItem extends DomComponent { /** The entry this duration belongs to */ @@ -12,7 +12,7 @@ export class TimesheetStatusBarItem extends DomComponent { currentContentInterval: number | undefined; /** Component for rendering the real time updating duration */ - duration: TimesheetRowDurationComponent; + duration: TimesheetRowDuration; /** Callback to open the file when the content is clicked */ onOpen: VoidFunction; @@ -44,7 +44,6 @@ export class TimesheetStatusBarItem extends DomComponent { "timekeep-status-item__button", "button-icon", ]); - this.registerDomEvent(stopIcon, "click", this.onStop); stopIcon.title = "Stop Entry"; const contentEl = wrapperEl.createDiv({ @@ -57,8 +56,9 @@ export class TimesheetStatusBarItem extends DomComponent { text: entry.name + ":", }); - this.addChild(new TimesheetRowDurationComponent(contentEl, entry)); + this.addChild(new TimesheetRowDuration(contentEl, entry)); + this.registerDomEvent(stopIcon, "click", this.onStop); this.registerDomEvent(contentEl, "click", this.onOpen); } } diff --git a/src/components/timesheetTable.test.ts b/src/components/timesheetTable.test.ts new file mode 100644 index 0000000..5139acf --- /dev/null +++ b/src/components/timesheetTable.test.ts @@ -0,0 +1,5 @@ +// @vitest-environment happy-dom + +import { describe } from "vitest"; + +describe("TimesheetTable", () => {}); diff --git a/src/components/timesheetTimer.test.ts b/src/components/timesheetTimer.test.ts index 32eac46..72a3f4e 100644 --- a/src/components/timesheetTimer.test.ts +++ b/src/components/timesheetTimer.test.ts @@ -16,7 +16,7 @@ describe("TimesheetTimer", () => { }); it("should create timer elements on load", () => { - timer.onload(); + timer.load(); const timerEl = container.children[0] as HTMLElement; expect(timerEl).toBeDefined(); @@ -36,16 +36,16 @@ describe("TimesheetTimer", () => { }); it("should remove timer element on unload", () => { - timer.onload(); + timer.load(); const timerEl = container.children[0] as HTMLElement; expect(timerEl).toBeDefined(); - timer.onunload(); + timer.unload(); expect(timerEl.parentElement).toBeNull(); }); it("should hide or show the timer element", () => { - timer.onload(); + timer.load(); const timerEl = container.children[0] as HTMLElement; timer.setHidden(true); @@ -56,7 +56,7 @@ describe("TimesheetTimer", () => { }); it("should set primary and secondary values correctly", () => { - timer.onload(); + timer.load(); const timerEl = container.children[0] as HTMLElement; const primary = timerEl.children[0] as HTMLDivElement; const secondary = timerEl.children[1] as HTMLDivElement; @@ -73,7 +73,7 @@ describe("TimesheetTimer", () => { }); it("secondary value should be hidden if it is empty", () => { - timer.onload(); + timer.load(); const timerEl = container.children[0] as HTMLElement; const secondary = timerEl.children[1] as HTMLDivElement;