ccc62d9324
* feat(github): configurable quick-action presets and PR branch checkout - Add workspace-scoped action presets (icon, label, hint, prompt template) for PRs and Issues, editable from GitHub settings with reset-to-defaults. - When starting a task from a PR, check out the PR head branch directly in the worktree instead of branching off main. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(github): extract action-preset service to keep service.go under line limit Splits the preset CRUD methods out of the oversized service.go and applies prettier formatting to three frontend files flagged by CI. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(github): address review feedback on action presets - Prevent infinite refetch loop when fetching presets fails by tracking attempted workspaces in a ref instead of re-entering the effect on every loading-state change. - Drop unused savePR/saveIssue helpers from the hook; only save() is consumed. - Reorder the init spread in updateGitHubActionPresets/resetGitHubActionPresets so callers can't accidentally overwrite method/body. - Tighten the GitHubActionPreset.icon type with string & {} so known icon keys keep autocomplete. - Sharpen the eye-icon test to assert the exact IconEye mapping. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(github): address second round of review feedback - Seed preset editor drafts from current store state via lazy initializers so a warm mount shows the user's saved customisations instead of silently reverting to defaults when Save is clicked. - Guard Reset button with its own in-flight state to prevent double-firing. - Stop freezing resolved defaults into the stored row on a partial update; persist only what the store has for the untouched kind so future default improvements still reach those workspaces. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(github): tighten quick-actions editor UI Replace the stacked PR/Issue editors and multi-line preset cards with a tabbed layout and single-line rows. Each row shows icon · label · hint inline; an "Edit prompt" toggle reveals the template textarea only when needed. Drops the redundant section headings, up/down reorder buttons, and verbose template helper label (hint moved into the textarea placeholder). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(github): widen icon select trigger so the icon is visible The icon select trigger had a fixed 40px width which the SelectTrigger's built-in chevron, padding, and icon gap all competed for, clipping the icon out of view. Drop the width override so the trigger sizes to its content. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
95 lines
2.7 KiB
TypeScript
95 lines
2.7 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import {
|
|
DEFAULT_ISSUE_PRESETS,
|
|
DEFAULT_PR_PRESETS,
|
|
iconForPresetKey,
|
|
interpolatePromptTemplate,
|
|
resolveIssuePresets,
|
|
resolvePRPresets,
|
|
toTaskPreset,
|
|
} from "./action-presets";
|
|
import { IconEye, IconSparkles } from "@tabler/icons-react";
|
|
|
|
describe("interpolatePromptTemplate", () => {
|
|
it("replaces {url} and {title} placeholders", () => {
|
|
const out = interpolatePromptTemplate("Review {url} titled {title}", {
|
|
url: "https://gh/pr/1",
|
|
title: "Fix bug",
|
|
});
|
|
expect(out).toBe("Review https://gh/pr/1 titled Fix bug");
|
|
});
|
|
|
|
it("leaves unknown placeholders intact", () => {
|
|
const out = interpolatePromptTemplate("See {url} and {unknown}", {
|
|
url: "u",
|
|
title: "t",
|
|
});
|
|
expect(out).toBe("See u and {unknown}");
|
|
});
|
|
|
|
it("handles a template with no placeholders", () => {
|
|
expect(interpolatePromptTemplate("nothing to sub", { url: "u", title: "t" })).toBe(
|
|
"nothing to sub",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("iconForPresetKey", () => {
|
|
it("returns the mapped Tabler icon for a known key", () => {
|
|
expect(iconForPresetKey("eye")).toBe(IconEye);
|
|
});
|
|
|
|
it("falls back to sparkle for unknown or missing keys", () => {
|
|
expect(iconForPresetKey(undefined)).toBe(IconSparkles);
|
|
expect(iconForPresetKey("nonsense")).toBe(IconSparkles);
|
|
});
|
|
});
|
|
|
|
describe("resolvePRPresets / resolveIssuePresets", () => {
|
|
it("uses defaults when stored is null", () => {
|
|
const pr = resolvePRPresets(null);
|
|
expect(pr.length).toBe(DEFAULT_PR_PRESETS.length);
|
|
const issue = resolveIssuePresets(null);
|
|
expect(issue.length).toBe(DEFAULT_ISSUE_PRESETS.length);
|
|
});
|
|
|
|
it("uses defaults when the stored list is empty (treats as unset)", () => {
|
|
const pr = resolvePRPresets({ workspace_id: "w", pr: [], issue: [] });
|
|
expect(pr.length).toBe(DEFAULT_PR_PRESETS.length);
|
|
});
|
|
|
|
it("uses stored presets when non-empty", () => {
|
|
const stored = {
|
|
workspace_id: "w",
|
|
pr: [
|
|
{
|
|
id: "custom",
|
|
label: "Custom",
|
|
hint: "h",
|
|
icon: "eye",
|
|
prompt_template: "Do it on {url}",
|
|
},
|
|
],
|
|
issue: [],
|
|
};
|
|
const pr = resolvePRPresets(stored);
|
|
expect(pr).toHaveLength(1);
|
|
expect(pr[0].label).toBe("Custom");
|
|
expect(pr[0].prompt({ url: "https://x", title: "t" })).toBe("Do it on https://x");
|
|
});
|
|
});
|
|
|
|
describe("toTaskPreset", () => {
|
|
it("wires the prompt function to interpolate the stored template", () => {
|
|
const stored = {
|
|
id: "id",
|
|
label: "L",
|
|
hint: "H",
|
|
icon: "message",
|
|
prompt_template: "On {url} for {title}",
|
|
};
|
|
const preset = toTaskPreset(stored);
|
|
expect(preset.prompt({ url: "U", title: "T" })).toBe("On U for T");
|
|
});
|
|
});
|