v0.3.93 0192a2e

reload_ext_test.go RawHistoryFolder

1package modes
2
3import (
4	"errors"
5	"strings"
6	"testing"
7
8	"github.com/patriceckhart/zot/packages/agent/extensions"
9	"github.com/patriceckhart/zot/packages/core"
10	"github.com/patriceckhart/zot/packages/tui"
11)
12
13func TestFormatReloadStatusIncludesErrorDetails(t *testing.T) {
14	msg, failed := formatReloadStatus(extensions.ReloadStats{
15		Stopped: 2,
16		Loaded:  1,
17		Ready:   1,
18		Errors: []error{
19			errors.New("broken manifest"),
20			errors.New("spawn worker: executable not found"),
21		},
22	})
23	if !failed {
24		t.Fatal("reload with errors was not marked failed")
25	}
26	for _, want := range []string{
27		"2 stopped, 1 loaded (1 ready)",
28		"2 error(s)",
29		"broken manifest",
30		"spawn worker: executable not found",
31	} {
32		if !strings.Contains(msg, want) {
33			t.Fatalf("reload status missing %q: %s", want, msg)
34		}
35	}
36}
37
38func TestRenderReloadErrorsPreservesMultilineAlignment(t *testing.T) {
39	lines := renderReloadErrors(tui.Theme{Error: 1}, []string{"failed to start\nextension directory: /tmp/ext\nstderr log: /tmp/ext.log"}, 80)
40	if len(lines) != 3 {
41		t.Fatalf("rendered %d lines, want 3: %q", len(lines), lines)
42	}
43	if !strings.Contains(lines[0], "✖ failed to start") {
44		t.Fatalf("first line = %q, want error marker", lines[0])
45	}
46	for _, line := range lines[1:] {
47		if !strings.Contains(line, "  ") {
48			t.Fatalf("continuation line = %q, want indentation", line)
49		}
50	}
51}
52
53func TestFormatReloadStatusSuccess(t *testing.T) {
54	msg, failed := formatReloadStatus(extensions.ReloadStats{Stopped: 2, Loaded: 3, Ready: 3})
55	if failed {
56		t.Fatal("successful reload was marked failed")
57	}
58	if want := "reloaded: 2 stopped, 3 loaded (3 ready)"; msg != want {
59		t.Fatalf("reload status = %q, want %q", msg, want)
60	}
61}
62
63func TestSetReloadStatusUsesErrorChannelForFailures(t *testing.T) {
64	i := &Interactive{}
65	i.setReloadStatus("broken extension", true)
66	if i.statusErr != "broken extension" || i.statusOK != "" {
67		t.Fatalf("failed reload status used wrong channel: err=%q ok=%q", i.statusErr, i.statusOK)
68	}
69	if len(i.reloadErrors) != 1 || i.reloadErrors[0] != "broken extension" {
70		t.Fatalf("reload errors = %q, want persistent failure", i.reloadErrors)
71	}
72	i.setReloadStatus("reload complete", false)
73	if i.statusOK != "reload complete" || i.statusErr != "" {
74		t.Fatalf("successful reload status used wrong channel: err=%q ok=%q", i.statusErr, i.statusOK)
75	}
76	if len(i.reloadErrors) != 0 {
77		t.Fatalf("reload errors = %q after successful reload, want none", i.reloadErrors)
78	}
79}
80
81func TestStartupExtensionFailureAppearsInChatWithoutEnteringContext(t *testing.T) {
82	agent := core.NewAgent(nil, "", "", nil)
83	i := NewInteractive(InteractiveConfig{
84		Agent:                  agent,
85		Theme:                  tui.Theme{Error: 1},
86		StartupExtensionErrors: []string{"startup failed: stderr log: /tmp/startup.log"},
87	})
88
89	chat := strings.Join(i.buildChatLocked(30), "\n")
90	if !strings.Contains(chat, "startup failed") || !strings.Contains(chat, "/tmp/startup.log") {
91		t.Fatalf("chat does not contain startup extension failure: %q", chat)
92	}
93	if got := len(agent.Messages()); got != 0 {
94		t.Fatalf("agent context contains %d startup error message(s), want none", got)
95	}
96}
97
98func TestReloadFailureRemainsInChatWithoutEnteringContext(t *testing.T) {
99	agent := core.NewAgent(nil, "", "", nil)
100	i := NewInteractive(InteractiveConfig{
101		Agent: agent,
102		Theme: tui.Theme{Error: 1},
103	})
104	seq := i.setReloadStatus("broken extension: stderr log: /tmp/ext.log", true)
105	if !i.clearReloadStatus(seq, i.statusErr, true) {
106		t.Fatal("temporary reload status was not cleared")
107	}
108
109	chat := strings.Join(i.buildChatLocked(30), "\n")
110	if !strings.Contains(chat, "broken extension") || !strings.Contains(chat, "/tmp/ext.log") {
111		t.Fatalf("chat does not contain persistent reload failure: %q", chat)
112	}
113	if got := len(agent.Messages()); got != 0 {
114		t.Fatalf("agent context contains %d reload error message(s), want none", got)
115	}
116}
117
118func TestClearReloadStatusDoesNotClearNewerStatus(t *testing.T) {
119	i := &Interactive{statusErr: "newer error", statusOK: "newer success"}
120	if i.clearReloadStatus(0, "old error", true) {
121		t.Fatal("stale error dismissal reported a clear")
122	}
123	if i.clearReloadStatus(0, "old success", false) {
124		t.Fatal("stale success dismissal reported a clear")
125	}
126	if i.statusErr != "newer error" || i.statusOK != "newer success" {
127		t.Fatalf("stale dismissal changed status: err=%q ok=%q", i.statusErr, i.statusOK)
128	}
129
130	if !i.clearReloadStatus(0, "newer error", true) || i.statusErr != "" {
131		t.Fatalf("matching error status was not cleared: %q", i.statusErr)
132	}
133	if !i.clearReloadStatus(0, "newer success", false) || i.statusOK != "" {
134		t.Fatalf("matching success status was not cleared: %q", i.statusOK)
135	}
136
137	first := i.setReloadStatus("same result", false)
138	second := i.setReloadStatus("same result", false)
139	if first == second {
140		t.Fatal("reload status generation did not advance")
141	}
142	if i.clearReloadStatus(first, "same result", false) {
143		t.Fatal("older identical reload status cleared the newer result")
144	}
145}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 477

↑↓ moveenter openesc close