saeeol 1.3.1 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,175 @@
1
+ /**
2
+ * tui-walkthrough-leader.test.ts — Leader 키 조합 전체 검증
3
+ *
4
+ * ctrl+x(leader) + 키 조합으로 실행되는 모든 기능을 한 번씩 누름.
5
+ * 다이얼로그 열기/닫기, 토글, 액션 모두 포함.
6
+ */
7
+
8
+ import { describe, expect, test, beforeAll, afterAll } from "bun:test"
9
+ import { tmpdir, disposeAllInstances } from "../fixture/fixture"
10
+ import * as Log from "@saeeol/core/util/log"
11
+ import { Flag } from "@saeeol/core/flag/flag"
12
+ import { TuiDriver, Key } from "./tui-walkthrough-driver"
13
+
14
+ void Log.init({ print: false })
15
+
16
+ describe("TUI walkthrough: leader keys", () => {
17
+ let driver: TuiDriver
18
+ let tmp: Awaited<ReturnType<typeof tmpdir>>
19
+
20
+ beforeAll(async () => {
21
+ tmp = await tmpdir({ git: true, config: {} })
22
+ Flag.SAEEOL_EXPERIMENTAL_HTTPAPI = false
23
+ driver = new TuiDriver(tmp.path)
24
+ await driver.wait(6000)
25
+ })
26
+
27
+ afterAll(async () => {
28
+ await driver.saveReport(".tui-leader-report.txt")
29
+ await driver.kill()
30
+ await disposeAllInstances()
31
+ Flag.SAEEOL_EXPERIMENTAL_HTTPAPI = true
32
+ })
33
+
34
+ // ── 다이얼로그 열고 닫기 ──
35
+
36
+ test("leader+l → session list dialog", async () => {
37
+ await driver.leader("l")
38
+ await driver.wait(1500)
39
+ driver.snapshot("leader-l-session-list")
40
+ expect(driver.errors()).toHaveLength(0)
41
+ await driver.dismiss()
42
+ })
43
+
44
+ test("leader+m → model list dialog", async () => {
45
+ await driver.leader("m")
46
+ await driver.wait(1500)
47
+ driver.snapshot("leader-m-model-list")
48
+ expect(driver.errors()).toHaveLength(0)
49
+ await driver.dismiss()
50
+ })
51
+
52
+ test("leader+a → agent list dialog", async () => {
53
+ await driver.leader("a")
54
+ await driver.wait(1500)
55
+ driver.snapshot("leader-a-agent-list")
56
+ expect(driver.errors()).toHaveLength(0)
57
+ await driver.dismiss()
58
+ })
59
+
60
+ test("leader+s → status dialog", async () => {
61
+ await driver.leader("s")
62
+ await driver.wait(1500)
63
+ driver.snapshot("leader-s-status")
64
+ expect(driver.errors()).toHaveLength(0)
65
+ await driver.dismiss()
66
+ })
67
+
68
+ test("leader+t → theme list dialog", async () => {
69
+ await driver.leader("t")
70
+ await driver.wait(1500)
71
+ driver.snapshot("leader-t-theme-list")
72
+ expect(driver.errors()).toHaveLength(0)
73
+ await driver.dismiss()
74
+ })
75
+
76
+ test("leader+g → session timeline dialog", async () => {
77
+ await driver.leader("g")
78
+ await driver.wait(1500)
79
+ driver.snapshot("leader-g-timeline")
80
+ expect(driver.errors()).toHaveLength(0)
81
+ await driver.dismiss()
82
+ })
83
+
84
+ // ── 토글 / 액션 ──
85
+
86
+ test("leader+b → sidebar toggle (open)", async () => {
87
+ await driver.leader("b")
88
+ await driver.wait(800)
89
+ driver.snapshot("leader-b-sidebar-open")
90
+ expect(driver.errors()).toHaveLength(0)
91
+ })
92
+
93
+ test("leader+b → sidebar toggle (close)", async () => {
94
+ await driver.leader("b")
95
+ await driver.wait(800)
96
+ driver.snapshot("leader-b-sidebar-close")
97
+ expect(driver.errors()).toHaveLength(0)
98
+ })
99
+
100
+ test("leader+n → new session (home)", async () => {
101
+ await driver.leader("n")
102
+ await driver.wait(1000)
103
+ driver.snapshot("leader-n-new-session")
104
+ expect(driver.errors()).toHaveLength(0)
105
+ })
106
+
107
+ test("leader+c → session compact", async () => {
108
+ await driver.leader("c")
109
+ await driver.wait(1000)
110
+ driver.snapshot("leader-c-compact")
111
+ expect(driver.errors()).toHaveLength(0)
112
+ })
113
+
114
+ test("leader+h → toggle conceal / tips", async () => {
115
+ await driver.leader("h")
116
+ await driver.wait(800)
117
+ driver.snapshot("leader-h-conceal-toggle")
118
+ expect(driver.errors()).toHaveLength(0)
119
+ })
120
+
121
+ test("leader+= → feedback up", async () => {
122
+ await driver.leader("=")
123
+ await driver.wait(800)
124
+ driver.snapshot("leader-eq-feedback-up")
125
+ expect(driver.errors()).toHaveLength(0)
126
+ })
127
+
128
+ test("leader+- → feedback down", async () => {
129
+ await driver.leader("-")
130
+ await driver.wait(800)
131
+ driver.snapshot("leader-minus-feedback-down")
132
+ expect(driver.errors()).toHaveLength(0)
133
+ })
134
+
135
+ test("leader+y → copy message", async () => {
136
+ await driver.leader("y")
137
+ await driver.wait(800)
138
+ driver.snapshot("leader-y-copy")
139
+ expect(driver.errors()).toHaveLength(0)
140
+ })
141
+
142
+ test("leader+u → undo message", async () => {
143
+ await driver.leader("u")
144
+ await driver.wait(800)
145
+ driver.snapshot("leader-u-undo")
146
+ expect(driver.errors()).toHaveLength(0)
147
+ })
148
+
149
+ test("leader+r → redo message", async () => {
150
+ await driver.leader("r")
151
+ await driver.wait(800)
152
+ driver.snapshot("leader-r-redo")
153
+ expect(driver.errors()).toHaveLength(0)
154
+ })
155
+
156
+ // ── 세션 네비게이션 (leader+방향키) ──
157
+
158
+ test("leader+down → child session first", async () => {
159
+ driver.write(Key.ctrlX)
160
+ await driver.wait(100)
161
+ driver.write(Key.down)
162
+ await driver.wait(800)
163
+ driver.snapshot("leader-down-child-first")
164
+ expect(driver.errors()).toHaveLength(0)
165
+ })
166
+
167
+ // ── leader+q → 종료 (마지막에) ──
168
+
169
+ test("leader+q → exit", async () => {
170
+ await driver.leader("q")
171
+ await driver.wait(2000)
172
+ driver.snapshot("leader-q-exit")
173
+ expect(driver.errors()).toHaveLength(0)
174
+ })
175
+ })
@@ -0,0 +1,177 @@
1
+ /**
2
+ * tui-walkthrough-scroll.test.ts — 스크롤/네비게이션 키 전체 검증
3
+ *
4
+ * - page up/down
5
+ * - ctrl+page up/down (half page)
6
+ * - ctrl+up/down (line)
7
+ * - ctrl+g / home (first message)
8
+ * - ctrl+alt+g / end (last message)
9
+ * - tab / shift+tab (agent cycle)
10
+ * - f2 / shift+f2 (model cycle)
11
+ * - ctrl+p (command palette)
12
+ * - ctrl+t (variant cycle)
13
+ * - escape (interrupt)
14
+ */
15
+
16
+ import { describe, expect, test, beforeAll, afterAll } from "bun:test"
17
+ import { tmpdir, disposeAllInstances } from "../fixture/fixture"
18
+ import * as Log from "@saeeol/core/util/log"
19
+ import { Flag } from "@saeeol/core/flag/flag"
20
+ import { TuiDriver, Key } from "./tui-walkthrough-driver"
21
+
22
+ void Log.init({ print: false })
23
+
24
+ describe("TUI walkthrough: scroll & navigation", () => {
25
+ let driver: TuiDriver
26
+ let tmp: Awaited<ReturnType<typeof tmpdir>>
27
+
28
+ beforeAll(async () => {
29
+ tmp = await tmpdir({ git: true, config: {} })
30
+ Flag.SAEEOL_EXPERIMENTAL_HTTPAPI = false
31
+ driver = new TuiDriver(tmp.path)
32
+ await driver.wait(6000)
33
+ })
34
+
35
+ afterAll(async () => {
36
+ await driver.saveReport(".tui-scroll-report.txt")
37
+ await driver.kill()
38
+ await disposeAllInstances()
39
+ Flag.SAEEOL_EXPERIMENTAL_HTTPAPI = true
40
+ })
41
+
42
+ // ── 페이지 스크롤 ──
43
+
44
+ test("page up", async () => {
45
+ driver.write(Key.pageUp)
46
+ await driver.wait(300)
47
+ driver.snapshot("scroll-page-up")
48
+ expect(driver.errors()).toHaveLength(0)
49
+ })
50
+
51
+ test("page down", async () => {
52
+ driver.write(Key.pageDown)
53
+ await driver.wait(300)
54
+ driver.snapshot("scroll-page-down")
55
+ expect(driver.errors()).toHaveLength(0)
56
+ })
57
+
58
+ test("ctrl+page up (half page)", async () => {
59
+ driver.write(Key.ctrlPageUp)
60
+ await driver.wait(300)
61
+ driver.snapshot("scroll-ctrl-page-up")
62
+ expect(driver.errors()).toHaveLength(0)
63
+ })
64
+
65
+ test("ctrl+page down (half page)", async () => {
66
+ driver.write(Key.ctrlPageDown)
67
+ await driver.wait(300)
68
+ driver.snapshot("scroll-ctrl-page-down")
69
+ expect(driver.errors()).toHaveLength(0)
70
+ })
71
+
72
+ // ── 라인 스크롤 ──
73
+
74
+ test("ctrl+up (line up)", async () => {
75
+ driver.write(Key.ctrlUp)
76
+ await driver.wait(300)
77
+ driver.snapshot("scroll-ctrl-up")
78
+ expect(driver.errors()).toHaveLength(0)
79
+ })
80
+
81
+ test("ctrl+down (line down)", async () => {
82
+ driver.write(Key.ctrlDown)
83
+ await driver.wait(300)
84
+ driver.snapshot("scroll-ctrl-down")
85
+ expect(driver.errors()).toHaveLength(0)
86
+ })
87
+
88
+ // ── 첫/마지막 메시지 ──
89
+
90
+ test("ctrl+g (first message)", async () => {
91
+ driver.write(Key.ctrlG)
92
+ await driver.wait(300)
93
+ driver.snapshot("scroll-ctrl-g-first")
94
+ expect(driver.errors()).toHaveLength(0)
95
+ })
96
+
97
+ test("home (first message)", async () => {
98
+ driver.write(Key.home)
99
+ await driver.wait(300)
100
+ driver.snapshot("scroll-home")
101
+ expect(driver.errors()).toHaveLength(0)
102
+ })
103
+
104
+ test("end (last message)", async () => {
105
+ driver.write(Key.end)
106
+ await driver.wait(300)
107
+ driver.snapshot("scroll-end")
108
+ expect(driver.errors()).toHaveLength(0)
109
+ })
110
+
111
+ // ── 에이전트/모델 사이클 ──
112
+
113
+ test("tab (next agent)", async () => {
114
+ driver.write(Key.tab)
115
+ await driver.wait(500)
116
+ driver.snapshot("nav-tab-agent-next")
117
+ expect(driver.errors()).toHaveLength(0)
118
+ })
119
+
120
+ test("shift+tab (prev agent)", async () => {
121
+ driver.write(Key.shiftTab)
122
+ await driver.wait(500)
123
+ driver.snapshot("nav-shift-tab-agent-prev")
124
+ expect(driver.errors()).toHaveLength(0)
125
+ })
126
+
127
+ test("f2 (next model)", async () => {
128
+ driver.write(Key.f2)
129
+ await driver.wait(500)
130
+ driver.snapshot("nav-f2-model-next")
131
+ expect(driver.errors()).toHaveLength(0)
132
+ })
133
+
134
+ test("shift+f2 (prev model)", async () => {
135
+ driver.write(Key.shiftF2)
136
+ await driver.wait(500)
137
+ driver.snapshot("nav-shift-f2-model-prev")
138
+ expect(driver.errors()).toHaveLength(0)
139
+ })
140
+
141
+ // ── 커맨드 팔레트 ──
142
+
143
+ test("ctrl+p (command palette)", async () => {
144
+ driver.write(Key.ctrlP)
145
+ await driver.wait(1000)
146
+ driver.snapshot("nav-ctrl-p-palette")
147
+ expect(driver.errors()).toHaveLength(0)
148
+ await driver.dismiss()
149
+ })
150
+
151
+ // ── variant 사이클 ──
152
+
153
+ test("ctrl+t (variant cycle)", async () => {
154
+ driver.write(Key.ctrlT)
155
+ await driver.wait(500)
156
+ driver.snapshot("nav-ctrl-t-variant")
157
+ expect(driver.errors()).toHaveLength(0)
158
+ })
159
+
160
+ // ── 인터럽트 ──
161
+
162
+ test("escape (interrupt)", async () => {
163
+ driver.write(Key.esc)
164
+ await driver.wait(500)
165
+ driver.snapshot("nav-escape-interrupt")
166
+ expect(driver.errors()).toHaveLength(0)
167
+ })
168
+
169
+ // ── ctrl+d (종료 시도) ──
170
+
171
+ test("ctrl+d (exit attempt)", async () => {
172
+ driver.write(Key.ctrlD)
173
+ await driver.wait(1000)
174
+ driver.snapshot("nav-ctrl-d-exit")
175
+ expect(driver.errors()).toHaveLength(0)
176
+ })
177
+ })
@@ -0,0 +1,302 @@
1
+ /**
2
+ * tui-walkthrough-slash.test.ts — 슬래시 명령어 전체 검증
3
+ *
4
+ * /help, /status, /models, /agents, /themes, /sessions, /new, /exit,
5
+ * /restart, /connect, /mcps, /mcp-refresh, /variants, /logs,
6
+ * /editor, /skills, /pointing, /timestamps, /thinking, /rename,
7
+ * /timeline, /fork, /compact, /copy, /export, /share, /unshare,
8
+ * /undo, /redo, /org, /quit, /q 등 전부.
9
+ */
10
+
11
+ import { describe, expect, test, beforeAll, afterAll } from "bun:test"
12
+ import { tmpdir, disposeAllInstances } from "../fixture/fixture"
13
+ import * as Log from "@saeeol/core/util/log"
14
+ import { Flag } from "@saeeol/core/flag/flag"
15
+ import { TuiDriver } from "./tui-walkthrough-driver"
16
+
17
+ void Log.init({ print: false })
18
+
19
+ describe("TUI walkthrough: slash commands", () => {
20
+ let driver: TuiDriver
21
+ let tmp: Awaited<ReturnType<typeof tmpdir>>
22
+
23
+ beforeAll(async () => {
24
+ tmp = await tmpdir({ git: true, config: {} })
25
+ Flag.SAEEOL_EXPERIMENTAL_HTTPAPI = false
26
+ driver = new TuiDriver(tmp.path)
27
+ await driver.wait(6000)
28
+ })
29
+
30
+ afterAll(async () => {
31
+ await driver.saveReport(".tui-slash-report.txt")
32
+ await driver.kill()
33
+ await disposeAllInstances()
34
+ Flag.SAEEOL_EXPERIMENTAL_HTTPAPI = true
35
+ })
36
+
37
+ // ── 시스템 슬래시 명령 ──
38
+
39
+ test("/help → help dialog", async () => {
40
+ await driver.slash("help")
41
+ driver.snapshot("slash-help")
42
+ expect(driver.errors()).toHaveLength(0)
43
+ await driver.dismiss()
44
+ })
45
+
46
+ test("/status → status dialog", async () => {
47
+ await driver.slash("status")
48
+ driver.snapshot("slash-status")
49
+ expect(driver.errors()).toHaveLength(0)
50
+ await driver.dismiss()
51
+ })
52
+
53
+ test("/themes → theme dialog", async () => {
54
+ await driver.slash("themes")
55
+ driver.snapshot("slash-themes")
56
+ expect(driver.errors()).toHaveLength(0)
57
+ await driver.dismiss()
58
+ })
59
+
60
+ test("/models → model dialog", async () => {
61
+ await driver.slash("models")
62
+ driver.snapshot("slash-models")
63
+ expect(driver.errors()).toHaveLength(0)
64
+ await driver.dismiss()
65
+ })
66
+
67
+ test("/agents → agent dialog", async () => {
68
+ await driver.slash("agents")
69
+ driver.snapshot("slash-agents")
70
+ expect(driver.errors()).toHaveLength(0)
71
+ await driver.dismiss()
72
+ })
73
+
74
+ test("/variants → variant dialog", async () => {
75
+ await driver.slash("variants")
76
+ driver.snapshot("slash-variants")
77
+ expect(driver.errors()).toHaveLength(0)
78
+ await driver.dismiss()
79
+ })
80
+
81
+ // ── 세션 슬래시 명령 ──
82
+
83
+ test("/sessions → session list", async () => {
84
+ await driver.slash("sessions")
85
+ driver.snapshot("slash-sessions")
86
+ expect(driver.errors()).toHaveLength(0)
87
+ await driver.dismiss()
88
+ })
89
+
90
+ test("/resume → session list alias", async () => {
91
+ await driver.slash("resume")
92
+ driver.snapshot("slash-resume")
93
+ expect(driver.errors()).toHaveLength(0)
94
+ await driver.dismiss()
95
+ })
96
+
97
+ test("/continue → session list alias", async () => {
98
+ await driver.slash("continue")
99
+ driver.snapshot("slash-continue")
100
+ expect(driver.errors()).toHaveLength(0)
101
+ await driver.dismiss()
102
+ })
103
+
104
+ test("/new → new session (home)", async () => {
105
+ await driver.slash("new")
106
+ driver.snapshot("slash-new")
107
+ expect(driver.errors()).toHaveLength(0)
108
+ })
109
+
110
+ test("/clear → alias for /new", async () => {
111
+ await driver.slash("clear")
112
+ driver.snapshot("slash-clear")
113
+ expect(driver.errors()).toHaveLength(0)
114
+ })
115
+
116
+ // ── 에이전트/MCP 슬래시 명령 ──
117
+
118
+ test("/mcps → MCP dialog", async () => {
119
+ await driver.slash("mcps")
120
+ driver.snapshot("slash-mcps")
121
+ expect(driver.errors()).toHaveLength(0)
122
+ await driver.dismiss()
123
+ })
124
+
125
+ test("/mcp-refresh → refresh MCP servers", async () => {
126
+ await driver.slash("mcp-refresh")
127
+ driver.snapshot("slash-mcp-refresh")
128
+ expect(driver.errors()).toHaveLength(0)
129
+ })
130
+
131
+ test("/connect → provider dialog", async () => {
132
+ await driver.slash("connect")
133
+ driver.snapshot("slash-connect")
134
+ expect(driver.errors()).toHaveLength(0)
135
+ await driver.dismiss()
136
+ })
137
+
138
+ // ── 세션 관리 슬래시 명령 ──
139
+
140
+ test("/timeline → timeline dialog", async () => {
141
+ await driver.slash("timeline")
142
+ driver.snapshot("slash-timeline")
143
+ expect(driver.errors()).toHaveLength(0)
144
+ await driver.dismiss()
145
+ })
146
+
147
+ test("/compact → compact session", async () => {
148
+ await driver.slash("compact")
149
+ driver.snapshot("slash-compact")
150
+ expect(driver.errors()).toHaveLength(0)
151
+ })
152
+
153
+ test("/summarize → alias for compact", async () => {
154
+ await driver.slash("summarize")
155
+ driver.snapshot("slash-summarize")
156
+ expect(driver.errors()).toHaveLength(0)
157
+ })
158
+
159
+ test("/rename → rename dialog", async () => {
160
+ await driver.slash("rename")
161
+ driver.snapshot("slash-rename")
162
+ expect(driver.errors()).toHaveLength(0)
163
+ await driver.dismiss()
164
+ })
165
+
166
+ test("/fork → fork dialog", async () => {
167
+ await driver.slash("fork")
168
+ driver.snapshot("slash-fork")
169
+ expect(driver.errors()).toHaveLength(0)
170
+ await driver.dismiss()
171
+ })
172
+
173
+ // ── 편집/내보내기 ──
174
+
175
+ test("/editor → open editor", async () => {
176
+ await driver.slash("editor")
177
+ driver.snapshot("slash-editor")
178
+ expect(driver.errors()).toHaveLength(0)
179
+ await driver.dismiss(1000)
180
+ })
181
+
182
+ test("/skills → skills dialog", async () => {
183
+ await driver.slash("skills")
184
+ driver.snapshot("slash-skills")
185
+ expect(driver.errors()).toHaveLength(0)
186
+ await driver.dismiss()
187
+ })
188
+
189
+ test("/copy → copy transcript", async () => {
190
+ await driver.slash("copy")
191
+ driver.snapshot("slash-copy")
192
+ expect(driver.errors()).toHaveLength(0)
193
+ })
194
+
195
+ test("/export → export dialog", async () => {
196
+ await driver.slash("export")
197
+ driver.snapshot("slash-export")
198
+ expect(driver.errors()).toHaveLength(0)
199
+ await driver.dismiss()
200
+ })
201
+
202
+ // ── 토글 ──
203
+
204
+ test("/timestamps → toggle timestamps", async () => {
205
+ await driver.slash("timestamps")
206
+ driver.snapshot("slash-timestamps")
207
+ expect(driver.errors()).toHaveLength(0)
208
+ })
209
+
210
+ test("/toggle-timestamps → alias", async () => {
211
+ await driver.slash("toggle-timestamps")
212
+ driver.snapshot("slash-toggle-timestamps")
213
+ expect(driver.errors()).toHaveLength(0)
214
+ })
215
+
216
+ test("/thinking → toggle thinking", async () => {
217
+ await driver.slash("thinking")
218
+ driver.snapshot("slash-thinking")
219
+ expect(driver.errors()).toHaveLength(0)
220
+ })
221
+
222
+ test("/toggle-thinking → alias", async () => {
223
+ await driver.slash("toggle-thinking")
224
+ driver.snapshot("slash-toggle-thinking")
225
+ expect(driver.errors()).toHaveLength(0)
226
+ })
227
+
228
+ // ── 포인팅 ──
229
+
230
+ test("/pointing → pointing dialog", async () => {
231
+ await driver.slash("pointing")
232
+ driver.snapshot("slash-pointing")
233
+ expect(driver.errors()).toHaveLength(0)
234
+ await driver.dismiss()
235
+ })
236
+
237
+ test("/point → alias for pointing", async () => {
238
+ await driver.slash("point")
239
+ driver.snapshot("slash-point")
240
+ expect(driver.errors()).toHaveLength(0)
241
+ await driver.dismiss()
242
+ })
243
+
244
+ test("/unpointing → clear pointing", async () => {
245
+ await driver.slash("unpointing")
246
+ driver.snapshot("slash-unpointing")
247
+ expect(driver.errors()).toHaveLength(0)
248
+ })
249
+
250
+ test("/unpoint → alias for unpointing", async () => {
251
+ await driver.slash("unpoint")
252
+ driver.snapshot("slash-unpoint")
253
+ expect(driver.errors()).toHaveLength(0)
254
+ })
255
+
256
+ // ── 공유 (share/unshare는 provider 없이 에러 처리만) ──
257
+
258
+ test("/share → share session", async () => {
259
+ await driver.slash("share")
260
+ driver.snapshot("slash-share")
261
+ expect(driver.errors()).toHaveLength(0)
262
+ await driver.dismiss()
263
+ })
264
+
265
+ test("/unshare → unshare session", async () => {
266
+ await driver.slash("unshare")
267
+ driver.snapshot("slash-unshare")
268
+ expect(driver.errors()).toHaveLength(0)
269
+ await driver.dismiss()
270
+ })
271
+
272
+ // ── undo/redo ──
273
+
274
+ test("/undo → undo message", async () => {
275
+ await driver.slash("undo")
276
+ driver.snapshot("slash-undo")
277
+ expect(driver.errors()).toHaveLength(0)
278
+ })
279
+
280
+ test("/redo → redo message", async () => {
281
+ await driver.slash("redo")
282
+ driver.snapshot("slash-redo")
283
+ expect(driver.errors()).toHaveLength(0)
284
+ })
285
+
286
+ // ── 로그 ──
287
+
288
+ test("/logs → open log file", async () => {
289
+ await driver.slash("logs")
290
+ driver.snapshot("slash-logs")
291
+ expect(driver.errors()).toHaveLength(0)
292
+ })
293
+
294
+ // ── 종료 (마지막) ──
295
+
296
+ test("/quit → exit alias", async () => {
297
+ await driver.slash("quit")
298
+ await driver.wait(2000)
299
+ driver.snapshot("slash-quit")
300
+ expect(driver.errors()).toHaveLength(0)
301
+ })
302
+ })