feat: enhance CI workflow and update webapp styles
Some checks failed
CI / lint-and-test (push) Failing after 45s
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.
This commit is contained in:
@@ -15,7 +15,7 @@ beforeAll(() => {
|
||||
const mockGetInitData = vi.fn();
|
||||
vi.mock("./auth.js", () => ({ getInitData: () => mockGetInitData() }));
|
||||
|
||||
import { buildFetchOptions, fetchDuties } from "./api.js";
|
||||
import { buildFetchOptions, fetchDuties, apiGet, fetchCalendarEvents } from "./api.js";
|
||||
import { state } from "./dom.js";
|
||||
|
||||
describe("buildFetchOptions", () => {
|
||||
@@ -102,3 +102,111 @@ describe("fetchDuties", () => {
|
||||
).rejects.toMatchObject({ name: "AbortError" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("apiGet", () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
beforeEach(() => {
|
||||
mockGetInitData.mockReturnValue("init-data");
|
||||
state.lang = "en";
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
});
|
||||
|
||||
it("builds URL with path and query params and returns response", async () => {
|
||||
let capturedUrl = "";
|
||||
globalThis.fetch = vi.fn().mockImplementation((url) => {
|
||||
capturedUrl = url;
|
||||
return Promise.resolve({ ok: true, status: 200 });
|
||||
});
|
||||
await apiGet("/api/duties", { from: "2025-02-01", to: "2025-02-28" });
|
||||
expect(capturedUrl).toContain("/api/duties");
|
||||
expect(capturedUrl).toContain("from=2025-02-01");
|
||||
expect(capturedUrl).toContain("to=2025-02-28");
|
||||
});
|
||||
|
||||
it("omits query string when params empty", async () => {
|
||||
let capturedUrl = "";
|
||||
globalThis.fetch = vi.fn().mockImplementation((url) => {
|
||||
capturedUrl = url;
|
||||
return Promise.resolve({ ok: true });
|
||||
});
|
||||
await apiGet("/api/health", {});
|
||||
expect(capturedUrl).toBe(window.location.origin + "/api/health");
|
||||
});
|
||||
|
||||
it("passes X-Telegram-Init-Data and Accept-Language headers", async () => {
|
||||
let capturedOpts = null;
|
||||
globalThis.fetch = vi.fn().mockImplementation((url, opts) => {
|
||||
capturedOpts = opts;
|
||||
return Promise.resolve({ ok: true });
|
||||
});
|
||||
await apiGet("/api/duties", { from: "2025-01-01", to: "2025-01-31" });
|
||||
expect(capturedOpts?.headers["X-Telegram-Init-Data"]).toBe("init-data");
|
||||
expect(capturedOpts?.headers["Accept-Language"]).toBe("en");
|
||||
});
|
||||
|
||||
it("passes an abort signal to fetch when options.signal provided", async () => {
|
||||
const controller = new AbortController();
|
||||
let capturedSignal = null;
|
||||
globalThis.fetch = vi.fn().mockImplementation((url, opts) => {
|
||||
capturedSignal = opts.signal;
|
||||
return Promise.resolve({ ok: true });
|
||||
});
|
||||
await apiGet("/api/duties", {}, { signal: controller.signal });
|
||||
expect(capturedSignal).toBeDefined();
|
||||
expect(capturedSignal).toBeInstanceOf(AbortSignal);
|
||||
});
|
||||
});
|
||||
|
||||
describe("fetchCalendarEvents", () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
beforeEach(() => {
|
||||
mockGetInitData.mockReturnValue("init-data");
|
||||
state.lang = "ru";
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
});
|
||||
|
||||
it("returns JSON array on 200", async () => {
|
||||
globalThis.fetch = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () => Promise.resolve([{ date: "2025-02-25", summary: "Holiday" }]),
|
||||
});
|
||||
const result = await fetchCalendarEvents("2025-02-01", "2025-02-28");
|
||||
expect(result).toEqual([{ date: "2025-02-25", summary: "Holiday" }]);
|
||||
});
|
||||
|
||||
it("returns empty array on non-OK response", async () => {
|
||||
globalThis.fetch = vi.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
status: 500,
|
||||
});
|
||||
const result = await fetchCalendarEvents("2025-02-01", "2025-02-28");
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("returns empty array on 403 (does not throw)", async () => {
|
||||
globalThis.fetch = vi.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
status: 403,
|
||||
});
|
||||
const result = await fetchCalendarEvents("2025-02-01", "2025-02-28");
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("rethrows AbortError when request is aborted", async () => {
|
||||
const aborter = new AbortController();
|
||||
const abortError = new DOMException("aborted", "AbortError");
|
||||
globalThis.fetch = vi.fn().mockImplementation(() => Promise.reject(abortError));
|
||||
await expect(
|
||||
fetchCalendarEvents("2025-02-01", "2025-02-28", aborter.signal)
|
||||
).rejects.toMatchObject({ name: "AbortError" });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user