Files
kandev/apps/web/components/task/changes-panel-hooks.test.ts
T
2026-08-31 03:26:51 +00:00

110 lines
4.1 KiB
TypeScript

import { afterEach, describe, expect, it } from "vitest";
import i18n from "i18next";
import type { PerRepoOperationResult } from "@/hooks/domains/session/use-session-git";
import { describePerRepo } from "./changes-panel-hooks";
/**
* The multi-repo git toast. Its three branches were English template literals
* (`${operationName} partially succeeded`, `Failed in N repos: ...`) and are now
* catalog messages with five interpolation variables between them.
*
* A misspelled variable, such as `repo` for `repos` or `count` for `succeeded`, makes
* i18next leave the placeholder in the rendered string rather than throw, so it
* ships a toast reading "succeeded in {{count}} repos" and nothing else catches
* it. Every assertion below therefore checks for a leftover `{{`.
*/
afterEach(async () => {
await i18n.changeLanguage("en");
});
const repo = (
name: string,
success: boolean,
error?: string,
errorCode?: string,
): PerRepoOperationResult =>
({ repository_name: name, success, error, error_code: errorCode }) as PerRepoOperationResult;
const noPlaceholders = (result: { title: string; description: string }) => {
expect(result.title).not.toContain("{{");
expect(result.description).not.toContain("{{");
};
describe("describePerRepo", () => {
it("reports every repo succeeding", () => {
const result = describePerRepo([repo("api", true), repo("web", true)], "Push");
expect(result.variant).toBe("success");
expect(result.title).toBe("Push successful");
expect(result.description).toBe("Push succeeded in 2 repos: api, web");
noPlaceholders(result);
});
it("reports every repo failing, with each repo's error", () => {
const result = describePerRepo(
[repo("api", false, "no upstream"), repo("web", false, "conflict")],
"Rebase",
);
expect(result.variant).toBe("error");
expect(result.title).toBe("Rebase failed");
expect(result.description).toBe("Failed in 2 repos: api: no upstream; web: conflict");
noPlaceholders(result);
});
it("surfaces a partial success as an error but names what did succeed", () => {
const result = describePerRepo([repo("api", true), repo("web", false, "conflict")], "Merge");
expect(result.variant).toBe("error");
expect(result.title).toBe("Merge partially succeeded");
expect(result.description).toBe(
"Merge succeeded in 1 of 2 repos (api); failed in web: conflict",
);
noPlaceholders(result);
});
it("falls back to a generic error when a repo reports no message", () => {
const result = describePerRepo([repo("api", false)], "Pull");
expect(result.description).toBe("Failed in 1 repo: api: Unknown error");
noPlaceholders(result);
});
it("uses the singular form for a single failing repo", () => {
// `_one` vs `_other` is the reason `count` is passed at all; English differs
// only in "repo"/"repos", but a locale with more forms needs the number.
const one = describePerRepo([repo("api", false, "boom")], "Push");
const two = describePerRepo([repo("api", false, "boom"), repo("web", false, "boom")], "Push");
expect(one.description).toContain("1 repo:");
expect(two.description).toContain("2 repos:");
});
it("uses localized recovery copy for a bounded publication failure", () => {
const result = describePerRepo(
[repo("api", false, "raw remote output", "empty_remote_remote_changed")],
"Push",
);
expect(result.description).toBe(
"Failed in 1 repo: api: The remote changed before the base branch was published. Refresh the task and try again.",
);
expect(result.description).not.toContain("raw remote output");
});
it("keeps every branch resolving after a locale switch", async () => {
await i18n.changeLanguage("pseudo");
for (const result of [
describePerRepo([repo("api", true)], "Push"),
describePerRepo([repo("api", false, "boom")], "Push"),
describePerRepo([repo("api", true), repo("web", false, "boom")], "Push"),
]) {
noPlaceholders(result);
// The repo names are data and stay ASCII; the frame around them must not.
expect(result.title).toMatch(/[^\x20-\x7E]/);
}
});
});