/** * Unit tests for currentDuty (findCurrentDuty, renderCurrentDutyContent, showCurrentDutyView). */ import { describe, it, expect, beforeAll, vi } from "vitest"; vi.mock("./api.js", () => ({ fetchDuties: vi.fn().mockResolvedValue([]) })); import { findCurrentDuty, getRemainingTime, renderCurrentDutyContent } from "./currentDuty.js"; describe("currentDuty", () => { beforeAll(() => { document.body.innerHTML = '
' + '
' + '
' + '
' + '' + "
"; }); describe("getRemainingTime", () => { it("returns hours and minutes until end from now", () => { const endAt = "2025-03-02T17:30:00.000Z"; vi.useFakeTimers(); vi.setSystemTime(new Date("2025-03-02T12:00:00.000Z")); const { hours, minutes } = getRemainingTime(endAt); vi.useRealTimers(); expect(hours).toBe(5); expect(minutes).toBe(30); }); it("returns 0 when end is in the past", () => { const endAt = "2025-03-02T09:00:00.000Z"; vi.useFakeTimers(); vi.setSystemTime(new Date("2025-03-02T12:00:00.000Z")); const { hours, minutes } = getRemainingTime(endAt); vi.useRealTimers(); expect(hours).toBe(0); expect(minutes).toBe(0); }); }); describe("findCurrentDuty", () => { it("returns duty when now is between start_at and end_at", () => { const now = new Date(); const start = new Date(now); start.setHours(start.getHours() - 1, 0, 0, 0); const end = new Date(now); end.setHours(end.getHours() + 1, 0, 0, 0); const duties = [ { event_type: "duty", full_name: "Иванов", start_at: start.toISOString(), end_at: end.toISOString() } ]; const duty = findCurrentDuty(duties); expect(duty).not.toBeNull(); expect(duty.full_name).toBe("Иванов"); }); it("returns null when no duty overlaps current time", () => { const duties = [ { event_type: "duty", full_name: "Past", start_at: "2020-01-01T09:00:00Z", end_at: "2020-01-01T17:00:00Z" }, { event_type: "duty", full_name: "Future", start_at: "2030-01-01T09:00:00Z", end_at: "2030-01-01T17:00:00Z" } ]; expect(findCurrentDuty(duties)).toBeNull(); }); }); describe("renderCurrentDutyContent", () => { it("renders no-duty message and back button when duty is null", () => { const html = renderCurrentDutyContent(null, "en"); expect(html).toContain("current-duty-card"); expect(html).toContain("current-duty-card--no-duty"); expect(html).toContain("Current Duty"); expect(html).toContain("current-duty-no-duty-wrap"); expect(html).toContain("current-duty-no-duty-icon"); expect(html).toContain("current-duty-no-duty"); expect(html).toContain("No one is on duty right now"); expect(html).toContain("Back to calendar"); expect(html).toContain('data-action="back"'); expect(html).not.toContain("current-duty-live-dot"); }); it("renders duty card with name, shift, remaining time, and back button when duty has no contacts", () => { const duty = { event_type: "duty", full_name: "Иванов Иван", start_at: "2025-03-02T06:00:00.000Z", end_at: "2025-03-03T06:00:00.000Z" }; const html = renderCurrentDutyContent(duty, "ru"); expect(html).toContain("current-duty-live-dot"); expect(html).toContain("Текущее дежурство"); expect(html).toContain("Иванов Иван"); expect(html).toContain("Смена"); expect(html).toContain("current-duty-remaining"); expect(html).toMatch(/Осталось:\s*\d+ч\s*\d+мин/); expect(html).toContain("Назад к календарю"); expect(html).toContain('data-action="back"'); }); it("renders duty card with phone and Telegram links when present", () => { const duty = { event_type: "duty", full_name: "Alice", start_at: "2025-03-02T09:00:00", end_at: "2025-03-02T17:00:00", phone: "+7 900 123-45-67", username: "alice_dev" }; const html = renderCurrentDutyContent(duty, "en"); expect(html).toContain("Alice"); expect(html).toContain("current-duty-remaining"); expect(html).toMatch(/Remaining:\s*\d+h\s*\d+min/); expect(html).toContain("current-duty-contact-row"); expect(html).toContain("current-duty-contact-row--blocks"); expect(html).toContain("current-duty-contact-block"); expect(html).toContain('href="tel:'); expect(html).toContain("+7 900 123-45-67"); expect(html).toContain("https://t.me/"); expect(html).toContain("alice_dev"); expect(html).toContain("Back to calendar"); }); }); describe("showCurrentDutyView", () => { it("hides the global loading element when called", async () => { vi.resetModules(); const { showCurrentDutyView } = await import("./currentDuty.js"); await showCurrentDutyView(() => {}); const loading = document.getElementById("loading"); expect(loading).not.toBeNull(); expect(loading.classList.contains("hidden")).toBe(true); }); }); });