fix: align contextual action hints
This commit is contained in:
@@ -64,6 +64,16 @@ func TestCompleteModelContextualHelpOpensOverEveryPageAndRestoresPageState(t *te
|
||||
t.Fatalf("closing Help did not restore form state: page=%d field=%d Profile=%q", model.page, model.form.field, model.form.profile.Name)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Active Profile picker search and selection", func(t *testing.T) {
|
||||
model := openProfilePicker(t, resizedModel(t, module, themeModeNoColor, 80, 24))
|
||||
model = updateKey(model, "down")
|
||||
model = updateKey(model, "r")
|
||||
model = updateKey(model, "?")
|
||||
assertUsableFrame(t, model.View(), 80, 24, "Contextual Help", "Active Profile picker", "q is ordinary search input", "Ctrl+P/Ctrl+N")
|
||||
model = updateKey(model, "esc")
|
||||
assertUsableFrame(t, model.View(), 80, 24, "Switch Active Profile", "Search: r", "> remote")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCompleteModelLongHelpScrollsAndSurvivesResize(t *testing.T) {
|
||||
@@ -183,16 +193,32 @@ func TestCompleteModelGlobalBackQuitAndInterruptSemantics(t *testing.T) {
|
||||
func(model Model) Model { return updateKey(model, "m") },
|
||||
func(model Model) Model { return updateKey(updateKey(model, "m"), "e") },
|
||||
func(model Model) Model { return updateKey(model, "?") },
|
||||
func(model Model) Model { return openProfilePicker(t, model) },
|
||||
func(model Model) Model { return updateKey(updateKey(model, "m"), "d") },
|
||||
} {
|
||||
candidate := setup(resizedModel(t, module, themeModeNoColor, 80, 24))
|
||||
_, command := candidate.Update(tea.KeyMsg{Type: tea.KeyCtrlC})
|
||||
if command == nil {
|
||||
t.Fatalf("Ctrl+C from page %d did not request exit", candidate.page)
|
||||
t.Fatalf("Ctrl+C from page or dialog did not request exit:\n%s", candidate.View())
|
||||
}
|
||||
if _, ok := command().(tea.QuitMsg); !ok {
|
||||
t.Fatalf("Ctrl+C from page %d did not emit tea.Quit", candidate.page)
|
||||
t.Fatalf("Ctrl+C from page or dialog did not emit tea.Quit:\n%s", candidate.View())
|
||||
}
|
||||
}
|
||||
|
||||
driftSnapshot := operationSnapshot()
|
||||
driftSnapshot.Targets[0].PrimaryAction = app.TargetAction{Kind: app.ResolveDriftTargetAction, Available: true}
|
||||
driftSnapshot.Targets[0].DriftResolutionActions = []app.TargetAction{{Kind: app.ReapplyActiveProfileAction, Available: true}}
|
||||
driftModule := &recordingModule{snapshot: driftSnapshot}
|
||||
drift := resizedModel(t, driftModule, themeModeNoColor, 80, 24)
|
||||
drift = updateKey(drift, " ")
|
||||
_, command := drift.Update(tea.KeyMsg{Type: tea.KeyCtrlC})
|
||||
if command == nil {
|
||||
t.Fatal("Ctrl+C from Drift Resolution did not request exit")
|
||||
}
|
||||
if _, ok := command().(tea.QuitMsg); !ok {
|
||||
t.Fatal("Ctrl+C from Drift Resolution did not emit tea.Quit")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompleteModelHelpDuringMutationExplainsAndPreservesDisabledActions(t *testing.T) {
|
||||
@@ -206,7 +232,10 @@ func TestCompleteModelHelpDuringMutationExplainsAndPreservesDisabledActions(t *t
|
||||
model = updateKey(model, "pgdown")
|
||||
}
|
||||
view := model.View()
|
||||
assertUsableFrame(t, view, 60, 16, "Mutation in progress", "Conflicting mutations are disabled")
|
||||
assertUsableFrame(t, view, 60, 16, "Mutation in progress", "Conflicting mutations are disabled", "Ctrl+C: defer safe exit")
|
||||
if strings.Contains(view, "Ctrl+C: exit") {
|
||||
t.Fatalf("busy Help incorrectly claimed Ctrl+C exits immediately:\n%s", view)
|
||||
}
|
||||
|
||||
model = updateKey(model, "esc")
|
||||
updated, blocked := model.Update(tea.KeyMsg{Type: tea.KeySpace})
|
||||
@@ -217,3 +246,90 @@ func TestCompleteModelHelpDuringMutationExplainsAndPreservesDisabledActions(t *t
|
||||
close(updates)
|
||||
_ = command
|
||||
}
|
||||
|
||||
func TestCompleteModelFooterHintsMatchContextAndAcceptedKeys(t *testing.T) {
|
||||
module := &recordingModule{snapshot: app.Snapshot{
|
||||
ActiveProfile: "office",
|
||||
Profiles: []profile.Profile{{Name: "office", Mode: profile.SingleMode, SingleURL: "http://proxy.example:8080"}},
|
||||
Targets: operationSnapshot().Targets,
|
||||
}}
|
||||
|
||||
t.Run("dashboard and busy details", func(t *testing.T) {
|
||||
model := resizedModel(t, module, themeModeNoColor, 120, 36)
|
||||
assertUsableFrame(t, model.View(), 120, 36, "Space: Enable", "u: show unavailable", "r: refresh", "q/Ctrl+C: quit")
|
||||
model = updateKey(model, "tab")
|
||||
assertUsableFrame(t, model.View(), 120, 36, "PgUp/PgDn: scroll", "u: show unavailable", "q/Ctrl+C: quit")
|
||||
|
||||
updates := make(chan app.OperationUpdate)
|
||||
busyModule := &interactiveScriptModule{snapshot: operationSnapshot(), streams: []chan app.OperationUpdate{updates}}
|
||||
model = resizedModel(t, busyModule, themeModeNoColor, 120, 36)
|
||||
model = updateKey(model, "tab")
|
||||
updated, _ := model.Update(tea.KeyMsg{Type: tea.KeySpace})
|
||||
model = updated.(Model)
|
||||
assertUsableFrame(t, model.View(), 120, 36, "u: show unavailable", "q/Ctrl+C: defer safe exit", "mutations disabled")
|
||||
model = updateKey(model, "u")
|
||||
assertUsableFrame(t, model.View(), 120, 36, "u: hide unavailable")
|
||||
close(updates)
|
||||
})
|
||||
|
||||
t.Run("picker, management, and confirmation", func(t *testing.T) {
|
||||
model := openProfilePicker(t, resizedModel(t, module, themeModeNoColor, 80, 24))
|
||||
assertUsableFrame(t, model.View(), 80, 24, "Type to search", "PgUp/PgDn: select", "Enter: activate", "Esc: cancel", "?: help")
|
||||
model = updateKey(model, "esc")
|
||||
model = updateKey(model, "m")
|
||||
assertUsableFrame(t, model.View(), 80, 24, "n: create", "e: edit", "d: delete", "q/Esc: back")
|
||||
model = updateKey(model, "d")
|
||||
assertUsableFrame(t, model.View(), 80, 24, "←/→/Tab: choose", "Enter: choose", "y: delete", "n/q/Esc: cancel", "?: help")
|
||||
model = updateKey(model, "q")
|
||||
if model.profileDelete != nil || model.page != profilesPage {
|
||||
t.Fatal("confirmation footer advertised q cancellation but q did not close only the dialog")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("form probe hint follows mode, focus, and progress", func(t *testing.T) {
|
||||
model := resizedModel(t, module, themeModeNoColor, 80, 24)
|
||||
model = updateKey(model, "m")
|
||||
model = updateKey(model, "e")
|
||||
view := model.View()
|
||||
assertUsableFrame(t, view, 80, 24, "Shift+Tab", "Ctrl+T: mode", "Ctrl+S: save", "q/Esc: cancel")
|
||||
if strings.Contains(view, "Ctrl+D: probe") {
|
||||
t.Fatalf("form advertised a field action while Name was focused:\n%s", view)
|
||||
}
|
||||
|
||||
model = updateKey(model, "tab")
|
||||
assertUsableFrame(t, model.View(), 80, 24, "Ctrl+D: probe")
|
||||
updated, probeCommand := model.Update(tea.KeyMsg{Type: tea.KeyCtrlD})
|
||||
model = updated.(Model)
|
||||
if probeCommand == nil || !strings.Contains(model.View(), "Protocol Probe in progress") || strings.Contains(model.View(), "Ctrl+D: probe") {
|
||||
t.Fatalf("probe footer did not follow accepted in-field action and progress:\n%s", model.View())
|
||||
}
|
||||
model = updateKey(model, "?")
|
||||
assertUsableFrame(t, model.View(), 80, 24, "Protocol Probe is in progress", "Ctrl+D does not start a duplicate probe")
|
||||
model = updateKey(model, "esc")
|
||||
|
||||
model = updateKey(model, "ctrl+t")
|
||||
if strings.Contains(model.View(), "Ctrl+D: probe") || !strings.Contains(model.View(), "advanced mode") {
|
||||
t.Fatalf("Advanced form advertised a Single-field Protocol Probe:\n%s", model.View())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Help exit hint follows operation state", func(t *testing.T) {
|
||||
idle := resizedModel(t, module, themeModeNoColor, 80, 24)
|
||||
idle = updateKey(idle, "?")
|
||||
assertUsableFrame(t, idle.View(), 80, 24, "Ctrl+C: exit")
|
||||
|
||||
updates := make(chan app.OperationUpdate)
|
||||
busyModule := &interactiveScriptModule{snapshot: operationSnapshot(), streams: []chan app.OperationUpdate{updates}}
|
||||
busy := resizedModel(t, busyModule, themeModeNoColor, 80, 24)
|
||||
updated, _ := busy.Update(tea.KeyMsg{Type: tea.KeySpace})
|
||||
busy = updateKey(updated.(Model), "?")
|
||||
assertUsableFrame(t, busy.View(), 80, 24, "Ctrl+C: defer safe exit")
|
||||
updated, quit := busy.Update(tea.KeyMsg{Type: tea.KeyCtrlC})
|
||||
busy = updated.(Model)
|
||||
if quit != nil {
|
||||
t.Fatal("busy Help Ctrl+C hint promised deferral but returned immediate quit")
|
||||
}
|
||||
assertUsableFrame(t, busy.View(), 80, 24, "Exit pending until safe completion")
|
||||
close(updates)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user