saeeol 1.4.0 → 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,286 @@
1
+ /**
2
+ * tui-walkthrough-input.test.ts — 입력 편집 키바인드 전체 검증
3
+ *
4
+ * 프롬프트 textarea의 모든 편집 단축키를 한 번씩 누름:
5
+ * - 커서 이동: left, right, up, down, home, end
6
+ * - ctrl+커서: ctrl+a (line home), ctrl+e (line end), ctrl+b (left), ctrl+f (right)
7
+ * - 단어 이동: alt+f, alt+b, ctrl+left, ctrl+right
8
+ * - 선택: shift+방향키, ctrl+shift+a/e
9
+ * - 삭제: backspace, delete, ctrl+k, ctrl+u, ctrl+w, ctrl+d
10
+ * - 단어 삭제: alt+d, ctrl+backspace
11
+ * - 클리어: ctrl+c
12
+ * - 입력: 일반 텍스트, enter, newline(shift+enter)
13
+ * - undo/redo: ctrl+-, ctrl+z (win32), ctrl+.
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: input editing", () => {
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-input-report.txt")
37
+ await driver.kill()
38
+ await disposeAllInstances()
39
+ Flag.SAEEOL_EXPERIMENTAL_HTTPAPI = true
40
+ })
41
+
42
+ // ── 기본 입력 ──
43
+
44
+ test("typing: regular text input", async () => {
45
+ driver.write("hello world this is a test")
46
+ await driver.wait(500)
47
+ driver.snapshot("input-typed")
48
+ expect(driver.errors()).toHaveLength(0)
49
+ })
50
+
51
+ // ── 커서 이동 ──
52
+
53
+ test("cursor: left/right arrow", async () => {
54
+ driver.write(Key.left)
55
+ await driver.wait(100)
56
+ driver.write(Key.left)
57
+ await driver.wait(100)
58
+ driver.write(Key.right)
59
+ await driver.wait(100)
60
+ driver.snapshot("input-cursor-lr")
61
+ expect(driver.errors()).toHaveLength(0)
62
+ })
63
+
64
+ test("cursor: up/down arrow", async () => {
65
+ driver.write(Key.up)
66
+ await driver.wait(100)
67
+ driver.write(Key.down)
68
+ await driver.wait(100)
69
+ driver.snapshot("input-cursor-ud")
70
+ expect(driver.errors()).toHaveLength(0)
71
+ })
72
+
73
+ test("cursor: home/end", async () => {
74
+ driver.write(Key.home)
75
+ await driver.wait(100)
76
+ driver.write(Key.end)
77
+ await driver.wait(100)
78
+ driver.snapshot("input-cursor-home-end")
79
+ expect(driver.errors()).toHaveLength(0)
80
+ })
81
+
82
+ test("cursor: ctrl+a (line home)", async () => {
83
+ driver.write(Key.ctrlA)
84
+ await driver.wait(200)
85
+ driver.snapshot("input-ctrl-a")
86
+ expect(driver.errors()).toHaveLength(0)
87
+ })
88
+
89
+ test("cursor: ctrl+e (line end)", async () => {
90
+ driver.write(Key.ctrlE)
91
+ await driver.wait(200)
92
+ driver.snapshot("input-ctrl-e")
93
+ expect(driver.errors()).toHaveLength(0)
94
+ })
95
+
96
+ test("cursor: ctrl+b (left)", async () => {
97
+ driver.write(Key.ctrlB)
98
+ await driver.wait(100)
99
+ driver.write(Key.ctrlB)
100
+ await driver.wait(100)
101
+ driver.snapshot("input-ctrl-b")
102
+ expect(driver.errors()).toHaveLength(0)
103
+ })
104
+
105
+ test("cursor: ctrl+f (right)", async () => {
106
+ driver.write(Key.ctrlF)
107
+ await driver.wait(100)
108
+ driver.write(Key.ctrlF)
109
+ await driver.wait(100)
110
+ driver.snapshot("input-ctrl-f")
111
+ expect(driver.errors()).toHaveLength(0)
112
+ })
113
+
114
+ // ── 단어 이동 ──
115
+
116
+ test("word: alt+f (forward)", async () => {
117
+ driver.write(Key.altF)
118
+ await driver.wait(200)
119
+ driver.snapshot("input-alt-f")
120
+ expect(driver.errors()).toHaveLength(0)
121
+ })
122
+
123
+ test("word: alt+b (backward)", async () => {
124
+ driver.write(Key.altB)
125
+ await driver.wait(200)
126
+ driver.snapshot("input-alt-b")
127
+ expect(driver.errors()).toHaveLength(0)
128
+ })
129
+
130
+ test("word: ctrl+left", async () => {
131
+ driver.write(Key.ctrlLeft)
132
+ await driver.wait(200)
133
+ driver.snapshot("input-ctrl-left")
134
+ expect(driver.errors()).toHaveLength(0)
135
+ })
136
+
137
+ test("word: ctrl+right", async () => {
138
+ driver.write(Key.ctrlRight)
139
+ await driver.wait(200)
140
+ driver.snapshot("input-ctrl-right")
141
+ expect(driver.errors()).toHaveLength(0)
142
+ })
143
+
144
+ // ── 선택 ──
145
+
146
+ test("select: shift+left/right", async () => {
147
+ driver.write(Key.shiftLeft)
148
+ await driver.wait(100)
149
+ driver.write(Key.shiftLeft)
150
+ await driver.wait(100)
151
+ driver.write(Key.shiftRight)
152
+ await driver.wait(100)
153
+ driver.snapshot("input-select-lr")
154
+ expect(driver.errors()).toHaveLength(0)
155
+ })
156
+
157
+ test("select: shift+up/down", async () => {
158
+ driver.write(Key.shiftUp)
159
+ await driver.wait(100)
160
+ driver.write(Key.shiftDown)
161
+ await driver.wait(100)
162
+ driver.snapshot("input-select-ud")
163
+ expect(driver.errors()).toHaveLength(0)
164
+ })
165
+
166
+ test("select: alt+a (visual line home)", async () => {
167
+ driver.write(Key.altA)
168
+ await driver.wait(200)
169
+ driver.snapshot("input-alt-a")
170
+ expect(driver.errors()).toHaveLength(0)
171
+ })
172
+
173
+ test("select: alt+e (visual line end)", async () => {
174
+ driver.write(Key.altE)
175
+ await driver.wait(200)
176
+ driver.snapshot("input-alt-e")
177
+ expect(driver.errors()).toHaveLength(0)
178
+ })
179
+
180
+ // ── 삭제 ──
181
+
182
+ test("delete: backspace", async () => {
183
+ driver.write(Key.backspace)
184
+ await driver.wait(200)
185
+ driver.snapshot("input-backspace")
186
+ expect(driver.errors()).toHaveLength(0)
187
+ })
188
+
189
+ test("delete: delete key", async () => {
190
+ driver.write(Key.delete)
191
+ await driver.wait(200)
192
+ driver.snapshot("input-delete")
193
+ expect(driver.errors()).toHaveLength(0)
194
+ })
195
+
196
+ test("delete: ctrl+k (to line end)", async () => {
197
+ driver.write(Key.ctrlK)
198
+ await driver.wait(200)
199
+ driver.snapshot("input-ctrl-k")
200
+ expect(driver.errors()).toHaveLength(0)
201
+ })
202
+
203
+ test("delete: ctrl+u (to line start)", async () => {
204
+ driver.write(Key.ctrlU)
205
+ await driver.wait(200)
206
+ driver.snapshot("input-ctrl-u")
207
+ expect(driver.errors()).toHaveLength(0)
208
+ })
209
+
210
+ test("delete: ctrl+w (word backward)", async () => {
211
+ // 텍스트 다시 입력
212
+ driver.write("another test phrase")
213
+ await driver.wait(200)
214
+ driver.write(Key.ctrlW)
215
+ await driver.wait(200)
216
+ driver.snapshot("input-ctrl-w")
217
+ expect(driver.errors()).toHaveLength(0)
218
+ })
219
+
220
+ test("delete: ctrl+d (char delete)", async () => {
221
+ driver.write(Key.ctrlD)
222
+ await driver.wait(200)
223
+ driver.snapshot("input-ctrl-d")
224
+ expect(driver.errors()).toHaveLength(0)
225
+ })
226
+
227
+ test("delete: alt+d (word forward)", async () => {
228
+ driver.write("word1 word2 word3")
229
+ await driver.wait(200)
230
+ driver.write(Key.altD)
231
+ await driver.wait(200)
232
+ driver.snapshot("input-alt-d")
233
+ expect(driver.errors()).toHaveLength(0)
234
+ })
235
+
236
+ // ── 클리어 / 언두 ──
237
+
238
+ test("clear: ctrl+c clears input", async () => {
239
+ driver.write("should be cleared")
240
+ await driver.wait(200)
241
+ driver.write(Key.ctrlC)
242
+ await driver.wait(300)
243
+ driver.snapshot("input-clear-ctrl-c")
244
+ expect(driver.errors()).toHaveLength(0)
245
+ })
246
+
247
+ test("undo: ctrl+-", async () => {
248
+ driver.write("typed text")
249
+ await driver.wait(200)
250
+ // ctrl+- 는 터미널에서 \x1f
251
+ driver.write("\x1f")
252
+ await driver.wait(300)
253
+ driver.snapshot("input-undo-ctrl-minus")
254
+ expect(driver.errors()).toHaveLength(0)
255
+ })
256
+
257
+ test("redo: ctrl+.", async () => {
258
+ driver.write(Key.ctrlV) // ctrl+. — 재할당 불가하므로 대략적
259
+ await driver.wait(200)
260
+ driver.snapshot("input-redo")
261
+ expect(driver.errors()).toHaveLength(0)
262
+ })
263
+
264
+ // ── 새 텍스트 입력 후 엔터 ──
265
+
266
+ test("submit: enter key (does not crash)", async () => {
267
+ driver.write(Key.ctrlC) // 클리어
268
+ await driver.wait(200)
269
+ driver.write("test message")
270
+ await driver.wait(200)
271
+ // 엔터 — 실제 전송은 안 됨 (LLM 서버 죽음), 크래시만 안 하면 됨
272
+ driver.write(Key.enter)
273
+ await driver.wait(1000)
274
+ driver.snapshot("input-submit")
275
+ expect(driver.errors()).toHaveLength(0)
276
+ })
277
+
278
+ // ── 정리 ──
279
+
280
+ test("cleanup: clear and verify", async () => {
281
+ driver.write(Key.ctrlC)
282
+ await driver.wait(300)
283
+ driver.snapshot("input-cleanup")
284
+ expect(driver.errors()).toHaveLength(0)
285
+ })
286
+ })
@@ -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
+ })