fix: simplify dashboard and disable unauthorized actions
hermetic-ci / test (push) Canceled after 0s

This commit is contained in:
proxyctl coordinator
2026-08-30 15:49:04 +08:00
parent 416425a9bf
commit 30f7206b14
20 changed files with 182 additions and 221 deletions
+2 -2
View File
@@ -180,7 +180,7 @@ func TestCompleteModelPickerConfirmedActivationContinuesThroughUnifiedOperation(
completion <- app.SucceededOperationUpdate(completed, app.OperationStatus{Summary: `Activated Profile "new"`}, nil)
close(completion)
model, _ = applyOperationCommand(t, model, command)
if view := model.View(); !strings.Contains(view, "Active Profile: new") || !strings.Contains(view, `Activated Profile "new"`) {
if view := model.View(); !strings.Contains(view, "Profile: new") || !strings.Contains(view, `Activated Profile "new"`) {
t.Fatalf("confirmed activation did not finish on the dashboard:\n%s", view)
}
}
@@ -213,7 +213,7 @@ func TestCompleteModelPickerPartialFailureKeepsProgressClosesPickerAndFocusesFai
close(updates)
model, _ = applyOperationCommand(t, model, command)
view := model.View()
if model.selectedTargetID != "docker" || !strings.Contains(view, "> ! Drifted ! Failed Docker daemon") || !strings.Contains(view, "Current error: Activated Profile new with 1 Target update failure") || !strings.Contains(view, "Active Profile: new") {
if model.selectedTargetID != "docker" || !strings.Contains(view, "> ! Drifted ! Failed Docker daemon") || !strings.Contains(view, "Current error: Activated Profile new with 1 Target update failure") || !strings.Contains(view, "Profile: new") {
t.Fatalf("partial failure was not retained and focused: selected=%q\n%s", model.selectedTargetID, view)
}
}
+3 -6
View File
@@ -35,12 +35,9 @@ func TestCompleteModelRendersAndDispatchesContextualPrimaryActions(t *testing.T)
module := &targetModule{snapshot: initial, result: app.Result{Snapshot: initial, Notice: "done"}}
model := resizedModel(t, module, themeModeNoColor, 120, 36)
view := model.View()
if !strings.Contains(view, "Action: "+test.label) || !strings.Contains(view, "Space: "+test.label) {
if !strings.Contains(view, "Space: "+test.label) {
t.Fatalf("contextual action not rendered:\n%s", view)
}
if test.state == target.External && (!strings.Contains(view, "proxyctl will begin managing this configuration") || !strings.Contains(view, "while preserving restorable state")) {
t.Fatalf("Apply Profile ownership explanation missing:\n%s", view)
}
updated, command := model.Update(tea.KeyMsg{Type: tea.KeySpace})
model = updated.(Model)
@@ -91,7 +88,7 @@ func TestCompleteModelRendersUnknownTargetStateAsStructurallyUnavailable(t *test
model := resizedModel(t, module, themeModeNoColor, 120, 36)
view := model.View()
if !strings.Contains(view, "Action: Unavailable") || !strings.Contains(view, "Action unavailable:") || !strings.Contains(view, "state is unknown") || !strings.Contains(view, "Space: Unavailable disabled") {
if !strings.Contains(view, "Unavailable:") || !strings.Contains(view, "Target actions are unavailable") || !strings.Contains(view, "unknown") || !strings.Contains(view, "Space: Unavailable unavailable") {
t.Fatalf("unknown Target action was not structurally unavailable:\n%s", view)
}
updated, command := model.Update(tea.KeyMsg{Type: tea.KeySpace})
@@ -108,7 +105,7 @@ func TestCompleteModelUsesStructuredDisabledReasonWithoutInferringFromState(t *t
module := &targetModule{snapshot: initial}
model := resizedModel(t, module, themeModeNoColor, 120, 36)
view := model.View()
if !strings.Contains(view, "No Active Profile is selected") || !strings.Contains(view, "Space: Enable disabled") {
if !strings.Contains(view, "No Active Profile is selected") || !strings.Contains(view, "Space: Enable unavailable") {
t.Fatalf("structured disabled reason missing:\n%s", view)
}
updated, command := model.Update(tea.KeyMsg{Type: tea.KeySpace})
+1 -41
View File
@@ -1,9 +1,6 @@
package tui
import (
"proxyctl/internal/app"
"proxyctl/internal/target"
)
import "proxyctl/internal/app"
func targetActionLabel(kind app.TargetActionKind) string {
switch kind {
@@ -101,40 +98,3 @@ func (c targetCatalog) retain(id string, formerIndex int) string {
}
return c.items[max(0, min(len(c.items)-1, formerIndex))].ID
}
type targetStateCounts struct {
enabled int
disabled int
external int
drifted int
checking int
failed int
}
func countAvailableTargetStates(snapshot app.Snapshot) targetStateCounts {
var counts targetStateCounts
for _, item := range snapshot.Targets {
switch item.DetectionPhase {
case app.TargetChecking:
counts.checking++
continue
case app.TargetDetectionFailed:
counts.failed++
continue
}
if !item.Available {
continue
}
switch item.State {
case target.Enabled:
counts.enabled++
case target.Disabled:
counts.disabled++
case target.External:
counts.external++
case target.Drifted:
counts.drifted++
}
}
return counts
}
+10 -15
View File
@@ -23,9 +23,8 @@ func TestCompleteModelWideOperationsConsoleShowsStableAvailableTargetDashboard(t
view := resizedModel(t, module, themeModeNoColor, 120, 36).View()
assertUsableFrame(t, view, 120, 36,
"proxyctl", "Active Profile: office",
"● Enabled 1", "○ Disabled 1", "◇ External 1", "! Drifted 1",
"> ● Enabled", "Shell", "State: ● Enabled", "Evidence: matches Active Profile",
"proxyctl", "Profile: office",
"> ● Enabled", "Shell", "matches Active Profile",
)
if strings.Contains(view, "APT") || strings.Contains(view, "apt-config was not detected") {
t.Fatalf("default dashboard included an unavailable Target:\n%s", view)
@@ -71,12 +70,8 @@ func TestCompleteModelDashboardRevealsUnavailableEvidenceAndStructuredDetails(t
view := model.View()
assertUsableFrame(t, view, 120, 36,
"> ○ Unavailable APT", "State: ○ Unavailable", "Evidence: apt-config was not detected",
"Affects: package operations across privilege boundaries",
"Permissions: pre-existing root write authority",
"Ownership & restoration: isolated overlay; disabling reveals the", "previous configuration",
"Restart risk: none", "Persistent package proxy.", "Longer APT explanation.",
"u: hide unavailable",
"> ○ Unavailable APT", "○ Unavailable", "apt-config was not detected",
"Persistent package proxy.", "u: Hide unavailable",
)
if !(strings.Index(view, "Shell") < strings.Index(view, "APT") && strings.Index(view, "APT") < strings.Index(view, "Docker daemon")) {
t.Fatalf("revealing unavailable Targets changed product order:\n%s", view)
@@ -95,7 +90,7 @@ func TestCompleteModelNarrowDashboardFocusAndIndependentScrollingRetainTargetIde
}
}
targets[22].Description.Summary = "Selected identity summary"
targets[22].Description.Details = strings.Repeat("Scrollable detail line. ", 45) + "END OF TARGET DETAILS"
targets[22].Evidence = strings.Repeat("Scrollable evidence line. ", 45) + "END OF TARGET EVIDENCE"
module := &recordingModule{snapshot: app.Snapshot{ActiveProfile: "office", Targets: targets}}
model := resizedModel(t, module, themeModeNoColor, 80, 24)
for range 22 {
@@ -103,7 +98,7 @@ func TestCompleteModelNarrowDashboardFocusAndIndependentScrollingRetainTargetIde
}
view := model.View()
assertUsableFrame(t, view, 80, 24, "> ○ Disabled Product Target 22", "Selected identity summary", "▶ Targets [focused]")
assertUsableFrame(t, view, 80, 24, "> ○ Disabled Product Target 22", "▶ Targets [focused]")
model = updateKey(model, "tab")
for range 70 {
@@ -111,16 +106,16 @@ func TestCompleteModelNarrowDashboardFocusAndIndependentScrollingRetainTargetIde
}
view = model.View()
assertUsableFrame(t, view, 80, 24,
"> ○ Disabled Product Target 22", "▶ Target details [focused]", "END OF", "TARGET DETAILS",
"↑/k ↓/j PgUp/PgDn: scroll", "Tab: focus Targets",
"> ○ Disabled Product Target 22", "▶ Target details [focused]", "END OF", "TARGET EVIDENCE",
"↑/↓: Scroll", "Tab: Targets",
)
model = resize(model, 120, 36)
view = model.View()
assertUsableFrame(t, view, 120, 36, "> ○ Disabled Product Target 22", "END OF TARGET DETAILS", "▶ Target details [focused]")
assertUsableFrame(t, view, 120, 36, "> ○ Disabled Product Target 22", "END OF TARGET", "EVIDENCE", "▶ Target details [focused]")
if !sameLine(view, "Targets", "Target details") {
t.Fatalf("resized dashboard did not become side by side:\n%s", view)
}
model = resize(model, 80, 24)
assertUsableFrame(t, model.View(), 80, 24, "> ○ Disabled Product Target 22", "END OF", "TARGET DETAILS", "▶ Target details [focused]")
assertUsableFrame(t, model.View(), 80, 24, "> ○ Disabled Product Target 22", "END OF", "TARGET EVIDENCE", "▶ Target details [focused]")
}
+7 -45
View File
@@ -8,7 +8,6 @@ import (
"github.com/charmbracelet/lipgloss"
"proxyctl/internal/app"
"proxyctl/internal/target"
)
type dashboardRenderState struct {
@@ -149,55 +148,18 @@ func (p terminalPresentation) targetDetails(item app.TargetSnapshot, progress ap
var details strings.Builder
details.WriteString(p.theme.section.Render(item.Description.Name))
fmt.Fprintf(&details, "\nState: %s\nEvidence: %s", state, evidence)
fmt.Fprintf(&details, "\nAction: %s", targetActionLabel(item.PrimaryAction.Kind))
if !item.PrimaryAction.Available && item.PrimaryAction.DisabledReason != "" {
fmt.Fprintf(&details, "\nAction unavailable: %s", item.PrimaryAction.DisabledReason)
}
switch item.PrimaryAction.Kind {
case app.ApplyProfileTargetAction:
details.WriteString("\nApply Profile means proxyctl will begin managing this configuration while preserving restorable state.")
case app.ResolveDriftTargetAction:
details.WriteString("\nResolve opens Drift Resolution without choosing a recovery direction for you.")
fmt.Fprintf(&details, "\n%s\n%s", state, evidence)
action := targetActionLabel(item.PrimaryAction.Kind)
if item.PrimaryAction.Available {
fmt.Fprintf(&details, "\n\nSpace: %s", action)
} else if item.PrimaryAction.DisabledReason != "" {
fmt.Fprintf(&details, "\n\nUnavailable: %s", item.PrimaryAction.DisabledReason)
}
if item.DetectionPhase == app.TargetDetectionFailed && item.DetectionErr != nil {
fmt.Fprintf(&details, "\nCheck error: %s", item.DetectionErr)
}
facts := []struct {
label string
value string
}{
{"Affects", item.Description.Scope},
{"Permissions", item.Description.Permissions},
{"Ownership & restoration", item.Description.Ownership},
{"Restart risk", item.Description.RestartRisk},
}
for _, fact := range facts {
if fact.value != "" {
fmt.Fprintf(&details, "\n%s: %s", fact.label, fact.value)
}
fmt.Fprintf(&details, "\nCheck failed: %s", item.DetectionErr)
}
if item.Description.Summary != "" {
fmt.Fprintf(&details, "\n\n%s", item.Description.Summary)
}
if item.Description.Details != "" {
fmt.Fprintf(&details, "\n\n%s", item.Description.Details)
}
return protectRenderedText(details.String())
}
func (p terminalPresentation) stateCountSummary(snapshot app.Snapshot) string {
counts := countAvailableTargetStates(snapshot)
summary := fmt.Sprintf("%s %d %s %d %s %d %s %d",
p.theme.renderTargetState(target.Enabled, false), counts.enabled,
p.theme.renderTargetState(target.Disabled, false), counts.disabled,
p.theme.renderTargetState(target.External, false), counts.external,
p.theme.renderTargetState(target.Drifted, false), counts.drifted)
if counts.checking > 0 {
summary += fmt.Sprintf(" %s %d", p.theme.checking.Render("… Checking"), counts.checking)
}
if counts.failed > 0 {
summary += fmt.Sprintf(" %s %d", p.theme.checkFailed.Render("! Check failed"), counts.failed)
}
return summary
}
+10 -10
View File
@@ -24,7 +24,7 @@ func TestCompleteModelRendersResponsiveTerminalSizeClasses(t *testing.T) {
if !sameLine(view, "Targets", "Target details") {
t.Fatalf("wide layout did not place list and details side by side:\n%s", view)
}
assertUsableFrame(t, view, 120, 36, "Shell", "Shell summary", "Active Profile: office")
assertUsableFrame(t, view, 120, 36, "Shell", "Shell summary", "Profile: office")
})
t.Run("narrow 80x24", func(t *testing.T) {
@@ -33,7 +33,7 @@ func TestCompleteModelRendersResponsiveTerminalSizeClasses(t *testing.T) {
if sameLine(view, "Targets", "Target details") || strings.Index(view, "Target details") < strings.Index(view, "Shell") {
t.Fatalf("narrow layout did not stack list above details:\n%s", view)
}
assertUsableFrame(t, view, 80, 24, "Shell", "Shell summary", "Active Profile: office")
assertUsableFrame(t, view, 80, 24, "Shell", "Shell summary", "Profile: office")
})
t.Run("below minimum 59x15", func(t *testing.T) {
@@ -82,11 +82,11 @@ func TestCompleteModelResizePreservesValidSelectionAndFormFocus(t *testing.T) {
model := resizedModel(t, module, themeModeNoColor, 120, 36)
model = updateKey(model, "down")
model = resize(model, 80, 24)
if view := model.View(); !strings.Contains(view, "> ◇ External APT") || !strings.Contains(view, "Action: Apply Profile") {
if view := model.View(); !strings.Contains(view, "> ◇ External APT") || !strings.Contains(view, "Space: Apply Profile") {
t.Fatalf("Target selection did not survive wide-to-narrow resize:\n%s", view)
}
model = resize(model, 120, 36)
if view := model.View(); !strings.Contains(view, "> ◇ External APT") || !strings.Contains(view, "Action: Apply Profile") {
if view := model.View(); !strings.Contains(view, "> ◇ External APT") || !strings.Contains(view, "Space: Apply Profile") {
t.Fatalf("Target selection did not survive narrow-to-wide resize:\n%s", view)
}
@@ -110,7 +110,7 @@ func TestCompleteModelSemanticThemeSupportsDarkLightAndNoColor(t *testing.T) {
plain := resizedModel(t, module, themeModeNoColor, 80, 24).View()
for name, view := range map[string]string{"dark": dark, "light": light, "NO_COLOR": plain} {
for _, semantic := range []string{"Active Profile: office", "Disabled", "External", ">"} {
for _, semantic := range []string{"Profile: office", "Disabled", "External", ">"} {
if !strings.Contains(view, semantic) {
t.Fatalf("%s theme lost non-color semantic %q:\n%s", name, semantic, view)
}
@@ -134,7 +134,7 @@ func TestNewModelUsesInjectedBubbleTeaOutputForTerminalCapabilities(t *testing.T
if ansiPattern.MatchString(view) {
t.Fatalf("non-terminal Bubble Tea output received ANSI styling: %q", view)
}
for _, semantic := range []string{"Active Profile: office", "Disabled", "External", ">"} {
for _, semantic := range []string{"Profile: office", "Disabled", "External", ">"} {
if !strings.Contains(view, semantic) {
t.Fatalf("injected-output rendering lost semantic %q:\n%s", semantic, view)
}
@@ -231,19 +231,19 @@ func TestCompleteModelNarrowShellKeepsLastProductionTargetStatusAndActionsVisibl
assertUsableFrame(t, model.View(), 80, 24,
"> ! Drifted Docker daemon",
"State: ! Drifted",
"! Drifted",
"Docker daemon selected evidence.",
"Status: Refresh completed; selected Target remains actionable.",
"Space: Resolve",
"p: switch Active Profile",
"m: manage Proxy Profiles",
"p: Profile",
"m: Profiles",
)
model = resize(model, 120, 36)
model = resize(model, 80, 24)
assertUsableFrame(t, model.View(), 80, 24,
"> ! Drifted Docker daemon",
"State: ! Drifted",
"! Drifted",
"Docker daemon selected evidence.",
"Space: Resolve",
)
+8 -5
View File
@@ -30,7 +30,7 @@ func TestCompleteModelContextualHelpOpensOverEveryPageAndRestoresPageState(t *te
model = updateKey(model, "down")
model = updateKey(model, "?")
view := model.View()
assertUsableFrame(t, view, 120, 36, "Operations Console", "Contextual Help", "Dashboard", "Target details focus", "APT", "Disable")
assertUsableFrame(t, view, 120, 36, "Profile: office", "Contextual Help", "Dashboard", "Target details focus", "APT", "Disable")
model = updateKey(model, " ")
if len(module.intents) != 0 {
@@ -259,9 +259,9 @@ func TestCompleteModelFooterHintsMatchContextAndAcceptedKeys(t *testing.T) {
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")
assertUsableFrame(t, model.View(), 120, 36, "Space: Enable", "u: Show hidden", "r: Refresh", "q: Quit")
model = updateKey(model, "tab")
assertUsableFrame(t, model.View(), 120, 36, "PgUp/PgDn: scroll", "u: show unavailable", "q/Ctrl+C: quit")
assertUsableFrame(t, model.View(), 120, 36, "↑/↓: Scroll", "u: Show hidden", "q: Quit")
updates := make(chan app.OperationUpdate)
busyModule := &interactiveScriptModule{snapshot: operationSnapshot(), streams: []chan app.OperationUpdate{updates}}
@@ -269,9 +269,12 @@ func TestCompleteModelFooterHintsMatchContextAndAcceptedKeys(t *testing.T) {
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")
assertUsableFrame(t, model.View(), 120, 36, "q: Stop")
model = updateKey(model, "u")
assertUsableFrame(t, model.View(), 120, 36, "u: hide unavailable")
if !model.showUnavailable {
t.Fatal("u did not toggle unavailable Targets while busy")
}
assertUsableFrame(t, model.View(), 120, 36, "q: Stop")
close(updates)
})
+2 -2
View File
@@ -42,7 +42,7 @@ func TestMainAndProfilePagesMapKeysAndObscurePasswords(t *testing.T) {
}},
}}
model := NewModel(module)
if view := model.View(); !strings.Contains(view, "Active Profile: office") {
if view := model.View(); !strings.Contains(view, "Profile: office") {
t.Fatalf("main view missing Active Profile:\n%s", view)
}
@@ -130,7 +130,7 @@ func TestRenderedMalformedProxyURLStillObscuresPassword(t *testing.T) {
func TestNoProfileStateStaysOnAccessibleMainPage(t *testing.T) {
model := NewModel(&recordingModule{})
view := model.View()
if !strings.Contains(view, "No Active Profile") || !strings.Contains(view, "m: manage Proxy Profiles") {
if !strings.Contains(view, "No Active Profile") || !strings.Contains(view, "m: Profiles") {
t.Fatalf("no-Profile main view is not actionable:\n%s", view)
}
}
+15 -52
View File
@@ -14,12 +14,7 @@ func (p terminalPresentation) mainView(model presentationState, width, height in
if activeProfile == "" {
activeProfile = "No Active Profile"
}
header := fmt.Sprintf("%s Operations Console\nActive Profile: %s Targets: %s",
p.theme.title.Render("proxyctl"), activeProfile, p.stateCountSummary(model.snapshot))
if width < wideWidth {
header = fmt.Sprintf("%s Operations Console\nActive Profile: %s\nTargets: %s",
p.theme.title.Render("proxyctl"), activeProfile, p.stateCountSummary(model.snapshot))
}
header := fmt.Sprintf("%s Profile: %s", p.theme.title.Render("proxyctl"), activeProfile)
catalog := newTargetCatalog(model.snapshot, dashboard.showUnavailable, dashboard.selectedTargetID)
footer := p.dashboardFooter(model, catalog)
@@ -97,62 +92,30 @@ func (p terminalPresentation) driftResolutionView(selected app.TargetSnapshot) (
}
func (p terminalPresentation) dashboardFooter(model presentationState, catalog targetCatalog) string {
dashboard, operation, width := model.dashboard, model.operation, model.terminal.width
visibility := "u: show unavailable"
dashboard, operation := model.dashboard, model.operation
visibility := "u: Show hidden"
if dashboard.showUnavailable {
visibility = "u: hide unavailable"
visibility = "u: Hide unavailable"
}
if operation.exitDeferred {
return "Operation stopping; changes disabled ?: Help"
}
if dashboard.focus == targetDetailsPane {
if operation.exitDeferred {
return "↑/k ↓/j PgUp/PgDn: browse Tab: focus Targets " + visibility + " ?: help\nExit pending; new work disabled until safe completion"
}
if width < wideWidth {
if operation.active {
return "↑/k ↓/j PgUp/PgDn: browse Tab: focus Targets\n?: help q/Ctrl+C: defer exit; mutations disabled"
}
if width < 70 {
return "↑/k ↓/j PgUp/PgDn: scroll Tab: focus Targets\np: switch Profile m: manage ?: help q: quit"
}
return "↑/k ↓/j PgUp/PgDn: scroll Tab: focus Targets\np: switch Active Profile m: manage Proxy Profiles ?: help q: quit"
}
footer := "↑/k ↓/j PgUp/PgDn: scroll Tab: focus Targets " + visibility + "\np: switch Active Profile m: manage Proxy Profiles ?: help q/Ctrl+C: quit"
if operation.active {
footer = "↑/k ↓/j PgUp/PgDn: scroll Tab: focus Targets " + visibility + " ?: help\nq/Ctrl+C: defer safe exit; mutations disabled"
return "↑/↓: Scroll Tab: Targets ?: Help q: Stop"
}
return footer
}
actions := "↑/k ↓/j: select Tab: focus details " + visibility
if operation.exitDeferred {
return actions + " ?: help\nExit pending; new work disabled until safe completion"
return "↑/↓: Scroll Tab: Targets " + visibility + "\np: Profile m: Profiles r: Refresh ?: Help q: Quit"
}
selected, ok := catalog.selected(dashboard.selectedTargetID)
availability := targetMutationAvailability(selected, ok, operation.active || operation.exitDeferred)
label := targetActionLabel(selected.PrimaryAction.Kind)
if availability.available {
actions = "Space: " + label + " " + actions
} else {
actions = "Space: " + label + " disabled (" + availability.compactReason + ") " + actions
availability := targetMutationAvailability(selected, ok, operation.active)
action := "Space: " + targetActionLabel(selected.PrimaryAction.Kind)
if !availability.available {
action += " unavailable"
}
if width < wideWidth {
if availability.available {
actions = "Space: " + label + " ↑/k ↓/j: select Tab: focus details"
} else {
actions = "Space: " + label + " disabled ↑/k ↓/j: select Tab: focus details"
}
if operation.active {
return actions + "\n?: help q/Ctrl+C: defer exit; mutations disabled"
}
if width < 70 {
return actions + "\np: switch Profile m: manage ?: help q: quit"
}
return actions + "\np: switch Active Profile m: manage Proxy Profiles ?: help q: quit"
}
footer := actions + "\np: switch Active Profile m: manage Proxy Profiles r: refresh ?: help q/Ctrl+C: quit"
if operation.active {
footer = actions + "\n?: help q: defer safe exit; new mutations disabled"
return action + " ↑/↓: Select Tab: Details\n?: Help q: Stop"
}
return footer
return action + " ↑/↓: Select Tab: Details " + visibility + "\np: Profile m: Profiles r: Refresh ?: Help q: Quit"
}
func (p terminalPresentation) profilesView(model presentationState) terminalPage {
+1 -1
View File
@@ -219,7 +219,7 @@ func TestCompleteModelProfileManagementNavigationAndBusyReasonsAreConsistent(t *
model = updateKey(model, "m")
updated, command := model.Update(back)
model = updated.(Model)
if command != nil || !strings.Contains(model.View(), "Operations Console") {
if command != nil || !strings.Contains(model.View(), "Profile:") {
t.Fatalf("%q did not return to the dashboard:\n%s", back.String(), model.View())
}
}
+1 -1
View File
@@ -125,7 +125,7 @@ func TestCompleteModelReconcilesAuthoritativeSnapshotWhenDetectionGenerationClos
if next != nil || model.operation.active {
t.Fatalf("stale closure left detection active: next=%v active=%v", next != nil, model.operation.active)
}
assertUsableFrame(t, model.View(), 80, 24, "> ● Enabled Shell", "Evidence: shell enabled")
assertUsableFrame(t, model.View(), 80, 24, "> ● Enabled Shell", "shell enabled")
}
func (m *progressiveDetectionModule) detectionUpdate(id string, phase app.TargetDetectionPhase, available bool, state target.State, evidence string, err error) app.OperationUpdate {
+9 -9
View File
@@ -33,7 +33,7 @@ func TestCompleteModelFixedSizeCoverageAcrossCompletedWorkflows(t *testing.T) {
}
return resizedModel(t, &recordingModule{snapshot: snapshot}, themeModeNoColor, width, height)
},
want: []string{"Operations Console", "Checking", "Target availability"},
want: []string{"Profile:", "Checking", "Target availability"},
},
{
name: "refresh",
@@ -46,7 +46,7 @@ func TestCompleteModelFixedSizeCoverageAcrossCompletedWorkflows(t *testing.T) {
}
return model
},
want: []string{"Operations Console", "Checking Target availability and state"},
want: []string{"Profile:", "Checking Target availability and state"},
},
{
name: "Target action",
@@ -155,7 +155,7 @@ func TestCompleteModelLightDarkAndNoColorRetainEquivalentSemantics(t *testing.T)
model, _ = applyOperationCommand(t, model, command)
model = updateKey(model, "e")
views[mode] = progressFrame + "--- operation completed ---\n" + model.View()
for _, semantic := range []string{"[focused]", "Disabled", "Drifted", "Applying", "mutations disabled", "Current error:", "REDACTED", "Action: Enable"} {
for _, semantic := range []string{"[focused]", "Disabled", "Drifted", "Applying", "q: Stop", "Current error:", "REDACTED", "Space: Enable"} {
if !strings.Contains(views[mode], semantic) {
t.Fatalf("theme %d lost %q:\n%s", mode, semantic, views[mode])
}
@@ -163,7 +163,7 @@ func TestCompleteModelLightDarkAndNoColorRetainEquivalentSemantics(t *testing.T)
}
plain := views[themeModeNoColor]
for _, symbol := range []string{"● Enabled", "○ Disabled", "! Drifted", "▶ Target details [focused]"} {
for _, symbol := range []string{"○ Disabled", "! Drifted", "▶ Target details [focused]"} {
if !strings.Contains(plain, symbol) {
t.Fatalf("standard Unicode/ASCII status strategy lost %q:\n%s", symbol, plain)
}
@@ -188,7 +188,7 @@ func TestCompleteModelBoundsUnicodeAndKeepsLongContentReachable(t *testing.T) {
snapshot.Profiles[0].Name = longProfile
snapshot.Targets[0].Description.Name = longName
snapshot.Targets[0].Evidence = strings.Repeat("証拠 and wrapped evidence ", 80) + " EVIDENCE-END"
snapshot.Targets[0].Description.Details = strings.Repeat("詳細 line ", 100) + " DETAILS-END"
snapshot.Targets[0].Evidence = strings.Repeat("証拠 and wrapped evidence ", 120) + " EVIDENCE-END"
model := resizedModel(t, &recordingModule{snapshot: snapshot}, themeModeNoColor, 80, 24)
assertCellBoundedFrame(t, model.View(), 80, 24, "Disabled")
@@ -196,7 +196,7 @@ func TestCompleteModelBoundsUnicodeAndKeepsLongContentReachable(t *testing.T) {
for range 120 {
model = updateKey(model, "pgdown")
}
assertCellBoundedFrame(t, model.View(), 80, 24, "DETAILS-END")
assertCellBoundedFrame(t, model.View(), 80, 24, "EVIDENCE-END")
model = updateKey(model, "m")
for range 60 {
@@ -318,7 +318,7 @@ func TestCompleteModelLongFieldsAndDialogsRemainReachableAcrossResizeAtNontermin
func TestCompleteModelResizeStressPreservesDialogFocusSelectionViewportInFlightAndDeferredExit(t *testing.T) {
snapshot := hardeningSnapshot()
snapshot.Targets[1].Description.Details = strings.Repeat("APT scrollable detail. ", 90) + " APT-VIEWPORT-END"
snapshot.Targets[1].Evidence = strings.Repeat("APT scrollable evidence. ", 90) + " APT-VIEWPORT-END"
updates := make(chan app.OperationUpdate, 2)
module := &interactiveScriptModule{snapshot: snapshot, streams: []chan app.OperationUpdate{updates}}
model := resizedModel(t, module, themeModeNoColor, 120, 36)
@@ -354,8 +354,8 @@ func TestCompleteModelResizeStressPreservesDialogFocusSelectionViewportInFlightA
model = updateKey(model, "q")
assertCellBoundedFrame(t, model.View(), 80, 24,
"> ! Drifted APT", "▶ Target details [focused]", "APT-VIEWPORT-", "END",
"Applying", "Exit requested", "new work disabled")
"> ! Drifted APT", "▶ Target details [focused]", "VIEWPORT-END",
"Applying", "Exit requested", "changes disabled")
updates <- app.SucceededOperationUpdate(snapshot,
app.OperationStatus{Summary: "Shell Target is now Enabled", TargetID: "shell"},
+7 -7
View File
@@ -49,7 +49,7 @@ func TestTargetPanelShowsOnlyAvailableTargetsAndAppliesAsynchronously(t *testing
module.result = app.Result{Snapshot: app.Snapshot{ActiveProfile: "office", Targets: []app.TargetSnapshot{{ID: "shell", Description: target.Description{Name: "Shell", Summary: "Exports for Bash/Zsh"}, Available: true, State: target.Enabled, Evidence: "matches", PrimaryAction: app.TargetAction{Kind: app.DisableTargetAction, Available: true}}}}, Notice: "Shell Target is now Enabled"}
model := NewModel(module)
view := model.View()
if !strings.Contains(view, "Shell") || strings.Contains(view, "APT") || !strings.Contains(view, "Action: Apply Profile") {
if !strings.Contains(view, "Shell") || strings.Contains(view, "APT") || !strings.Contains(view, "Space: Apply Profile") {
t.Fatalf("Target panel:\n%s", view)
}
@@ -104,7 +104,7 @@ func TestHelpListsEveryTargetAndMainListsOnlyAvailableTargets(t *testing.T) {
}}}
model := NewModel(module)
view := model.View()
if strings.Contains(view, "APT") || !strings.Contains(view, "Action: Enable") || !strings.Contains(view, "integration installed") {
if strings.Contains(view, "APT") || !strings.Contains(view, "Unavailable:") || !strings.Contains(view, "integration installed") {
t.Fatalf("main Target panel:\n%s", view)
}
model = updateKey(model, "?")
@@ -123,7 +123,7 @@ func TestHelpListsEveryTargetAndMainListsOnlyAvailableTargets(t *testing.T) {
t.Fatalf("scrollable Target reference:\n%s", view)
}
model = updateKey(model, "esc")
if !strings.Contains(model.View(), "Operations Console") {
if !strings.Contains(model.View(), "Profile:") {
t.Fatalf("Escape did not return from help:\n%s", model.View())
}
}
@@ -279,12 +279,12 @@ func TestQAndEscapeReturnFromNestedPages(t *testing.T) {
for _, key := range []tea.KeyMsg{{Type: tea.KeyRunes, Runes: []rune{'q'}}, {Type: tea.KeyEsc}} {
model := updateKey(NewModel(module), "?")
updated, command := model.Update(key)
if command != nil || !strings.Contains(updated.(Model).View(), "Operations Console") {
t.Fatalf("help key %q did not return to Operations Console", key.String())
if command != nil || !strings.Contains(updated.(Model).View(), "Profile:") {
t.Fatalf("help key %q did not return to dashboard", key.String())
}
model = updateKey(NewModel(module), "m")
updated, command = model.Update(key)
if command != nil || !strings.Contains(updated.(Model).View(), "Operations Console") {
if command != nil || !strings.Contains(updated.(Model).View(), "Profile:") {
t.Fatalf("Profile key %q did not return to Target panel", key.String())
}
}
@@ -315,7 +315,7 @@ func TestActiveProfileTransitionRendersPerTargetProgress(t *testing.T) {
t.Fatal("Profile transition did not start progressive operation")
}
model, command = applyOperationCommand(t, model, command)
if command == nil || !strings.Contains(model.View(), "Applying") || !strings.Contains(model.View(), "Active Profile: new") {
if command == nil || !strings.Contains(model.View(), "Applying") || !strings.Contains(model.View(), "Profile: new") {
t.Fatalf("applying progress:\n%s", model.View())
}
model, command = applyOperationCommand(t, model, command)