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>
156 lines
4.8 KiB
Go
156 lines
4.8 KiB
Go
package github
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
)
|
|
|
|
// TestGetActionPresets_EmptyReturnsDefaults verifies that a workspace with no
|
|
// stored presets gets the hard-coded defaults — the /github page should never
|
|
// see an empty dropdown for a fresh workspace.
|
|
func TestGetActionPresets_EmptyReturnsDefaults(t *testing.T) {
|
|
svc, _, _ := setupSyncTest(t)
|
|
ctx := context.Background()
|
|
|
|
got, err := svc.GetActionPresets(ctx, "ws-1")
|
|
if err != nil {
|
|
t.Fatalf("GetActionPresets: %v", err)
|
|
}
|
|
if got.WorkspaceID != "ws-1" {
|
|
t.Errorf("WorkspaceID = %q, want %q", got.WorkspaceID, "ws-1")
|
|
}
|
|
if len(got.PR) != len(DefaultPRActionPresets()) {
|
|
t.Errorf("PR preset count = %d, want %d", len(got.PR), len(DefaultPRActionPresets()))
|
|
}
|
|
if len(got.Issue) != len(DefaultIssueActionPresets()) {
|
|
t.Errorf("Issue preset count = %d, want %d", len(got.Issue), len(DefaultIssueActionPresets()))
|
|
}
|
|
}
|
|
|
|
// TestUpdateActionPresets_PartialUpdate verifies that updating only the PR
|
|
// list leaves the Issue list at defaults, and vice versa.
|
|
func TestUpdateActionPresets_PartialUpdate(t *testing.T) {
|
|
svc, _, _ := setupSyncTest(t)
|
|
ctx := context.Background()
|
|
|
|
custom := []ActionPreset{
|
|
{ID: "custom", Label: "Custom review", Hint: "Deep review", Icon: "eye", PromptTemplate: "Review {url}"},
|
|
}
|
|
got, err := svc.UpdateActionPresets(ctx, &UpdateActionPresetsRequest{
|
|
WorkspaceID: "ws-1",
|
|
PR: &custom,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("UpdateActionPresets: %v", err)
|
|
}
|
|
if len(got.PR) != 1 || got.PR[0].Label != "Custom review" {
|
|
t.Errorf("unexpected PR presets: %+v", got.PR)
|
|
}
|
|
if len(got.Issue) != len(DefaultIssueActionPresets()) {
|
|
t.Errorf("Issue presets not preserved at defaults: %+v", got.Issue)
|
|
}
|
|
|
|
// Re-read to make sure it persisted.
|
|
reread, err := svc.GetActionPresets(ctx, "ws-1")
|
|
if err != nil {
|
|
t.Fatalf("GetActionPresets after update: %v", err)
|
|
}
|
|
if len(reread.PR) != 1 || reread.PR[0].Label != "Custom review" {
|
|
t.Errorf("stored PR presets lost: %+v", reread.PR)
|
|
}
|
|
}
|
|
|
|
// TestUpdateActionPresets_NormalisesInput verifies that empty-label presets
|
|
// are dropped and missing IDs get auto-assigned.
|
|
func TestUpdateActionPresets_NormalisesInput(t *testing.T) {
|
|
svc, _, _ := setupSyncTest(t)
|
|
ctx := context.Background()
|
|
|
|
input := []ActionPreset{
|
|
{ID: "", Label: " Has label ", PromptTemplate: "go"},
|
|
{ID: "skip", Label: "", PromptTemplate: "drop me"},
|
|
}
|
|
got, err := svc.UpdateActionPresets(ctx, &UpdateActionPresetsRequest{
|
|
WorkspaceID: "ws-1",
|
|
PR: &input,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("UpdateActionPresets: %v", err)
|
|
}
|
|
if len(got.PR) != 1 {
|
|
t.Fatalf("empty-label preset not dropped: %+v", got.PR)
|
|
}
|
|
if got.PR[0].Label != "Has label" {
|
|
t.Errorf("label not trimmed: %q", got.PR[0].Label)
|
|
}
|
|
if got.PR[0].ID == "" {
|
|
t.Errorf("missing ID not auto-assigned")
|
|
}
|
|
}
|
|
|
|
// TestResetActionPresets_RestoresDefaults verifies that reset removes stored
|
|
// customisations — the next Get returns defaults again.
|
|
func TestResetActionPresets_RestoresDefaults(t *testing.T) {
|
|
svc, _, _ := setupSyncTest(t)
|
|
ctx := context.Background()
|
|
|
|
custom := []ActionPreset{
|
|
{ID: "custom", Label: "Custom", PromptTemplate: "x"},
|
|
}
|
|
if _, err := svc.UpdateActionPresets(ctx, &UpdateActionPresetsRequest{
|
|
WorkspaceID: "ws-1",
|
|
PR: &custom,
|
|
}); err != nil {
|
|
t.Fatalf("UpdateActionPresets: %v", err)
|
|
}
|
|
|
|
got, err := svc.ResetActionPresets(ctx, "ws-1")
|
|
if err != nil {
|
|
t.Fatalf("ResetActionPresets: %v", err)
|
|
}
|
|
if len(got.PR) != len(DefaultPRActionPresets()) {
|
|
t.Errorf("reset did not restore PR defaults: got %d", len(got.PR))
|
|
}
|
|
}
|
|
|
|
// TestUpdateActionPresets_RequiresWorkspaceID guards the controller-layer
|
|
// invariant: the service refuses an empty workspace rather than writing
|
|
// rows keyed on "".
|
|
func TestUpdateActionPresets_RequiresWorkspaceID(t *testing.T) {
|
|
svc, _, _ := setupSyncTest(t)
|
|
ctx := context.Background()
|
|
|
|
empty := []ActionPreset{{ID: "x", Label: "x"}}
|
|
if _, err := svc.UpdateActionPresets(ctx, &UpdateActionPresetsRequest{PR: &empty}); err == nil {
|
|
t.Fatal("expected error on empty workspace_id, got nil")
|
|
}
|
|
}
|
|
|
|
// TestGetActionPresets_FallsBackPerKind verifies the half-populated case: if
|
|
// PR was customised but Issue wasn't, Issue falls back to defaults.
|
|
func TestGetActionPresets_FallsBackPerKind(t *testing.T) {
|
|
svc, _, _ := setupSyncTest(t)
|
|
ctx := context.Background()
|
|
|
|
custom := []ActionPreset{
|
|
{ID: "only-pr", Label: "Only PR", PromptTemplate: "pr"},
|
|
}
|
|
if _, err := svc.UpdateActionPresets(ctx, &UpdateActionPresetsRequest{
|
|
WorkspaceID: "ws-1",
|
|
PR: &custom,
|
|
}); err != nil {
|
|
t.Fatalf("UpdateActionPresets: %v", err)
|
|
}
|
|
|
|
got, err := svc.GetActionPresets(ctx, "ws-1")
|
|
if err != nil {
|
|
t.Fatalf("GetActionPresets: %v", err)
|
|
}
|
|
if len(got.PR) != 1 {
|
|
t.Errorf("PR not the customised list: %+v", got.PR)
|
|
}
|
|
if len(got.Issue) == 0 {
|
|
t.Errorf("Issue list empty — expected fallback to defaults")
|
|
}
|
|
}
|