Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions internal/tui/screens/wizard.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
67 changes: 67 additions & 0 deletions internal/tui/screens/wizard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions openspec/changes/tui-personality/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading