v0.11.5 c4261b5

fast_mode_test.go RawHistoryFolder

1package modes
2
3import (
4	"context"
5	"errors"
6	"strings"
7	"testing"
8
9	"github.com/bnema/zut/packages/agent/subagents"
10	"github.com/bnema/zut/packages/core"
11	"github.com/bnema/zut/packages/provider"
12)
13
14type recordingFastModeSettingsStore struct {
15	SettingsStore
16	setCalls int
17	last     bool
18}
19
20func (s *recordingFastModeSettingsStore) SetFastMode(enabled bool) error {
21	s.setCalls++
22	s.last = enabled
23	return nil
24}
25
26type failingFastModeSettingsStore struct {
27	SettingsStore
28	err error
29}
30
31func (s *failingFastModeSettingsStore) SetFastMode(bool) error { return s.err }
32
33type fastModeProviderClient struct{}
34
35func (fastModeProviderClient) Name() string { return "openai" }
36func (fastModeProviderClient) Stream(_ context.Context, _ provider.Request) (<-chan provider.Event, error) {
37	out := make(chan provider.Event)
38	close(out)
39	return out, nil
40}
41
42func TestSettingsDialogOffersFastModeOffByDefault(t *testing.T) {
43	interactive := NewInteractive(InteractiveConfig{Provider: "openai", Model: "gpt-5"})
44	interactive.openSettingsDialog()
45
46	item := findSettingsItem(interactive.settingsDialog.items, "fast_mode")
47	if item == nil {
48		t.Fatal("settings dialog is missing fast mode")
49	}
50	if item.value {
51		t.Fatal("fast mode is enabled by default")
52	}
53}
54
55func TestApplyFastModeUpdatesAgentAndStore(t *testing.T) {
56	store := &recordingFastModeSettingsStore{}
57	agent := core.NewAgent(fastModeProviderClient{}, "gpt-5", "", nil)
58	interactive := NewInteractive(InteractiveConfig{
59		Provider:      "openai",
60		Model:         "gpt-5",
61		SettingsStore: store,
62		Agent:         agent,
63	})
64	interactive.openSettingsDialog()
65	interactive.rend = nil
66
67	interactive.applySettingToggle("fast_mode", true)
68
69	if interactive.cfg.FastMode == nil || !*interactive.cfg.FastMode {
70		t.Fatal("interactive FastMode was not enabled")
71	}
72	if !agent.FastMode {
73		t.Fatal("agent FastMode was not enabled")
74	}
75	if store.setCalls != 1 || !store.last {
76		t.Fatalf("store calls = %d/%v, want one enabled write", store.setCalls, store.last)
77	}
78}
79
80func TestFastSlashTogglesFastMode(t *testing.T) {
81	store := &recordingFastModeSettingsStore{}
82	agent := core.NewAgent(fastModeProviderClient{}, "gpt-5", "", nil)
83	supervisor := subagents.New(subagents.Config{Root: t.TempDir(), RepoRoot: t.TempDir()})
84	interactive := NewInteractive(InteractiveConfig{
85		Provider:      "openai",
86		Model:         "gpt-5",
87		SettingsStore: store,
88		Agent:         agent,
89		Supervisor:    supervisor,
90	})
91	interactive.rend = nil
92
93	if interactive.runSlash(context.Background(), "/FAST") {
94		t.Fatal("/fast requested exit")
95	}
96	if interactive.cfg.FastMode == nil || !*interactive.cfg.FastMode || !agent.FastMode || !supervisor.FastMode() {
97		t.Fatalf("/fast did not enable every live fast-mode target: cfg=%v agent=%v supervisor=%v", interactive.cfg.FastMode, agent.FastMode, supervisor.FastMode())
98	}
99	if store.setCalls != 1 || !store.last || interactive.statusOK != "fast mode enabled" {
100		t.Fatalf("enable persistence/status = calls=%d last=%v status=%q", store.setCalls, store.last, interactive.statusOK)
101	}
102
103	interactive.runSlash(context.Background(), "/fast")
104	if interactive.cfg.FastMode == nil || *interactive.cfg.FastMode || agent.FastMode || supervisor.FastMode() {
105		t.Fatalf("/fast did not disable every live fast-mode target: cfg=%v agent=%v supervisor=%v", interactive.cfg.FastMode, agent.FastMode, supervisor.FastMode())
106	}
107	if store.setCalls != 2 || store.last || interactive.statusOK != "fast mode disabled" {
108		t.Fatalf("disable persistence/status = calls=%d last=%v status=%q", store.setCalls, store.last, interactive.statusOK)
109	}
110}
111
112func TestApplyFastModeRejectsUnsupportedProvider(t *testing.T) {
113	store := &recordingFastModeSettingsStore{}
114	interactive := NewInteractive(InteractiveConfig{
115		Provider:      "anthropic",
116		Model:         "claude-sonnet-4-5",
117		SettingsStore: store,
118	})
119	interactive.openSettingsDialog()
120	interactive.rend = nil
121
122	interactive.applySettingToggle("fast_mode", true)
123
124	if interactive.cfg.FastMode != nil && *interactive.cfg.FastMode {
125		t.Fatal("unsupported fast mode was enabled")
126	}
127	if store.setCalls != 0 {
128		t.Fatalf("store calls = %d, want none", store.setCalls)
129	}
130	if !strings.Contains(interactive.statusErr, "only supported for OpenAI providers") {
131		t.Fatalf("status error = %q, want unsupported-provider error", interactive.statusErr)
132	}
133}
134
135func TestApplyFastModeCanDisableUnsupportedProvider(t *testing.T) {
136	enabled := true
137	store := &recordingFastModeSettingsStore{}
138	agent := core.NewAgent(fastModeProviderClient{}, "claude-sonnet-4-5", "", nil)
139	agent.FastMode = true
140	interactive := NewInteractive(InteractiveConfig{
141		Provider:      "anthropic",
142		Model:         "claude-sonnet-4-5",
143		FastMode:      &enabled,
144		SettingsStore: store,
145		Agent:         agent,
146	})
147	interactive.openSettingsDialog()
148	interactive.rend = nil
149
150	interactive.applySettingToggle("fast_mode", false)
151
152	if interactive.cfg.FastMode == nil || *interactive.cfg.FastMode {
153		t.Fatal("fast mode was not disabled")
154	}
155	if agent.FastMode {
156		t.Fatal("agent fast mode was not disabled")
157	}
158	if store.setCalls != 1 || store.last {
159		t.Fatalf("store calls = %d/%v, want one disabled write", store.setCalls, store.last)
160	}
161}
162
163func TestApplyFastModeLeavesLiveStateWhenPersistenceFails(t *testing.T) {
164	store := &failingFastModeSettingsStore{err: errors.New("disk full")}
165	agent := core.NewAgent(fastModeProviderClient{}, "gpt-5", "", nil)
166	interactive := NewInteractive(InteractiveConfig{
167		Provider:      "openai",
168		Model:         "gpt-5",
169		SettingsStore: store,
170		Agent:         agent,
171	})
172	interactive.openSettingsDialog()
173	interactive.rend = nil
174
175	interactive.applySettingToggle("fast_mode", true)
176
177	if interactive.cfg.FastMode != nil && *interactive.cfg.FastMode {
178		t.Fatal("fast mode changed despite persistence failure")
179	}
180	if agent.FastMode {
181		t.Fatal("agent fast mode changed despite persistence failure")
182	}
183	if !strings.Contains(interactive.statusErr, "disk full") {
184		t.Fatalf("status error = %q, want persistence error", interactive.statusErr)
185	}
186}
187
188func findSettingsItem(items []settingsItem, key string) *settingsItem {
189	for idx := range items {
190		if items[idx].key == key {
191			return &items[idx]
192		}
193	}
194	return nil
195}

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 476

↑↓ moveenter openesc close