Files
kandev/apps/web/components/settings/workflow-pipeline-editor-helpers.test.tsx
T
2026-08-05 13:44:25 +01:00

103 lines
4.3 KiB
TypeScript

import { afterEach, describe, expect, it } from "vitest";
import { cleanup, render, screen } from "@testing-library/react";
import { i18n } from "@/lib/i18n";
import type { WorkflowStep } from "@/lib/types/http";
import {
getChildrenCompletedTransitionType,
HelpTip,
PROMPT_TEMPLATES,
STEP_COLORS,
} from "./workflow-pipeline-editor-helpers";
/**
* Attribute-borne copy has no second check: the pseudo-locale oracle walks text
* nodes, so an `aria-label` reverted to an English literal leaves no trace on
* screen and nothing downstream catches it (see docs/i18n.md, "It cannot see
* copy that is not a text node"). `HelpTip` is the densest attribute-copy site
* on this surface — every help affordance in the pipeline editor is one — so its
* default label is pinned here rather than left to lint alone.
*/
describe("HelpTip aria-label", () => {
// `cleanup()` is load-bearing, not redundant: vitest.config.ts does not set
// `globals: true`, so `afterEach` is not on globalThis and RTL never
// auto-registers its own cleanup. Without this, the two renders below collide
// on `[data-testid="tip"]`.
afterEach(async () => {
cleanup();
await i18n.changeLanguage("en");
});
it("resolves its default label through the catalog", () => {
render(<HelpTip text="body" testId="tip" />);
expect(screen.getByTestId("tip").getAttribute("aria-label")).toBe("More information");
});
it("follows a locale switch rather than being frozen at boot", async () => {
render(<HelpTip text="body" testId="tip" />);
await i18n.changeLanguage("pseudo");
const label = screen.getByTestId("tip").getAttribute("aria-label") ?? "";
expect(label).not.toBe("More information");
expect(label).toMatch(/[À-ɏ]/);
});
// `ariaLabel` short-circuits the default (`ariaLabel ?? t(...)`), so the
// override is whatever the caller passes — every call site in this surface
// passes an already-translated string.
it("lets a caller supply a pre-translated label override", () => {
render(<HelpTip text="body" testId="tip" ariaLabel="Custom label" />);
expect(screen.getByTestId("tip").getAttribute("aria-label")).toBe("Custom label");
});
});
/**
* Both tables are the guard's documented blind spot — a literal assigned to a
* SCREAMING_CASE identifier is skipped entirely — so their shape is asserted
* here: every entry carries a catalog key that resolves, and neither the
* persisted `value` nor the persisted `prompt` is translated.
*/
describe("step colour and prompt-template tables", () => {
it("gives every colour a resolving label key and a Tailwind value", () => {
for (const color of STEP_COLORS) {
expect(color.value).toMatch(/^bg-[a-z]+-\d{3}$/);
expect(color.labelKey.startsWith("workflows:")).toBe(true);
expect(i18n.t(color.labelKey)).not.toBe(color.labelKey);
}
});
it("translates template labels but never their persisted prompt bodies", () => {
for (const template of PROMPT_TEMPLATES) {
expect(i18n.t(template.labelKey)).not.toBe(template.labelKey);
// The prompt is written into WorkflowStep.prompt and sent to the agent
// verbatim, so it must stay a plain English literal.
expect(template.prompt).toMatch(/^[\s\S]*[A-Za-z]{4,}[\s\S]*$/);
expect(template.prompt.startsWith("workflows:")).toBe(false);
}
});
it("derives the review base from the remote default branch", () => {
const reviewPrompt = PROMPT_TEMPLATES.find(
(template) => template.labelKey === "workflows:templateCodeReview",
)?.prompt;
expect(reviewPrompt).toContain("git remote show origin | grep 'HEAD branch'");
expect(reviewPrompt).toContain('git merge-base "$BASE_REF" HEAD');
expect(reviewPrompt).not.toContain("git merge-base origin/main HEAD");
});
});
describe("workflow pipeline editor helpers", () => {
it("reads the all child tasks complete transition from workflow step events", () => {
const step = {
events: {
on_children_completed: [{ type: "move_to_step", config: { step_id: "done-step" } }],
},
} as WorkflowStep;
expect(getChildrenCompletedTransitionType(step)).toBe("move_to_step");
});
it("defaults all child tasks complete to none when no transition is configured", () => {
expect(getChildrenCompletedTransitionType({ events: {} } as WorkflowStep)).toBe("none");
});
});