feat: update language support and enhance API functionality
- Changed the default language in `index.html` from Russian to English, updating the title and button aria-labels for improved accessibility. - Refactored the `buildFetchOptions` function in `api.js` to include an optional external abort signal, enhancing request management. - Updated `fetchDuties` and `fetchCalendarEvents` to support request cancellation using the new abort signal, improving error handling. - Added unit tests for the API functions to ensure proper functionality, including handling of 403 errors and request cancellations. - Enhanced CSS styles for duty markers to improve visual consistency. - Removed unused code and improved the overall structure of the JavaScript files for better maintainability.
This commit is contained in:
104
webapp/js/api.test.js
Normal file
104
webapp/js/api.test.js
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Unit tests for api: buildFetchOptions, fetchDuties (403 handling, AbortError).
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, beforeAll } from "vitest";
|
||||
|
||||
beforeAll(() => {
|
||||
document.body.innerHTML =
|
||||
'<div id="calendar"></div><h2 id="monthTitle"></h2>' +
|
||||
'<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>';
|
||||
});
|
||||
|
||||
const mockGetInitData = vi.fn();
|
||||
vi.mock("./auth.js", () => ({ getInitData: () => mockGetInitData() }));
|
||||
|
||||
import { buildFetchOptions, fetchDuties } from "./api.js";
|
||||
import { state } from "./dom.js";
|
||||
|
||||
describe("buildFetchOptions", () => {
|
||||
beforeEach(() => {
|
||||
state.lang = "ru";
|
||||
});
|
||||
|
||||
it("sets X-Telegram-Init-Data when initData provided", () => {
|
||||
const opts = buildFetchOptions("init-data-string");
|
||||
expect(opts.headers["X-Telegram-Init-Data"]).toBe("init-data-string");
|
||||
opts.cleanup();
|
||||
});
|
||||
|
||||
it("omits X-Telegram-Init-Data when initData empty", () => {
|
||||
const opts = buildFetchOptions("");
|
||||
expect(opts.headers["X-Telegram-Init-Data"]).toBeUndefined();
|
||||
opts.cleanup();
|
||||
});
|
||||
|
||||
it("sets Accept-Language from state.lang", () => {
|
||||
state.lang = "en";
|
||||
const opts = buildFetchOptions("");
|
||||
expect(opts.headers["Accept-Language"]).toBe("en");
|
||||
opts.cleanup();
|
||||
});
|
||||
|
||||
it("returns signal and cleanup function", () => {
|
||||
const opts = buildFetchOptions("");
|
||||
expect(opts.signal).toBeDefined();
|
||||
expect(typeof opts.cleanup).toBe("function");
|
||||
opts.cleanup();
|
||||
});
|
||||
|
||||
it("cleanup clears timeout and removes external abort listener", () => {
|
||||
const controller = new AbortController();
|
||||
const opts = buildFetchOptions("", controller.signal);
|
||||
opts.cleanup();
|
||||
controller.abort();
|
||||
});
|
||||
});
|
||||
|
||||
describe("fetchDuties", () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
beforeEach(() => {
|
||||
mockGetInitData.mockReturnValue("test-init-data");
|
||||
state.lang = "ru";
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
});
|
||||
|
||||
it("returns JSON on 200", async () => {
|
||||
globalThis.fetch = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: () => Promise.resolve([{ id: 1 }]),
|
||||
});
|
||||
const result = await fetchDuties("2025-02-01", "2025-02-28");
|
||||
expect(result).toEqual([{ id: 1 }]);
|
||||
});
|
||||
|
||||
it("throws ACCESS_DENIED on 403 with server detail from body", async () => {
|
||||
globalThis.fetch = vi.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
status: 403,
|
||||
json: () => Promise.resolve({ detail: "Custom access denied" }),
|
||||
});
|
||||
await expect(fetchDuties("2025-02-01", "2025-02-28")).rejects.toMatchObject({
|
||||
message: "ACCESS_DENIED",
|
||||
serverDetail: "Custom access denied",
|
||||
});
|
||||
});
|
||||
|
||||
it("rethrows AbortError when request is aborted", async () => {
|
||||
const aborter = new AbortController();
|
||||
const abortError = new DOMException("aborted", "AbortError");
|
||||
globalThis.fetch = vi.fn().mockImplementation(() => {
|
||||
return Promise.reject(abortError);
|
||||
});
|
||||
await expect(
|
||||
fetchDuties("2025-02-01", "2025-02-28", aborter.signal)
|
||||
).rejects.toMatchObject({ name: "AbortError" });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user