Some checks failed
CI / lint-and-test (push) Failing after 45s
- Added Node.js setup and webapp testing steps to the CI workflow for improved integration. - Updated HTML to link multiple CSS files for better modularity and organization of styles. - Removed deprecated `style.css` and introduced new CSS files for base styles, calendar, day detail, hints, markers, states, and duty list to enhance maintainability and readability. - Implemented new styles for improved presentation of duty information and user interactions. - Added unit tests for new API functions and contact link rendering to ensure functionality and reliability.
165 lines
5.7 KiB
JavaScript
165 lines
5.7 KiB
JavaScript
/**
|
||
* Unit tests for dutyList (dutyTimelineCardHtml, dutyItemHtml, contact rendering).
|
||
*/
|
||
|
||
import { describe, it, expect, beforeAll, vi, afterEach } from "vitest";
|
||
import * as dateUtils from "./dateUtils.js";
|
||
import { dutyTimelineCardHtml, dutyItemHtml } from "./dutyList.js";
|
||
|
||
describe("dutyList", () => {
|
||
beforeAll(() => {
|
||
document.body.innerHTML =
|
||
'<div id="calendar"></div><div id="monthTitle"></div>' +
|
||
'<div id="dutyList"></div><div id="loading"></div><div id="error"></div>' +
|
||
'<div id="accessDenied"></div><div class="header"></div><div class="weekdays"></div>' +
|
||
'<button id="prevMonth"></button><button id="nextMonth"></button>';
|
||
});
|
||
|
||
describe("dutyTimelineCardHtml", () => {
|
||
it("renders duty with full_name and time range (no flip when no contacts)", () => {
|
||
const d = {
|
||
event_type: "duty",
|
||
full_name: "Иванов",
|
||
start_at: "2025-02-25T09:00:00",
|
||
end_at: "2025-02-25T18:00:00",
|
||
};
|
||
const html = dutyTimelineCardHtml(d, false);
|
||
expect(html).toContain("Иванов");
|
||
expect(html).toContain("duty-item");
|
||
expect(html).toContain("duty-timeline-card");
|
||
expect(html).not.toContain("duty-flip-card");
|
||
expect(html).not.toContain("duty-flip-btn");
|
||
});
|
||
|
||
it("uses flip-card wrapper with front and back when phone or username present", () => {
|
||
const d = {
|
||
event_type: "duty",
|
||
full_name: "Alice",
|
||
start_at: "2025-03-01T09:00:00",
|
||
end_at: "2025-03-01T17:00:00",
|
||
phone: "+79991234567",
|
||
username: "alice_dev",
|
||
};
|
||
const html = dutyTimelineCardHtml(d, false);
|
||
expect(html).toContain("Alice");
|
||
expect(html).toContain("duty-flip-card");
|
||
expect(html).toContain("duty-flip-inner");
|
||
expect(html).toContain("duty-flip-front");
|
||
expect(html).toContain("duty-flip-back");
|
||
expect(html).toContain("duty-flip-btn");
|
||
expect(html).toContain('data-flipped="false"');
|
||
expect(html).toContain("duty-contact-row");
|
||
expect(html).toContain('href="tel:');
|
||
expect(html).toContain("+79991234567");
|
||
expect(html).toContain("https://t.me/");
|
||
expect(html).toContain("alice_dev");
|
||
});
|
||
|
||
it("front face contains name and time; back face contains contact links", () => {
|
||
const d = {
|
||
event_type: "duty",
|
||
full_name: "Bob",
|
||
start_at: "2025-03-02T08:00:00",
|
||
end_at: "2025-03-02T16:00:00",
|
||
phone: "+79001112233",
|
||
};
|
||
const html = dutyTimelineCardHtml(d, false);
|
||
const frontStart = html.indexOf("duty-flip-front");
|
||
const backStart = html.indexOf("duty-flip-back");
|
||
const frontSection = html.slice(frontStart, backStart);
|
||
const backSection = html.slice(backStart);
|
||
expect(frontSection).toContain("Bob");
|
||
expect(frontSection).toContain("time");
|
||
expect(frontSection).not.toContain("duty-contact-row");
|
||
expect(backSection).toContain("Bob");
|
||
expect(backSection).toContain("duty-contact-row");
|
||
expect(backSection).toContain("tel:");
|
||
});
|
||
|
||
it("omits flip wrapper and button when phone and username are missing", () => {
|
||
const d = {
|
||
event_type: "duty",
|
||
full_name: "Bob",
|
||
start_at: "2025-03-02T08:00:00",
|
||
end_at: "2025-03-02T16:00:00",
|
||
};
|
||
const html = dutyTimelineCardHtml(d, false);
|
||
expect(html).toContain("Bob");
|
||
expect(html).not.toContain("duty-flip-card");
|
||
expect(html).not.toContain("duty-flip-btn");
|
||
expect(html).not.toContain("duty-contact-row");
|
||
});
|
||
});
|
||
|
||
describe("dutyItemHtml", () => {
|
||
afterEach(() => {
|
||
vi.restoreAllMocks();
|
||
});
|
||
|
||
it("escapes timeOrRange so HTML special chars are not rendered raw", () => {
|
||
vi.spyOn(dateUtils, "formatHHMM").mockReturnValue("12:00 & 13:00");
|
||
vi.spyOn(dateUtils, "formatDateKey").mockReturnValue("01.02.2025");
|
||
const d = {
|
||
event_type: "duty",
|
||
full_name: "Test",
|
||
start_at: "2025-03-01T12:00:00",
|
||
end_at: "2025-03-01T13:00:00",
|
||
};
|
||
const html = dutyItemHtml(d, null, false);
|
||
expect(html).toContain("&");
|
||
expect(html).not.toContain('<div class="time">12:00 & 13:00');
|
||
});
|
||
|
||
it("uses typeLabelOverride when provided", () => {
|
||
const d = {
|
||
event_type: "duty",
|
||
full_name: "Alice",
|
||
start_at: "2025-03-01T09:00:00",
|
||
end_at: "2025-03-01T17:00:00",
|
||
};
|
||
const html = dutyItemHtml(d, "On duty now", false);
|
||
expect(html).toContain("On duty now");
|
||
expect(html).toContain("Alice");
|
||
});
|
||
|
||
it("shows duty.until when showUntilEnd is true for duty", () => {
|
||
const d = {
|
||
event_type: "duty",
|
||
full_name: "Bob",
|
||
start_at: "2025-03-01T09:00:00",
|
||
end_at: "2025-03-01T17:00:00",
|
||
};
|
||
const html = dutyItemHtml(d, null, true);
|
||
expect(html).toMatch(/until|до/);
|
||
expect(html).toMatch(/\d{2}:\d{2}/);
|
||
});
|
||
|
||
it("renders vacation with date range", () => {
|
||
vi.spyOn(dateUtils, "formatDateKey")
|
||
.mockReturnValueOnce("01.03")
|
||
.mockReturnValueOnce("05.03");
|
||
const d = {
|
||
event_type: "vacation",
|
||
full_name: "Charlie",
|
||
start_at: "2025-03-01T00:00:00",
|
||
end_at: "2025-03-05T23:59:59",
|
||
};
|
||
const html = dutyItemHtml(d);
|
||
expect(html).toContain("01.03 – 05.03");
|
||
expect(html).toContain("duty-item--vacation");
|
||
});
|
||
|
||
it("applies extraClass to container", () => {
|
||
const d = {
|
||
event_type: "duty",
|
||
full_name: "Dana",
|
||
start_at: "2025-03-01T09:00:00",
|
||
end_at: "2025-03-01T17:00:00",
|
||
};
|
||
const html = dutyItemHtml(d, null, false, "duty-item--current");
|
||
expect(html).toContain("duty-item--current");
|
||
expect(html).toContain("Dana");
|
||
});
|
||
});
|
||
});
|