diff --git a/internal/tui/screens/wizard.go b/internal/tui/screens/wizard.go index 2b69c60..69f708b 100644 --- a/internal/tui/screens/wizard.go +++ b/internal/tui/screens/wizard.go @@ -146,6 +146,16 @@ func (m *WizardModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { default: return m.handleNavigation(msg) } + + case tea.PasteMsg: + // Bracketed paste (tui-personality wizard-flow F8): append the pasted + // content to the active free-text input buffer. Only the profile-name + // step accepts free text; other steps ignore paste so selection state + // is not corrupted. + if m.Step == StepName { + m.NameInput += msg.Content + } + return m, nil } return m, nil } diff --git a/internal/tui/screens/wizard_test.go b/internal/tui/screens/wizard_test.go index e6d55bc..48de619 100644 --- a/internal/tui/screens/wizard_test.go +++ b/internal/tui/screens/wizard_test.go @@ -149,6 +149,73 @@ func TestWizardModel_NameStep_NamePersistsAcrossSteps(t *testing.T) { //nolint:p } } +// --- Paste support (tui-personality wizard-flow F8) --- + +// TestWizardModel_NameStep_PasteMsg verifies that a bracketed-paste event +// appends msg.Content to the active profile-name input buffer. Covers the +// spec scenarios "paste inserts pasted text" and "paste appends to existing +// text", plus edge cases with spaces and path separators. +func TestWizardModel_NameStep_PasteMsg(t *testing.T) { //nolint:paralleltest // not yet parallelized — shared state (os.Stderr/execCommand/config-file/struct) isolation pending + tests := []struct { + name string + initial string + paste string + wantInput string + }{ + {"empty input + paste inserts text", "", "work-laptop", "work-laptop"}, + {"non-empty input + paste appends", "work-", "laptop", "work-laptop"}, + {"paste with spaces", "", "my profile", "my profile"}, + {"paste with path separators", "", "/home/user/config", "/home/user/config"}, + {"paste appends to multi-segment", "foo-", "bar-baz", "foo-bar-baz"}, + } + + for _, tt := range tests { //nolint:paralleltest // subtests share table/struct state + t.Run(tt.name, func(t *testing.T) { //nolint:paralleltest // subtests share table/struct state + m := NewWizardModel("profile-create", []string{"github-gist"}) + m.NameInput = tt.initial + + _, _ = m.Update(tea.PasteMsg{Content: tt.paste}) + + if m.NameInput != tt.wantInput { + t.Errorf("NameInput after paste = %q, want %q", m.NameInput, tt.wantInput) + } + }) + } +} + +// TestWizardModel_NameStep_PasteThenBackspace verifies the spec scenario +// "regular keys still work after paste": after a paste populates the buffer, +// a Backspace keystroke removes the trailing rune as usual. +func TestWizardModel_NameStep_PasteThenBackspace(t *testing.T) { //nolint:paralleltest // not yet parallelized — shared state (os.Stderr/execCommand/config-file/struct) isolation pending + m := NewWizardModel("profile-create", []string{"github-gist"}) + + _, _ = m.Update(tea.PasteMsg{Content: "work-laptop"}) + _, _ = m.Update(tea.KeyPressMsg{Code: tea.KeyBackspace}) + + if m.NameInput != "work-lapto" { + t.Errorf("NameInput after paste+backspace = %q, want %q", m.NameInput, "work-lapto") + } +} + +// TestWizardModel_PasteMsg_NonNameStepNoOp verifies paste only affects the +// active free-text step (StepName). On other steps the paste is ignored and +// must not corrupt the name buffer or selection state. +func TestWizardModel_PasteMsg_NonNameStepNoOp(t *testing.T) { //nolint:paralleltest // not yet parallelized — shared state (os.Stderr/execCommand/config-file/struct) isolation pending + m := NewWizardModel("profile-create", []string{"github-gist"}) + + // Advance past the name step to the provider step. + _, _ = m.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) + if m.CurrentStep() != StepProvider { + t.Fatalf("setup: step = %d, want StepProvider", m.CurrentStep()) + } + + _, _ = m.Update(tea.PasteMsg{Content: "work-laptop"}) + + if m.NameInput != "" { + t.Errorf("NameInput should remain empty on non-name step, got %q", m.NameInput) + } +} + func TestWizardModel_ProfileName(t *testing.T) { //nolint:paralleltest // not yet parallelized — shared state (os.Stderr/execCommand/config-file/struct) isolation pending tests := []struct { name string diff --git a/openspec/changes/tui-personality/tasks.md b/openspec/changes/tui-personality/tasks.md index b56c116..650a122 100644 --- a/openspec/changes/tui-personality/tasks.md +++ b/openspec/changes/tui-personality/tasks.md @@ -60,11 +60,11 @@ Chain strategy: stacked-to-main ## Phase 5: Paste Support (PR 3 — Tier 3) -- [ ] 5.1 [RED] `internal/tui/screens/wizard_test.go`: send `tea.PasteMsg{Content: "work-laptop"}`, assert input buffer equals `"work-laptop"`; send paste to pre-filled input, assert append -- [ ] 5.2 [GREEN] `internal/tui/screens/wizard.go`: add `case tea.PasteMsg:` in active textinput Update paths; append `msg.Content` to input buffer. **Note: field is `Content` not `Text`** (v2 API) +- [x] 5.1 [RED] `internal/tui/screens/wizard_test.go`: send `tea.PasteMsg{Content: "work-laptop"}`, assert input buffer equals `"work-laptop"`; send paste to pre-filled input, assert append +- [x] 5.2 [GREEN] `internal/tui/screens/wizard.go`: add `case tea.PasteMsg:` in active textinput Update paths; append `msg.Content` to input buffer. **Note: field is `Content` not `Text`** (v2 API) ## Phase 6: Verification & Coverage -- [ ] 6.1 Run `go test ./internal/tui/...` — all tests green -- [ ] 6.2 Run `go test -cover ./internal/tui/...` — verify ≥80% per package -- [ ] 6.3 Run `go vet ./...` and `golangci-lint run` — clean +- [x] 6.1 Run `go test ./internal/tui/...` — all tests green +- [x] 6.2 Run `go test -cover ./internal/tui/...` — verify ≥80% per package +- [x] 6.3 Run `go vet ./...` and `golangci-lint run` — clean