Files
siftlode/frontend/src/lib/api/refusalText.dom.test.ts
T
peter ceadf69e43 fix(i18n): hold every refusal reason by type, and refuse what the scan cannot read
Review round 1 of the refusal contract: it claimed more than it held.

- The other reason carriers get their Literal too (BlockedUrl, ClipError,
  FolderError, the playlist and sign-in refusal helpers), so the type
  checker refuses a reason passed by variable or an unknown literal, and
  the contract reads one shape.
- A literal reason in a detail dict built anywhere counts as a reason of
  its code; a code the scan cannot read, a refused() whose reason it
  cannot read, and a new reason passed by variable each fail the contract
  until listed with what holds them.
- A sign-in refusal key the backend no longer sends is caught too.
- localizeDetail's numbers (download cap in GB, upload cap in bytes) and
  the shared-sentence fallbacks are tested in both languages.
- One Literal parser in the contract; a stale comment fixed.
2026-10-31 01:00:26 +01:00

81 lines
3.3 KiB
TypeScript

// @vitest-environment jsdom
//
// A server refusal said through the records (`refusalCodes`), in both languages, by the real i18n —
// one case per record shape, so moving the dispatch into data changed what no reader sees.
import { afterEach, describe, expect, it } from "vitest";
import i18n from "../../i18n";
import { formatBytes } from "../format";
import { localizeDetail } from "./refusalText";
afterEach(async () => {
await i18n.changeLanguage("en");
});
describe.each(["en", "hu"])("localizeDetail (%s)", (lng) => {
const say = async (d: unknown) => {
await i18n.changeLanguage(lng);
return localizeDetail(d);
};
it("names an edit refused for a file that is not video or audio", async () => {
const said = await say({ code: "edit", reason: "not_media" });
expect(said).toBe(i18n.t("downloads.errors.edit.not_media"));
expect(said).not.toBe(i18n.t("downloads.errors.edit.generic"));
});
it("says a family's reason, and its own fallback for a reason it does not know", async () => {
expect(await say({ code: "playlist_move", reason: "same_playlist" })).toBe(
i18n.t("playlists.moveErrors.same_playlist"),
);
expect(await say({ code: "playlist_move", reason: "nope" })).toBe(
i18n.t("playlists.moveFailed"),
);
});
it("reads a sign-in refusal's one-line form", async () => {
expect(await say({ code: "login", reason: "suspended" })).toBe(
i18n.t("welcome.auth.blocked.suspended.short"),
);
});
it("puts a refusal's numbers into its sentence", async () => {
expect(await say({ code: "quota", reason: "max_jobs", limit: 7 })).toContain("7");
expect(await say({ code: "reset", reason: "weak_password", min: 12 })).toContain("12");
expect(await say({ code: "refused", reason: "view_name_taken", name: "Esti" })).toContain(
"Esti",
);
});
it("puts a download cap in GB and an upload cap in bytes, in the reader's number format", async () => {
expect(await say({ code: "quota", reason: "max_bytes", limit: 1.5 * 2 ** 30 })).toContain(
lng === "hu" ? "1,5" : "1.5",
);
const upload = await say({ code: "upload_too_large", limit: 5_242_880 });
expect(upload).toContain(formatBytes(5_242_880, lng));
expect(upload).not.toBe(i18n.t("downloads.errors.upload.tooLarge", { size: "" }));
});
it("says a shared-sentence code's own fallback for a reason it does not know", async () => {
expect(await say({ code: "quota", reason: "nope" })).toBe(
i18n.t("downloads.errors.quota.generic"),
);
expect(await say({ code: "collection", reason: "nope" })).toBe(
i18n.t("plex.collection.editFailed"),
);
});
it("says a shared-sentence code's fallback, and a fixed code's one sentence", async () => {
expect(await say({ code: "reset", reason: "invalid" })).toBe(
i18n.t("welcome.auth.deadLink.short"),
);
expect(await say({ code: "sync_cooldown" })).toBe(i18n.t("errors.syncCooldown"));
});
it("reads a code no screen shows, or one it does not know, as the generic failure", async () => {
expect(await say({ code: "bad_cursor" })).toBe(i18n.t("errors.generic"));
expect(await say({ code: "never_sent" })).toBe(i18n.t("errors.generic"));
expect(await say({ code: "constructor" })).toBe(i18n.t("errors.generic"));
expect(await say(null)).toBe(i18n.t("errors.generic"));
});
});