Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

176 lines
6.4 KiB
Go

package main
import (
"testing"
tea "github.com/charmbracelet/bubbletea"
"github.com/stretchr/testify/assert"
"github.com/zzet/gortex/internal/agents"
)
// fakeAdapter is a minimal agents.Adapter used to seed the wizard's checklist
// without depending on every real adapter's Detect side-effects. detected
// controls what Detect returns.
type fakeAdapter struct {
name string
detected bool
}
func (a *fakeAdapter) Name() string { return a.name }
func (a *fakeAdapter) DocsURL() string { return "" }
func (a *fakeAdapter) Detect(_ agents.Env) (bool, error) { return a.detected, nil }
func (a *fakeAdapter) Plan(_ agents.Env) (*agents.Plan, error) { return &agents.Plan{}, nil }
func (a *fakeAdapter) Apply(_ agents.Env, _ agents.ApplyOpts) (*agents.Result, error) {
return &agents.Result{Name: a.name, Detected: a.detected, Configured: a.detected}, nil
}
func sampleRegistered() []agents.Adapter {
return []agents.Adapter{
&fakeAdapter{name: "claude-code", detected: true},
&fakeAdapter{name: "cursor", detected: true},
&fakeAdapter{name: "aider", detected: false},
&fakeAdapter{name: "codex", detected: false},
}
}
func sampleDetected() map[string]bool {
return map[string]bool{
"claude-code": true,
"cursor": true,
"aider": false,
"codex": false,
}
}
func defaultDefaults() initDefaults {
return initDefaults{hooks: true, hookMode: "deny", analyze: false, skills: true}
}
// pressKey simulates one key press and returns the model after Update.
func pressKey(m *initWizardModel, key string) *initWizardModel {
updated, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(key)})
out, _ := updated.(*initWizardModel)
return out
}
func pressSpecial(m *initWizardModel, key tea.KeyType) *initWizardModel {
updated, _ := m.Update(tea.KeyMsg{Type: key})
out, _ := updated.(*initWizardModel)
return out
}
func TestInitWizard_DetectedAdaptersStartChecked(t *testing.T) {
m := newInitWizardModel(".", sampleRegistered(), sampleDetected(), defaultDefaults())
picked := m.collectPickedLabels()
assert.Contains(t, picked, "Claude Code", "Claude Code should always be pre-checked")
assert.Contains(t, picked, "Cursor", "detected adapters should be pre-checked")
assert.NotContains(t, picked, "Aider", "undetected adapters should be unchecked")
assert.NotContains(t, picked, "Codex CLI", "undetected adapters should be unchecked")
}
func TestInitWizard_ClaudeCodeAlwaysCheckedEvenIfUndetected(t *testing.T) {
registered := []agents.Adapter{
&fakeAdapter{name: "claude-code", detected: false},
&fakeAdapter{name: "cursor", detected: false},
}
detected := map[string]bool{"claude-code": false, "cursor": false}
m := newInitWizardModel(".", registered, detected, defaultDefaults())
picked := m.collectPickedLabels()
assert.Contains(t, picked, "Claude Code", "Claude Code must default-on even when Detect returns false — it owns the load-bearing hook surface")
assert.NotContains(t, picked, "Cursor")
}
func TestInitWizard_QuitOnEsc(t *testing.T) {
m := newInitWizardModel(".", sampleRegistered(), sampleDetected(), defaultDefaults())
m = pressSpecial(m, tea.KeyEsc)
assert.True(t, m.cancelled, "Esc must cancel")
assert.Equal(t, stepCancelled, m.step)
}
func TestInitWizard_AdvancesThroughStepsAndCollectsChoices(t *testing.T) {
m := newInitWizardModel(".", sampleRegistered(), sampleDetected(), defaultDefaults())
// Step 1: agents — toggle codex on, then advance.
m = pressKey(m, "j") // cursor → cursor
m = pressKey(m, "j") // → aider
m = pressKey(m, "j") // → codex
m = pressKey(m, " ") // pick codex
m = pressSpecial(m, tea.KeyEnter)
assert.Equal(t, stepOptions, m.step)
// Step 2: options — flip "Codebase analysis" on.
m = pressKey(m, "j") // cursor on row 1 → row 2 (analyze)
m = pressKey(m, " ") // toggle analyze
m = pressKey(m, "N") // shift-N: advance
assert.Equal(t, stepConfirm, m.step)
// Step 3: confirm.
m = pressSpecial(m, tea.KeyEnter)
assert.True(t, m.confirmed)
assert.Contains(t, m.pickedAgents, "claude-code")
assert.Contains(t, m.pickedAgents, "cursor")
assert.Contains(t, m.pickedAgents, "codex")
assert.True(t, m.analyze, "analyze must be set by the toggle")
assert.True(t, m.hooks, "hooks default carried through")
assert.Equal(t, "deny", m.hookMode)
}
func TestInitWizard_PickNoneFallsBackToClaudeCode(t *testing.T) {
m := newInitWizardModel(".", sampleRegistered(), sampleDetected(), defaultDefaults())
m = pressKey(m, "n") // pick none
m = pressSpecial(m, tea.KeyEnter) // advance — should auto-pick claude-code
pickedLabels := m.collectPickedLabels()
assert.Equal(t, []string{"Claude Code"}, pickedLabels, "advancing with nothing picked must snap to Claude Code")
assert.Equal(t, stepOptions, m.step)
}
func TestInitWizard_PickAllAndPickInvert(t *testing.T) {
m := newInitWizardModel(".", sampleRegistered(), sampleDetected(), defaultDefaults())
m = pressKey(m, "a")
assert.Equal(t, 4, m.checklist.PickedCount(), "a picks all")
m = pressKey(m, "i")
assert.Equal(t, 0, m.checklist.PickedCount(), "i inverts all-picked → none")
m = pressKey(m, "i")
assert.Equal(t, 4, m.checklist.PickedCount(), "i again inverts none → all")
}
func TestInitWizard_HookModeSelectCycles(t *testing.T) {
m := newInitWizardModel(".", sampleRegistered(), sampleDetected(), defaultDefaults())
// jump to confirm-ready: agents → options.
m = pressSpecial(m, tea.KeyEnter)
// step 2 has 3 toggles + 1 select; cursor must reach select row (index 3).
m = pressKey(m, "j") // → toggle 1
m = pressKey(m, "j") // → toggle 2
m = pressKey(m, "j") // → select (hook posture)
// Right arrow cycles forward.
updated, _ := m.Update(tea.KeyMsg{Type: tea.KeyRight})
m, _ = updated.(*initWizardModel)
assert.Equal(t, "enrich", m.options.Selects[0].Value(), "right cycles deny → enrich")
updated, _ = m.Update(tea.KeyMsg{Type: tea.KeyLeft})
m, _ = updated.(*initWizardModel)
assert.Equal(t, "deny", m.options.Selects[0].Value(), "left cycles back")
}
func TestInitWizard_ViewIncludesBannerAndStepLine(t *testing.T) {
m := newInitWizardModel("/tmp/demo", sampleRegistered(), sampleDetected(), defaultDefaults())
view := m.View()
assert.Contains(t, view, "gortex init", "banner title must appear")
assert.Contains(t, view, "/tmp/demo", "subtitle must echo the repo path")
assert.Contains(t, view, "Step 1", "step line must reflect current step")
assert.Contains(t, view, "Claude Code", "checklist must render adapter labels")
}