Files
siftlode/frontend/src/lib/api/refusalText.dom.test.ts
T
peter c221452a00 feat(downloads): a clip or an edit can be a GIF, shared as a picture
A clip and an editor range can come out as a silent GIF (long side
320/480/640, 12 fps, at most 30 s, one palette per GIF); one over 30 MB
is not kept. A shared picture is served inline under the preview's
sandbox, and a GIF's link card is the GIF itself up to 15 MB, the still
for Facebook's crawler.
2026-11-11 01:50:55 +01:00

90 lines
3.7 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 clip's cap in minutes and a GIF's in seconds, from a clip or an edit", async () => {
expect(await say({ code: "clip", reason: "too_long", limit_s: 600 })).toContain("10");
for (const code of ["clip", "edit"]) {
const said = await say({ code, reason: "gif_too_long", limit_s: 30 });
expect(said).toContain("30");
expect(said).not.toContain("{{");
}
});
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"));
});
});