mirror of
https://github.com/semanticdata/obsidian-pomodoro.git
synced 2026-07-22 12:00:26 +00:00
220 lines
6.7 KiB
TypeScript
220 lines
6.7 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
import "../setup";
|
|
import PomodoroPlugin from "../../src/main";
|
|
import { moment } from "obsidian";
|
|
import { PluginWithPrivates } from "../setup";
|
|
import { createStandardTestPlugin, cleanupStandardTestPlugin } from "../helpers/plugin-test-helpers";
|
|
|
|
describe("PomodoroTimer - Basic Operations", () => {
|
|
let plugin: PomodoroPlugin;
|
|
let mockStatusBarItem: HTMLElement;
|
|
|
|
beforeEach(async () => {
|
|
const setup = await createStandardTestPlugin();
|
|
plugin = setup.plugin;
|
|
mockStatusBarItem = (plugin as PluginWithPrivates)._statusBarItem;
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await cleanupStandardTestPlugin(plugin);
|
|
});
|
|
|
|
describe("Basic Timer Operations (with fake timers)", () => {
|
|
beforeEach(() => {
|
|
jest.useFakeTimers({ legacyFakeTimers: false });
|
|
jest.setSystemTime(new Date());
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
it("should start the timer and update display", () => {
|
|
const timer = (plugin as PluginWithPrivates)._timer;
|
|
|
|
timer.toggleTimer();
|
|
expect(timer.isRunning).toBe(true);
|
|
expect(mockStatusBarItem.classList.add).toHaveBeenCalledWith(
|
|
"pomodoro-active",
|
|
);
|
|
expect(mockStatusBarItem.classList.remove).toHaveBeenCalledWith(
|
|
"pomodoro-paused",
|
|
);
|
|
expect(window.setInterval).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("should update display when timer counts down", () => {
|
|
const now = Date.now();
|
|
const timer = (plugin as PluginWithPrivates)._timer;
|
|
|
|
// Set up querySelector to return text element
|
|
const textEl: any = { textContent: "" };
|
|
mockStatusBarItem.querySelector = jest
|
|
.fn()
|
|
.mockReturnValue(textEl);
|
|
|
|
// Start the timer
|
|
timer.toggleTimer();
|
|
expect(timer.isRunning).toBe(true);
|
|
|
|
// Advance the fake system time by 1 second and update display manually.
|
|
const after = now + 1000;
|
|
jest.setSystemTime(after);
|
|
|
|
// Call updateDisplay to reflect the new time
|
|
(timer as any).updateDisplay();
|
|
|
|
// Should show 24:59 (25 minutes - 1 second)
|
|
expect(textEl.textContent).toBe("24:59");
|
|
|
|
// Clean up
|
|
timer.pauseTimer();
|
|
});
|
|
|
|
it("should format time with consistent MM:SS format (positive and negative)", () => {
|
|
const now = Date.now();
|
|
const timer = (plugin as PluginWithPrivates)._timer;
|
|
|
|
const textEl: any = { textContent: "" };
|
|
mockStatusBarItem.querySelector = jest
|
|
.fn()
|
|
.mockReturnValue(textEl);
|
|
|
|
timer.toggleTimer();
|
|
|
|
// Test various positive times
|
|
const positiveTestCases = [
|
|
{ offset: 0, expected: "25:00" }, // Full time
|
|
{ offset: 1000, expected: "24:59" }, // 1 second elapsed
|
|
{ offset: 60000, expected: "24:00" }, // 1 minute elapsed
|
|
{ offset: 9000, expected: "24:51" }, // 9 seconds elapsed
|
|
{ offset: 599000, expected: "15:01" }, // 9:59 elapsed
|
|
{ offset: 1494000, expected: "0:06" }, // Almost done
|
|
];
|
|
|
|
positiveTestCases.forEach(({ offset, expected }) => {
|
|
jest.setSystemTime(now + offset);
|
|
(timer as any).updateDisplay();
|
|
expect(textEl.textContent).toBe(expected);
|
|
});
|
|
|
|
// Test various negative times (overflow scenarios)
|
|
const negativeTestCases = [
|
|
{ offset: 1500000 + 1000, expected: "-0:01" }, // 1 second over
|
|
{ offset: 1500000 + 5000, expected: "-0:05" }, // 5 seconds over
|
|
{ offset: 1500000 + 60000, expected: "-1:00" }, // 1 minute over
|
|
{ offset: 1500000 + 65000, expected: "-1:05" }, // 1:05 over
|
|
{ offset: 1500000 + 600000, expected: "-10:00" }, // 10 minutes over
|
|
{ offset: 1500000 + 3599000, expected: "-59:59" }, // 59:59 over
|
|
];
|
|
|
|
negativeTestCases.forEach(({ offset, expected }) => {
|
|
jest.setSystemTime(now + offset);
|
|
(timer as any).updateDisplay();
|
|
expect(textEl.textContent).toBe(expected);
|
|
});
|
|
|
|
timer.pauseTimer();
|
|
});
|
|
});
|
|
|
|
it("should pause the timer", () => {
|
|
const timer = (plugin as PluginWithPrivates)._timer;
|
|
|
|
// Clear previous classList calls from setup
|
|
(mockStatusBarItem.classList.add as jest.Mock).mockClear();
|
|
(mockStatusBarItem.classList.remove as jest.Mock).mockClear();
|
|
|
|
timer.toggleTimer();
|
|
timer.pauseTimer();
|
|
expect(timer.isRunning).toBe(false);
|
|
expect(mockStatusBarItem.classList.remove).toHaveBeenCalledWith(
|
|
"pomodoro-active",
|
|
);
|
|
expect(mockStatusBarItem.classList.add).toHaveBeenCalledWith(
|
|
"pomodoro-paused",
|
|
);
|
|
expect(window.clearInterval).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("should reset the timer", () => {
|
|
const timer = (plugin as PluginWithPrivates)._timer;
|
|
timer.resetTimer();
|
|
|
|
// After reset, timeRemaining should match the configured work duration
|
|
expect(timer.timeRemaining.asMilliseconds()).toBeCloseTo(
|
|
moment
|
|
.duration(plugin.settings.workMinutes, "minutes")
|
|
.asMilliseconds(),
|
|
);
|
|
});
|
|
|
|
it("should cycle durations correctly", () => {
|
|
const timer = (plugin as PluginWithPrivates)._timer;
|
|
|
|
timer.cycleDuration();
|
|
expect(timer.timerType).toBe(1); // Short break
|
|
|
|
timer.cycleDuration();
|
|
expect(timer.timerType).toBe(2); // Long break
|
|
|
|
timer.cycleDuration();
|
|
expect(timer.timerType).toBe(0); // Work
|
|
});
|
|
|
|
it("should not cycle duration when timer is running", () => {
|
|
const timer = (plugin as PluginWithPrivates)._timer;
|
|
|
|
timer.toggleTimer();
|
|
const initialTimerType = timer.timerType;
|
|
timer.cycleDuration();
|
|
|
|
expect(timer.timerType).toBe(initialTimerType);
|
|
timer.pauseTimer();
|
|
});
|
|
|
|
describe("resetToWorkState Method", () => {
|
|
it("should reset timer to work state and pause", () => {
|
|
const timer = (plugin as PluginWithPrivates)._timer;
|
|
|
|
// Set timer to different state
|
|
timer["currentDurationIndex"] = 2; // Long break
|
|
timer["workIntervalCount"] = 3;
|
|
timer.toggleTimer();
|
|
|
|
// Call resetToWorkState (covers lines 191-194)
|
|
timer.resetToWorkState();
|
|
|
|
expect(timer["currentDurationIndex"]).toBe(0); // Work state
|
|
expect(timer["workIntervalCount"]).toBe(0); // Reset count
|
|
expect(timer.isRunning).toBe(false); // Should be paused
|
|
// Compare durations numerically to avoid 1ms differences from timing
|
|
expect(timer.timeRemaining.asMilliseconds()).toBeCloseTo(
|
|
moment
|
|
.duration(plugin.settings.workMinutes, "minutes")
|
|
.asMilliseconds(),
|
|
-2,
|
|
); // Work duration
|
|
});
|
|
|
|
it("should reset to work state even when already in work state", () => {
|
|
const timer = (plugin as PluginWithPrivates)._timer;
|
|
|
|
// Already in work state but with some progress
|
|
timer["currentDurationIndex"] = 0; // Work state
|
|
timer["workIntervalCount"] = 2;
|
|
timer["timeEnd"] = moment.utc(moment.now()).add(500, "seconds"); // Some progress made
|
|
|
|
timer.resetToWorkState();
|
|
|
|
expect(timer["currentDurationIndex"]).toBe(0); // Still work state
|
|
expect(timer["workIntervalCount"]).toBe(0); // Reset count
|
|
expect(timer.timeRemaining.asMilliseconds()).toBeCloseTo(
|
|
moment
|
|
.duration(plugin.settings.workMinutes, "minutes")
|
|
.asMilliseconds(),
|
|
-2,
|
|
); // Reset duration
|
|
});
|
|
});
|
|
});
|