tenseleyflow/bensch / 388c123

Browse files

extract interactive specs: completion, signals, vi_mode

completion.yaml -> suites/interactive/completion/
signals_jobs.yaml -> suites/interactive/signals/
vi_mode.yaml -> suites/interactive/editing/

Verbatim copies. 1360 lines, ~102 test cases.

Source: fortsh/tests/interactive/test_specs/
Authored by espadonne
SHA
388c123e0f9d4563e6ed744a549f9d5514db9e2e
Parents
ce6dda2
Tree
58d032e

3 changed files

StatusFile+-
A suites/interactive/completion/completion.yaml 417 0
A suites/interactive/editing/vi_mode.yaml 523 0
A suites/interactive/signals/signals_jobs.yaml 420 0
suites/interactive/completion/completion.yamladded
@@ -0,0 +1,417 @@
1
+# Completion Tests for fortsh
2
+# Phase 4: Tab completion for commands, files, variables
3
+
4
+metadata:
5
+  category: "Completion"
6
+  description: "Tests for tab completion functionality"
7
+  phase: 4
8
+
9
+tests:
10
+  # =============================================================
11
+  # COMMAND COMPLETION
12
+  # =============================================================
13
+  - name: "Tab completes common command"
14
+    steps:
15
+      - send: "ech"
16
+      - send_key: "Tab"
17
+      - send: " completed"
18
+      - send_key: "Enter"
19
+    expect_output: "completed"
20
+    match_type: "contains"
21
+
22
+  - name: "Tab completes builtin command"
23
+    steps:
24
+      - send: "exi"
25
+      - send_key: "Tab"
26
+      - send_key: "C-a"
27
+      - send: "echo "
28
+      - send_key: "C-e"
29
+      - send_key: "Enter"
30
+    expect_output: "exit"
31
+    match_type: "contains"
32
+
33
+  - name: "Tab with no match produces no change"
34
+    steps:
35
+      - send: "xyznotacmd"
36
+      - send_key: "Tab"
37
+      - send_key: "C-u"
38
+      - send: "echo nochange"
39
+      - send_key: "Enter"
40
+    expect_output: "nochange"
41
+    match_type: "contains"
42
+
43
+  - name: "Tab completes partial command"
44
+    steps:
45
+      - send: "pw"
46
+      - send_key: "Tab"
47
+      - send_key: "Enter"
48
+    expect_output: "/"
49
+    match_type: "contains"
50
+
51
+  - name: "Double tab shows multiple completions"
52
+    steps:
53
+      - send: "l"
54
+      - send_key: "Tab"
55
+      - send_key: "Tab"
56
+      - wait: 0.3
57
+      - send_key: "C-c"
58
+      - send: "echo shown"
59
+      - send_key: "Enter"
60
+    expect_output: "shown"
61
+    match_type: "contains"
62
+
63
+  # =============================================================
64
+  # FILE/DIRECTORY COMPLETION
65
+  # =============================================================
66
+  - name: "Tab completes filename"
67
+    steps:
68
+      - send_line: "cd /tmp"
69
+      - send_line: "touch testfile123.txt"
70
+      - send: "ls testf"
71
+      - send_key: "Tab"
72
+      - send_key: "Enter"
73
+    expect_output: "testfile123.txt"
74
+    match_type: "contains"
75
+
76
+  - name: "Tab completes directory name"
77
+    steps:
78
+      - send: "cd /u"
79
+      - send_key: "Tab"
80
+      - send: "sr"
81
+      - send_key: "Tab"
82
+      - send_key: "C-u"
83
+      - send: "echo dircomp"
84
+      - send_key: "Enter"
85
+    expect_output: "dircomp"
86
+    match_type: "contains"
87
+
88
+  - name: "Tab adds trailing slash for directories"
89
+    steps:
90
+      - send: "cd /tm"
91
+      - send_key: "Tab"
92
+      - send_key: "C-e"
93
+      - send_key: "Backspace"
94
+      - send_key: "Enter"
95
+    expect_output: ""
96
+    match_type: "contains"
97
+
98
+  - name: "Tab completes path with spaces"
99
+    steps:
100
+      - send_line: "mkdir -p '/tmp/test dir'"
101
+      - send_line: "touch '/tmp/test dir/file.txt'"
102
+      - send: "ls '/tmp/test d"
103
+      - send_key: "Tab"
104
+      - send_key: "Enter"
105
+    expect_output: "file.txt"
106
+    match_type: "contains"
107
+
108
+  - name: "Tab completes hidden files"
109
+    steps:
110
+      - send_line: "touch /tmp/.hiddenfile"
111
+      - send: "ls /tmp/.hidd"
112
+      - send_key: "Tab"
113
+      - send_key: "Enter"
114
+    expect_output: ".hiddenfile"
115
+    match_type: "contains"
116
+
117
+  - name: "Tab completes with tilde expansion"
118
+    steps:
119
+      - send: "cd ~"
120
+      - send_key: "Tab"
121
+      - send_key: "C-u"
122
+      - send: "echo tilde"
123
+      - send_key: "Enter"
124
+    expect_output: "tilde"
125
+    match_type: "contains"
126
+
127
+  - name: "Tab completes relative path"
128
+    steps:
129
+      - send_line: "cd /tmp"
130
+      - send_line: "mkdir -p subdir"
131
+      - send: "cd ./subd"
132
+      - send_key: "Tab"
133
+      - send_key: "C-u"
134
+      - send: "echo relative"
135
+      - send_key: "Enter"
136
+    expect_output: "relative"
137
+    match_type: "contains"
138
+
139
+  - name: "Tab completes parent directory path"
140
+    steps:
141
+      - send_line: "cd /tmp"
142
+      - send: "ls ../"
143
+      - send_key: "Tab"
144
+      - send_key: "C-u"
145
+      - send: "echo parent"
146
+      - send_key: "Enter"
147
+    expect_output: "parent"
148
+    match_type: "contains"
149
+
150
+  # =============================================================
151
+  # VARIABLE COMPLETION
152
+  # =============================================================
153
+  - name: "Tab completes environment variable"
154
+    steps:
155
+      - send: "echo $HOM"
156
+      - send_key: "Tab"
157
+      - send_key: "Enter"
158
+    expect_output: "/"
159
+    match_type: "contains"
160
+
161
+  - name: "Tab completes PATH variable"
162
+    steps:
163
+      - send: "echo $PAT"
164
+      - send_key: "Tab"
165
+      - send_key: "Enter"
166
+    expect_output: "/"
167
+    match_type: "contains"
168
+
169
+  - name: "Tab completes USER variable"
170
+    steps:
171
+      - send: "echo $USE"
172
+      - send_key: "Tab"
173
+      - send_key: "Enter"
174
+    expect_output: ""
175
+    match_type: "contains"
176
+
177
+  - name: "Tab completes variable in braces"
178
+    steps:
179
+      - send: "echo ${HOM"
180
+      - send_key: "Tab"
181
+      - send: "}"
182
+      - send_key: "Enter"
183
+    expect_output: "/"
184
+    match_type: "contains"
185
+
186
+  - name: "Tab completes custom variable"
187
+    env:
188
+      MYVAR: "testvalue"
189
+    steps:
190
+      - send: "echo $MYV"
191
+      - send_key: "Tab"
192
+      - send_key: "Enter"
193
+    expect_output: "testvalue"
194
+    match_type: "contains"
195
+
196
+  # =============================================================
197
+  # MULTIPLE COMPLETIONS
198
+  # =============================================================
199
+  - name: "Tab cycles through multiple file matches"
200
+    steps:
201
+      - send_line: "cd /tmp"
202
+      - send_line: "touch comp1.txt comp2.txt comp3.txt"
203
+      - send: "ls comp"
204
+      - send_key: "Tab"
205
+      # Single tab shows common prefix or menu; skip cycling test due to timing sensitivity
206
+      - send_key: "C-c"
207
+      - send: "echo multicycle"
208
+      - send_key: "Enter"
209
+    expect_output: "multicycle"
210
+    match_type: "contains"
211
+
212
+  - name: "Tab shows common prefix for multiple matches"
213
+    steps:
214
+      - send_line: "cd /tmp"
215
+      - send_line: "touch prefix_a.txt prefix_b.txt"
216
+      - send: "ls pref"
217
+      - send_key: "Tab"
218
+      - send_key: "C-u"
219
+      - send: "echo prefix"
220
+      - send_key: "Enter"
221
+    expect_output: "prefix"
222
+    match_type: "contains"
223
+
224
+  - name: "Tab lists completions when ambiguous"
225
+    steps:
226
+      - send_line: "cd /tmp"
227
+      - send_line: "touch ambig1 ambig2 ambig3"
228
+      - send: "ls amb"
229
+      - send_key: "Tab"
230
+      - send_key: "Tab"
231
+      - wait: 0.3
232
+      - send_key: "C-c"
233
+      - send: "echo listed"
234
+      - send_key: "Enter"
235
+    expect_output: "listed"
236
+    match_type: "contains"
237
+
238
+  # =============================================================
239
+  # COMPLETION EDGE CASES
240
+  # =============================================================
241
+  - name: "Tab at empty prompt"
242
+    steps:
243
+      - send_key: "Tab"
244
+      - send: "echo empty"
245
+      - send_key: "Enter"
246
+    expect_output: "empty"
247
+    match_type: "contains"
248
+
249
+  - name: "Tab with cursor in middle of word"
250
+    steps:
251
+      - send: "echo test"
252
+      - send_key: "C-b"
253
+      - send_key: "C-b"
254
+      - send_key: "Tab"
255
+      - send_key: "C-u"
256
+      - send: "echo middle"
257
+      - send_key: "Enter"
258
+    expect_output: "middle"
259
+    match_type: "contains"
260
+
261
+  - name: "Tab completes after pipe"
262
+    steps:
263
+      - send: "echo hello | ca"
264
+      - send_key: "Tab"
265
+      - send_key: "Enter"
266
+    expect_output: "hello"
267
+    match_type: "contains"
268
+
269
+  - name: "Tab completes after redirect"
270
+    steps:
271
+      - send_line: "cd /tmp"
272
+      - send_line: "touch redir_test.txt"
273
+      - send: "echo test > redir_"
274
+      - send_key: "Tab"
275
+      - send_key: "C-u"
276
+      - send: "echo redir"
277
+      - send_key: "Enter"
278
+    expect_output: "redir"
279
+    match_type: "contains"
280
+
281
+  - name: "Tab completes after semicolon"
282
+    steps:
283
+      - send: "echo first; ech"
284
+      - send_key: "Tab"
285
+      - send: " second"
286
+      - send_key: "Enter"
287
+    expect_output: "second"
288
+    match_type: "contains"
289
+
290
+  - name: "Tab completes after &&"
291
+    steps:
292
+      - send: "true && ech"
293
+      - send_key: "Tab"
294
+      - send: " andand"
295
+      - send_key: "Enter"
296
+    expect_output: "andand"
297
+    match_type: "contains"
298
+
299
+  - name: "Tab completes after ||"
300
+    steps:
301
+      - send: "false || ech"
302
+      - send_key: "Tab"
303
+      - send: " oror"
304
+      - send_key: "Enter"
305
+    expect_output: "oror"
306
+    match_type: "contains"
307
+
308
+  - name: "Tab completes quoted filename"
309
+    steps:
310
+      - send_line: "touch '/tmp/quoted file.txt'"
311
+      - send: "ls '/tmp/quoted f"
312
+      - send_key: "Tab"
313
+      - send_key: "Enter"
314
+    expect_output: "quoted file.txt"
315
+    match_type: "contains"
316
+
317
+  # DISABLED: Complex multi-step test with timing sensitivity
318
+  # - name: "Tab escapes special characters in filename"
319
+  #   steps:
320
+  #     - send_line: "touch '/tmp/special$file.txt'"
321
+  #     - send: "ls /tmp/special"
322
+  #     - send_key: "Tab"
323
+  #     - send_key: "C-u"
324
+  #     - send: "echo escaped"
325
+  #     - send_key: "Enter"
326
+  #   expect_output: "escaped"
327
+  #   match_type: "contains"
328
+
329
+  - name: "Tab completes with glob pattern"
330
+    steps:
331
+      - send_line: "cd /tmp"
332
+      - send_line: "touch glob1.txt glob2.txt"
333
+      - send: "ls glob*.tx"
334
+      - send_key: "Tab"
335
+      - send_key: "C-u"
336
+      - send: "echo glob"
337
+      - send_key: "Enter"
338
+    expect_output: "glob"
339
+    match_type: "contains"
340
+
341
+  - name: "Escape cancels completion menu"
342
+    steps:
343
+      - send: "l"
344
+      - send_key: "Tab"
345
+      - send_key: "Tab"
346
+      - send_key: "Escape"
347
+      - send_key: "C-u"
348
+      - send: "echo escaped"
349
+      - send_key: "Enter"
350
+    expect_output: "escaped"
351
+    match_type: "contains"
352
+
353
+  - name: "Tab completion preserves case"
354
+    steps:
355
+      - send_line: "touch /tmp/CamelCase.txt"
356
+      - send: "ls /tmp/Camel"
357
+      - send_key: "Tab"
358
+      - send_key: "Enter"
359
+    expect_output: "CamelCase.txt"
360
+    match_type: "contains"
361
+
362
+  # =============================================================
363
+  # PROGRAMMATIC COMPLETION
364
+  # =============================================================
365
+  - name: "Tab completes git subcommand"
366
+    steps:
367
+      - send: "git sta"
368
+      - send_key: "Tab"
369
+      - send_key: "C-u"
370
+      - send: "echo git"
371
+      - send_key: "Enter"
372
+    expect_output: "git"
373
+    match_type: "contains"
374
+
375
+  - name: "Tab completes make target"
376
+    steps:
377
+      - send_line: "cd /tmp"
378
+      - send_line: "echo 'all:\\n\\techo done' > Makefile"
379
+      - send: "make a"
380
+      - send_key: "Tab"
381
+      - send_key: "C-u"
382
+      - send: "echo make"
383
+      - send_key: "Enter"
384
+    expect_output: "make"
385
+    match_type: "contains"
386
+
387
+  # =============================================================
388
+  # MENU COMPLETION
389
+  # =============================================================
390
+  - name: "Arrow keys navigate completion menu"
391
+    steps:
392
+      - send_line: "cd /tmp"
393
+      - send_line: "touch menu1 menu2 menu3"
394
+      - send: "ls menu"
395
+      - send_key: "Tab"
396
+      - send_key: "Tab"
397
+      - send_key: "Down"
398
+      - send_key: "Enter"
399
+      - send_key: "C-u"
400
+      - send: "echo menu"
401
+      - send_key: "Enter"
402
+    expect_output: "menu"
403
+    match_type: "contains"
404
+
405
+  - name: "Enter selects from completion menu"
406
+    steps:
407
+      - send_line: "cd /tmp"
408
+      - send_line: "touch select1 select2"
409
+      - send: "ls sele"
410
+      - send_key: "Tab"
411
+      # Skip menu navigation test due to timing sensitivity; verify completion works
412
+      - send_key: "C-c"
413
+      - send: "echo selected"
414
+      - send_key: "Enter"
415
+    expect_output: "selected"
416
+    match_type: "contains"
417
+
suites/interactive/editing/vi_mode.yamladded
@@ -0,0 +1,523 @@
1
+# Vi Mode Tests for fortsh
2
+# Tests vi-style line editing commands
3
+
4
+metadata:
5
+  category: "Vi Mode"
6
+  description: "Tests for vi-style command line editing"
7
+  phase: 3
8
+
9
+tests:
10
+  # =============================================================
11
+  # MODE SWITCHING
12
+  # =============================================================
13
+  - name: "Enable vi mode with set -o vi"
14
+    steps:
15
+      - send_line: "set -o vi"
16
+      - send_line: "echo vi_enabled"
17
+    expect_output: "vi_enabled"
18
+    match_type: "contains"
19
+
20
+  - name: "Escape enters command mode"
21
+    steps:
22
+      - send_line: "set -o vi"
23
+      - send: "echo hello"
24
+      - send_key: "Escape"
25
+      - wait: 0.7
26
+      - send: "0"
27
+      - wait: 0.2
28
+      - send: "d"
29
+      - wait: 0.2
30
+      - send: "w"
31
+      - wait: 0.2
32
+      - send: "i"
33
+      - wait: 0.2
34
+      - send: "echo goodbye"
35
+      - send_key: "Enter"
36
+    expect_output: "goodbye"
37
+    match_type: "contains"
38
+
39
+  # =============================================================
40
+  # MOVEMENT COMMANDS (in command mode)
41
+  # =============================================================
42
+  - name: "h moves cursor left"
43
+    steps:
44
+      - send_line: "set -o vi"
45
+      - send: "echo ABCD"
46
+      - send_key: "Escape"
47
+      - wait: 0.7
48
+      - send: "h"
49
+      - wait: 0.2
50
+      - send: "i"
51
+      - wait: 0.2
52
+      - send: "X"
53
+      - send_key: "Escape"
54
+      - wait: 0.2
55
+      - send: "0"
56
+      - wait: 0.2
57
+      - send: "i"
58
+      - wait: 0.2
59
+      - send_key: "Enter"
60
+    expect_output: "ABXCD"
61
+    match_type: "contains"
62
+
63
+  - name: "l moves cursor right"
64
+    steps:
65
+      - send_line: "set -o vi"
66
+      - send: "echo ABCD"
67
+      - send_key: "Escape"
68
+      - wait: 0.7
69
+      - send: "0"
70
+      - wait: 0.2
71
+      - send: "w"
72
+      - wait: 0.2
73
+      - send: "l"
74
+      - wait: 0.2
75
+      - send: "i"
76
+      - wait: 0.2
77
+      - send: "X"
78
+      - send_key: "Escape"
79
+      - wait: 0.2
80
+      - send: "0"
81
+      - wait: 0.2
82
+      - send: "i"
83
+      - wait: 0.2
84
+      - send_key: "Enter"
85
+    expect_output: "AXBCD"
86
+    match_type: "contains"
87
+
88
+  - name: "0 moves to beginning of line"
89
+    steps:
90
+      - send_line: "set -o vi"
91
+      - send: "echo test"
92
+      - send_key: "Escape"
93
+      - wait: 0.7
94
+      - send: "0"
95
+      - wait: 0.2
96
+      - send: "d"
97
+      - wait: 0.2
98
+      - send: "w"
99
+      - wait: 0.2
100
+      - send: "i"
101
+      - wait: 0.2
102
+      - send: "printf '"
103
+      - send_key: "Escape"
104
+      - wait: 0.2
105
+      - send: "A"
106
+      - wait: 0.2
107
+      - send: "'"
108
+      - send_key: "Enter"
109
+    expect_output: "test"
110
+    match_type: "contains"
111
+
112
+  - name: "$ moves to end of line"
113
+    steps:
114
+      - send_line: "set -o vi"
115
+      - send: "echo test"
116
+      - send_key: "Escape"
117
+      - wait: 0.7
118
+      - send: "0"
119
+      - wait: 0.2
120
+      - send: "$"
121
+      - wait: 0.2
122
+      - send: "a"
123
+      - wait: 0.2
124
+      - send: "X"
125
+      - send_key: "Enter"
126
+    expect_output: "echo testX"
127
+    match_type: "contains"
128
+
129
+  - name: "w moves forward one word"
130
+    steps:
131
+      - send_line: "set -o vi"
132
+      - send: "echo ABC DEF"
133
+      - send_key: "Escape"
134
+      - wait: 0.7
135
+      - send: "0"
136
+      - wait: 0.2
137
+      - send: "w"
138
+      - wait: 0.2
139
+      - send: "w"
140
+      - wait: 0.2
141
+      - send: "i"
142
+      - wait: 0.2
143
+      - send: "X"
144
+      - send_key: "Escape"
145
+      - wait: 0.2
146
+      - send: "0"
147
+      - wait: 0.2
148
+      - send: "i"
149
+      - wait: 0.2
150
+      - send_key: "Enter"
151
+    expect_output: "ABC XDEF"
152
+    match_type: "contains"
153
+
154
+  - name: "b moves backward one word"
155
+    steps:
156
+      - send_line: "set -o vi"
157
+      - send: "echo ABC DEF"
158
+      - send_key: "Escape"
159
+      - wait: 0.7
160
+      - send: "b"
161
+      - wait: 0.2
162
+      - send: "i"
163
+      - wait: 0.2
164
+      - send: "X"
165
+      - send_key: "Escape"
166
+      - wait: 0.2
167
+      - send: "0"
168
+      - wait: 0.2
169
+      - send: "i"
170
+      - wait: 0.2
171
+      - send_key: "Enter"
172
+    expect_output: "ABC XDEF"
173
+    match_type: "contains"
174
+
175
+  - name: "e moves to end of word"
176
+    steps:
177
+      - send_line: "set -o vi"
178
+      - send: "echo ABC DEF"
179
+      - send_key: "Escape"
180
+      - wait: 0.7
181
+      - send: "0"
182
+      - wait: 0.2
183
+      - send: "w"
184
+      - wait: 0.2
185
+      - send: "e"
186
+      - wait: 0.2
187
+      - send: "a"
188
+      - wait: 0.2
189
+      - send: "X"
190
+      - send_key: "Escape"
191
+      - wait: 0.2
192
+      - send: "0"
193
+      - wait: 0.2
194
+      - send: "i"
195
+      - wait: 0.2
196
+      - send_key: "Enter"
197
+    expect_output: "ABCX DEF"
198
+    match_type: "contains"
199
+
200
+  # =============================================================
201
+  # INSERT MODE COMMANDS
202
+  # =============================================================
203
+  - name: "i enters insert mode before cursor"
204
+    steps:
205
+      - send_line: "set -o vi"
206
+      - send: "echo ABCD"
207
+      - send_key: "Escape"
208
+      - wait: 0.7
209
+      - send: "0"
210
+      - wait: 0.2
211
+      - send: "w"
212
+      - wait: 0.2
213
+      - send: "i"
214
+      - wait: 0.2
215
+      - send: "X"
216
+      - send_key: "Escape"
217
+      - wait: 0.2
218
+      - send: "0"
219
+      - wait: 0.2
220
+      - send: "i"
221
+      - wait: 0.2
222
+      - send_key: "Enter"
223
+    expect_output: "XABCD"
224
+    match_type: "contains"
225
+
226
+  - name: "a enters insert mode after cursor"
227
+    steps:
228
+      - send_line: "set -o vi"
229
+      - send: "echo ABCD"
230
+      - send_key: "Escape"
231
+      - wait: 0.7
232
+      - send: "0"
233
+      - wait: 0.2
234
+      - send: "w"
235
+      - wait: 0.2
236
+      - send: "a"
237
+      - wait: 0.2
238
+      - send: "X"
239
+      - send_key: "Escape"
240
+      - wait: 0.2
241
+      - send: "0"
242
+      - wait: 0.2
243
+      - send: "i"
244
+      - wait: 0.2
245
+      - send_key: "Enter"
246
+    expect_output: "AXBCD"
247
+    match_type: "contains"
248
+
249
+  - name: "I enters insert mode at beginning"
250
+    steps:
251
+      - send_line: "set -o vi"
252
+      - send: "cho test"
253
+      - send_key: "Escape"
254
+      - wait: 0.7
255
+      - send: "I"
256
+      - wait: 0.2
257
+      - send: "e"
258
+      - send_key: "Enter"
259
+    expect_output: "test"
260
+    match_type: "contains"
261
+
262
+  - name: "A enters insert mode at end"
263
+    steps:
264
+      - send_line: "set -o vi"
265
+      - send: "echo test"
266
+      - send_key: "Escape"
267
+      - wait: 0.5
268
+      - send: "A"
269
+      - wait: 0.1
270
+      - send: "X"
271
+      - send_key: "Enter"
272
+    expect_output: "echo testX"
273
+    match_type: "contains"
274
+
275
+  # =============================================================
276
+  # DELETION COMMANDS
277
+  # =============================================================
278
+  - name: "x deletes character under cursor"
279
+    steps:
280
+      - send_line: "set -o vi"
281
+      - send: "echo test"
282
+      - send_key: "Escape"
283
+      - wait: 0.7
284
+      - send: "0"
285
+      - wait: 0.2
286
+      - send: "x"
287
+      - wait: 0.2
288
+      - send: "i"
289
+      - send_key: "Enter"
290
+    expect_output: "cho test"
291
+    match_type: "contains"
292
+
293
+  - name: "X deletes character before cursor"
294
+    steps:
295
+      - send_line: "set -o vi"
296
+      - send: "echo test"
297
+      - send_key: "Escape"
298
+      - wait: 0.5
299
+      - send: "X"
300
+      - wait: 0.1
301
+      - send: "a"
302
+      - send_key: "Enter"
303
+    expect_output: "echo tes"
304
+    match_type: "contains"
305
+
306
+  - name: "dw deletes word"
307
+    steps:
308
+      - send_line: "set -o vi"
309
+      - send: "echo hello world"
310
+      - send_key: "Escape"
311
+      - wait: 0.7
312
+      - send: "0"
313
+      - wait: 0.2
314
+      - send: "d"
315
+      - wait: 0.2
316
+      - send: "w"
317
+      - wait: 0.2
318
+      - send: "i"
319
+      - send_key: "Enter"
320
+    expect_output: "hello world"
321
+    match_type: "contains"
322
+
323
+  - name: "dd clears entire line"
324
+    steps:
325
+      - send_line: "set -o vi"
326
+      - send: "echo test"
327
+      - send_key: "Escape"
328
+      - wait: 0.7
329
+      - send: "d"
330
+      - wait: 0.2
331
+      - send: "d"
332
+      - wait: 0.2
333
+      - send: "i"
334
+      - wait: 0.2
335
+      - send: "echo cleared"
336
+      - send_key: "Enter"
337
+    expect_output: "cleared"
338
+    match_type: "contains"
339
+
340
+  - name: "D deletes to end of line"
341
+    steps:
342
+      - send_line: "set -o vi"
343
+      - send: "echo hello world"
344
+      - send_key: "Escape"
345
+      - wait: 0.7
346
+      - send: "0"
347
+      - wait: 0.2
348
+      - send: "w"
349
+      - wait: 0.2
350
+      - send: "D"
351
+      - wait: 0.2
352
+      - send: "a"
353
+      - send_key: "Enter"
354
+    expect_output: "echo"
355
+    match_type: "contains"
356
+
357
+  # =============================================================
358
+  # CHANGE COMMANDS
359
+  # =============================================================
360
+  - name: "cw changes word"
361
+    steps:
362
+      - send_line: "set -o vi"
363
+      - send: "echo hello world"
364
+      - send_key: "Escape"
365
+      - wait: 0.7
366
+      - send: "0"
367
+      - wait: 0.2
368
+      - send: "w"
369
+      - wait: 0.2
370
+      - send: "c"
371
+      - wait: 0.2
372
+      - send: "w"
373
+      - wait: 0.2
374
+      - send: "goodbye"
375
+      - send_key: "Enter"
376
+    expect_output: "goodbye world"
377
+    match_type: "contains"
378
+
379
+  - name: "cc changes entire line"
380
+    steps:
381
+      - send_line: "set -o vi"
382
+      - send: "old command"
383
+      - send_key: "Escape"
384
+      - wait: 0.7
385
+      - send: "c"
386
+      - wait: 0.2
387
+      - send: "c"
388
+      - wait: 0.2
389
+      - send: "echo new"
390
+      - send_key: "Enter"
391
+    expect_output: "new"
392
+    match_type: "contains"
393
+
394
+  - name: "C changes to end of line"
395
+    steps:
396
+      - send_line: "set -o vi"
397
+      - send: "echo hello world"
398
+      - send_key: "Escape"
399
+      - wait: 0.7
400
+      - send: "0"
401
+      - wait: 0.2
402
+      - send: "w"
403
+      - wait: 0.2
404
+      - send: "C"
405
+      - wait: 0.2
406
+      - send: "changed"
407
+      - send_key: "Enter"
408
+    expect_output: "changed"
409
+    match_type: "contains"
410
+
411
+  # =============================================================
412
+  # HISTORY NAVIGATION (in command mode)
413
+  # =============================================================
414
+  - name: "k moves to previous history"
415
+    steps:
416
+      - send_line: "set -o vi"
417
+      - send_line: "echo first"
418
+      - send_line: "echo second"
419
+      - send_key: "Escape"
420
+      - wait: 0.5
421
+      - send: "k"
422
+      - send_key: "Enter"
423
+    expect_output: "second"
424
+    match_type: "contains"
425
+
426
+  - name: "j moves to next history"
427
+    steps:
428
+      - send_line: "set -o vi"
429
+      - send_line: "echo first"
430
+      - send_line: "echo second"
431
+      - send_key: "Escape"
432
+      - wait: 0.7
433
+      - send: "k"
434
+      - wait: 0.2
435
+      - send: "k"
436
+      - wait: 0.2
437
+      - send: "j"
438
+      - send_key: "Enter"
439
+    expect_output: "second"
440
+    match_type: "contains"
441
+
442
+  # =============================================================
443
+  # UNDO
444
+  # =============================================================
445
+  - name: "u undoes last change"
446
+    steps:
447
+      - send_line: "set -o vi"
448
+      - send: "echo test"
449
+      - send_key: "Escape"
450
+      - wait: 0.7
451
+      - send: "0"
452
+      - wait: 0.2
453
+      - send: "d"
454
+      - wait: 0.2
455
+      - send: "w"
456
+      - wait: 0.2
457
+      - send: "u"
458
+      - wait: 0.2
459
+      - send: "i"
460
+      - send_key: "Enter"
461
+    expect_output: "test"
462
+    match_type: "contains"
463
+
464
+  # =============================================================
465
+  # REPEAT AND COUNT
466
+  # =============================================================
467
+  - name: "2x deletes two characters"
468
+    steps:
469
+      - send_line: "set -o vi"
470
+      - send: "echo abcd"
471
+      - send_key: "Escape"
472
+      - wait: 0.7
473
+      - send: "0"
474
+      - wait: 0.2
475
+      - send: "2"
476
+      - wait: 0.2
477
+      - send: "x"
478
+      - wait: 0.2
479
+      - send: "i"
480
+      - send_key: "Enter"
481
+    expect_output: "ho abcd"
482
+    match_type: "contains"
483
+
484
+  - name: "3l moves 3 characters right"
485
+    steps:
486
+      - send_line: "set -o vi"
487
+      - send: "echo ABCDEF"
488
+      - send_key: "Escape"
489
+      - wait: 0.7
490
+      - send: "0"
491
+      - wait: 0.2
492
+      - send: "w"
493
+      - wait: 0.2
494
+      - send: "3"
495
+      - wait: 0.2
496
+      - send: "l"
497
+      - wait: 0.2
498
+      - send: "i"
499
+      - wait: 0.2
500
+      - send: "X"
501
+      - send_key: "Escape"
502
+      - wait: 0.2
503
+      - send: "0"
504
+      - wait: 0.2
505
+      - send: "i"
506
+      - wait: 0.2
507
+      - send_key: "Enter"
508
+    expect_output: "ABCXDEF"
509
+    match_type: "contains"
510
+
511
+  # =============================================================
512
+  # RETURN TO EMACS MODE
513
+  # =============================================================
514
+  - name: "set -o emacs returns to emacs mode"
515
+    steps:
516
+      - send_line: "set -o vi"
517
+      - send_line: "set -o emacs"
518
+      - send: "cho hello"
519
+      - send_key: "C-a"
520
+      - send: "e"
521
+      - send_key: "Enter"
522
+    expect_output: "hello"
523
+    match_type: "contains"
suites/interactive/signals/signals_jobs.yamladded
@@ -0,0 +1,420 @@
1
+# Signals and Job Control Tests for fortsh
2
+# Phase 5: Signal handling and job management
3
+
4
+metadata:
5
+  category: "Signals and Job Control"
6
+  description: "Tests for signal handling and job control functionality"
7
+  phase: 5
8
+
9
+tests:
10
+  # =============================================================
11
+  # CTRL+C - INTERRUPT
12
+  # =============================================================
13
+  - name: "Ctrl+C interrupts running command"
14
+    steps:
15
+      - send_line: "sleep 10"
16
+      - wait: 0.3
17
+      - send_key: "C-c"
18
+      - send: "echo interrupted"
19
+      - send_key: "Enter"
20
+    expect_output: "interrupted"
21
+    match_type: "contains"
22
+
23
+  - name: "Ctrl+C clears current input"
24
+    steps:
25
+      - send: "this will be cleared"
26
+      - send_key: "C-c"
27
+      - send: "echo cleared"
28
+      - send_key: "Enter"
29
+    expect_output: "cleared"
30
+    match_type: "contains"
31
+
32
+  - name: "Ctrl+C during pipe"
33
+    steps:
34
+      - send_line: "sleep 10 | cat"
35
+      - wait: 0.3
36
+      - send_key: "C-c"
37
+      - send: "echo pipeint"
38
+      - send_key: "Enter"
39
+    expect_output: "pipeint"
40
+    match_type: "contains"
41
+
42
+  - name: "Multiple Ctrl+C"
43
+    steps:
44
+      - send: "partial"
45
+      - send_key: "C-c"
46
+      - send: "another"
47
+      - send_key: "C-c"
48
+      - send: "echo final"
49
+      - send_key: "Enter"
50
+    expect_output: "final"
51
+    match_type: "contains"
52
+
53
+  - name: "Ctrl+C preserves exit status"
54
+    steps:
55
+      - send_line: "sleep 10"
56
+      - wait: 0.3
57
+      - send_key: "C-c"
58
+      - send_line: "echo $?"
59
+    expect_output: "130"
60
+    match_type: "contains"
61
+
62
+  # =============================================================
63
+  # CTRL+D - EOF
64
+  # =============================================================
65
+  - name: "Ctrl+D on empty line exits or shows message"
66
+    steps:
67
+      - send: "echo before"
68
+      - send_key: "Enter"
69
+      - send_key: "C-d"
70
+      - wait: 0.3
71
+    expect_output: "before"
72
+    match_type: "contains"
73
+
74
+  - name: "Ctrl+D with partial input does nothing"
75
+    steps:
76
+      - send: "partial"
77
+      - send_key: "C-d"
78
+      - send_key: "C-u"
79
+      - send: "echo partial"
80
+      - send_key: "Enter"
81
+    expect_output: "partial"
82
+    match_type: "contains"
83
+
84
+  - name: "Ctrl+D sends EOF to cat"
85
+    steps:
86
+      - send_line: "cat"
87
+      - send: "hello"
88
+      - send_key: "Enter"
89
+      - send_key: "C-d"
90
+    expect_output: "hello"
91
+    match_type: "contains"
92
+
93
+  # =============================================================
94
+  # CTRL+Z - SUSPEND
95
+  # =============================================================
96
+  - name: "Ctrl+Z suspends foreground job"
97
+    steps:
98
+      - send_line: "sleep 10"
99
+      - wait: 0.3
100
+      - send_key: "C-z"
101
+      - send: "echo suspended"
102
+      - send_key: "Enter"
103
+    expect_output: "suspended"
104
+    match_type: "contains"
105
+
106
+  - name: "Ctrl+Z shows stopped message"
107
+    steps:
108
+      - send_line: "sleep 10"
109
+      - wait: 0.3
110
+      - send_key: "C-z"
111
+    expect_output: "Stopped"
112
+    match_type: "contains"
113
+
114
+  - name: "Suspended job appears in jobs list"
115
+    steps:
116
+      - send_line: "sleep 10"
117
+      - wait: 0.3
118
+      - send_key: "C-z"
119
+      - send_line: "jobs"
120
+    expect_output: "sleep"
121
+    match_type: "contains"
122
+
123
+  # =============================================================
124
+  # BACKGROUND JOBS
125
+  # =============================================================
126
+  - name: "Run command in background with &"
127
+    steps:
128
+      - send_line: "sleep 1 &"
129
+      - send: "echo background"
130
+      - send_key: "Enter"
131
+    expect_output: "background"
132
+    match_type: "contains"
133
+
134
+  - name: "Background job shows job number"
135
+    steps:
136
+      - send_line: "sleep 5 &"
137
+    expect_output: "\\["
138
+    match_type: "contains"
139
+
140
+  - name: "Multiple background jobs"
141
+    steps:
142
+      - send_line: "sleep 10 &"
143
+      - send_line: "sleep 10 &"
144
+      - send_line: "jobs"
145
+    expect_output: "sleep"
146
+    match_type: "contains"
147
+
148
+  - name: "Background job runs without blocking"
149
+    steps:
150
+      - send_line: "sleep 1 &"
151
+      - send: "echo notblocked"
152
+      - send_key: "Enter"
153
+    expect_output: "notblocked"
154
+    match_type: "contains"
155
+
156
+  # =============================================================
157
+  # JOB CONTROL - jobs BUILTIN
158
+  # =============================================================
159
+  - name: "jobs shows running jobs"
160
+    steps:
161
+      - send_line: "sleep 10 &"
162
+      - send_line: "jobs"
163
+    expect_output: "Running"
164
+    match_type: "contains"
165
+
166
+  - name: "jobs shows stopped jobs"
167
+    steps:
168
+      - send_line: "sleep 10"
169
+      - wait: 0.3
170
+      - send_key: "C-z"
171
+      - send_line: "jobs"
172
+    expect_output: "Stopped"
173
+    match_type: "contains"
174
+
175
+  - name: "jobs -l shows PIDs"
176
+    steps:
177
+      - send_line: "sleep 10 &"
178
+      - send_line: "jobs -l"
179
+    expect_output: ""
180
+    match_type: "contains"
181
+
182
+  - name: "jobs empty when no jobs"
183
+    steps:
184
+      - send_line: "jobs"
185
+      - send: "echo empty"
186
+      - send_key: "Enter"
187
+    expect_output: "empty"
188
+    match_type: "contains"
189
+
190
+  # =============================================================
191
+  # JOB CONTROL - fg BUILTIN
192
+  # =============================================================
193
+  - name: "fg resumes stopped job"
194
+    steps:
195
+      - send_line: "sleep 10"
196
+      - wait: 0.3
197
+      - send_key: "C-z"
198
+      - send_line: "fg"
199
+      - wait: 0.3
200
+      - send_key: "C-c"
201
+      - send: "echo resumed"
202
+      - send_key: "Enter"
203
+    expect_output: "resumed"
204
+    match_type: "contains"
205
+
206
+  - name: "fg brings background job to foreground"
207
+    steps:
208
+      - send_line: "sleep 10 &"
209
+      - send_line: "fg"
210
+      - wait: 0.3
211
+      - send_key: "C-c"
212
+      - send: "echo foreground"
213
+      - send_key: "Enter"
214
+    expect_output: "foreground"
215
+    match_type: "contains"
216
+
217
+  - name: "fg with job number"
218
+    steps:
219
+      - send_line: "sleep 10 &"
220
+      - send_line: "sleep 10 &"
221
+      - send_line: "fg %1"
222
+      - wait: 0.3
223
+      - send_key: "C-c"
224
+      - send: "echo job1"
225
+      - send_key: "Enter"
226
+    expect_output: "job1"
227
+    match_type: "contains"
228
+
229
+  - name: "fg with no jobs shows error"
230
+    fresh_session: true
231
+    steps:
232
+      - send_line: "fg"
233
+    expect_output: "no"
234
+    match_type: "contains"
235
+
236
+  # =============================================================
237
+  # JOB CONTROL - bg BUILTIN
238
+  # =============================================================
239
+  - name: "bg resumes stopped job in background"
240
+    steps:
241
+      - send_line: "sleep 10"
242
+      - wait: 0.3
243
+      - send_key: "C-z"
244
+      - send_line: "bg"
245
+      - send_line: "jobs"
246
+    expect_output: "Running"
247
+    match_type: "contains"
248
+
249
+  - name: "bg with job number"
250
+    steps:
251
+      - send_line: "sleep 10"
252
+      - wait: 0.3
253
+      - send_key: "C-z"
254
+      - send_line: "bg %1"
255
+      - send_line: "jobs"
256
+    expect_output: "Running"
257
+    match_type: "contains"
258
+
259
+  - name: "bg with no stopped jobs shows error"
260
+    fresh_session: true
261
+    steps:
262
+      - send_line: "bg"
263
+    expect_output: "no"
264
+    match_type: "contains"
265
+
266
+  # =============================================================
267
+  # JOB CONTROL - PERCENT NOTATION
268
+  # =============================================================
269
+  - name: "%n refers to job number"
270
+    steps:
271
+      - send_line: "sleep 10 &"
272
+      - send_line: "fg %1"
273
+      - wait: 0.3
274
+      - send_key: "C-c"
275
+      - send: "echo percent"
276
+      - send_key: "Enter"
277
+    expect_output: "percent"
278
+    match_type: "contains"
279
+
280
+  - name: "%% refers to current job"
281
+    steps:
282
+      - send_line: "sleep 10 &"
283
+      - send_line: "fg %%"
284
+      - wait: 0.3
285
+      - send_key: "C-c"
286
+      - send: "echo current"
287
+      - send_key: "Enter"
288
+    expect_output: "current"
289
+    match_type: "contains"
290
+
291
+  - name: "%+ refers to current job"
292
+    steps:
293
+      - send_line: "sleep 10 &"
294
+      - send_line: "fg %+"
295
+      - wait: 0.3
296
+      - send_key: "C-c"
297
+      - send: "echo plus"
298
+      - send_key: "Enter"
299
+    expect_output: "plus"
300
+    match_type: "contains"
301
+
302
+  - name: "kill %n kills job"
303
+    steps:
304
+      - send_line: "sleep 10 &"
305
+      - send_line: "kill %1"
306
+      - wait: 0.5
307
+      - send_line: "jobs"
308
+      - send: "echo killed"
309
+      - send_key: "Enter"
310
+    expect_output: "killed"
311
+    match_type: "contains"
312
+
313
+  # =============================================================
314
+  # SIGNAL HANDLING
315
+  # =============================================================
316
+  - name: "SIGTERM terminates process"
317
+    steps:
318
+      - send_line: "sleep 10 &"
319
+      - send_line: "kill %1"
320
+      - wait: 0.5
321
+      - send_line: "jobs"
322
+    expect_output: ""
323
+    match_type: "contains"
324
+
325
+  - name: "SIGKILL force kills process"
326
+    steps:
327
+      - send_line: "sleep 10 &"
328
+      - send_line: "kill -9 %1"
329
+      - wait: 0.5
330
+      - send_line: "jobs"
331
+    expect_output: ""
332
+    match_type: "contains"
333
+
334
+  - name: "SIGCONT continues stopped process"
335
+    steps:
336
+      - send_line: "sleep 10"
337
+      - wait: 0.3
338
+      - send_key: "C-z"
339
+      - send_line: "kill -CONT %1"
340
+      - send_line: "jobs"
341
+    expect_output: "Running"
342
+    match_type: "contains"
343
+
344
+  # =============================================================
345
+  # PROCESS GROUPS
346
+  # =============================================================
347
+  - name: "Pipeline runs in same process group"
348
+    steps:
349
+      - send_line: "sleep 10 | sleep 10 &"
350
+      - send_line: "jobs"
351
+    expect_output: "sleep"
352
+    match_type: "contains"
353
+
354
+  # NOTE: Test disabled due to test framework timing issues with multi-stage pipelines.
355
+  # The shell correctly kills the entire pipeline on Ctrl+C (verified manually).
356
+  # The "Ctrl+C during pipe" test above covers 2-stage pipelines.
357
+  # - name: "Ctrl+C kills entire pipeline"
358
+  #   steps:
359
+  #     - send_line: "sleep 10 | sleep 10 | sleep 10"
360
+  #     - wait: 0.3
361
+  #     - send_key: "C-c"
362
+  #     - send: "echo pipeall"
363
+  #     - send_key: "Enter"
364
+  #   expect_output: "pipeall"
365
+  #   match_type: "contains"
366
+
367
+  # =============================================================
368
+  # EDGE CASES
369
+  # =============================================================
370
+  - name: "Disown removes job from table"
371
+    fresh_session: true
372
+    steps:
373
+      - send_line: "sleep 10 &"
374
+      - wait: 0.2
375
+      - send_line: "disown"
376
+      - wait: 0.2
377
+      - send_line: "jobs"
378
+      - wait: 0.2
379
+      - send: "echo disowned"
380
+      - send_key: "Enter"
381
+    expect_output: "disowned"
382
+    match_type: "contains"
383
+
384
+  - name: "Wait builtin exists"
385
+    steps:
386
+      - send_line: "true &"
387
+      - send_line: "wait; echo waited"
388
+    expect_output: "waited"
389
+    match_type: "contains"
390
+
391
+  - name: "Wait for specific job number"
392
+    fresh_session: true
393
+    steps:
394
+      - send_line: "true &"
395
+      - wait: 0.2
396
+      - send_line: "wait %1; echo waited"
397
+    expect_output: "waited"
398
+    match_type: "contains"
399
+
400
+  - name: "Nested command substitution with signals"
401
+    fresh_session: true
402
+    steps:
403
+      - send_line: "echo $(sleep 10)"
404
+      - wait: 0.5
405
+      - send_key: "C-c"
406
+      - wait: 0.3
407
+      - send: "echo nested"
408
+      - send_key: "Enter"
409
+    expect_output: "nested"
410
+    match_type: "contains"
411
+
412
+  - name: "Subshell job control"
413
+    fresh_session: true
414
+    steps:
415
+      - send_line: "(sleep 10) &"
416
+      - wait: 0.2
417
+      - send_line: "jobs"
418
+    expect_output: "Running"
419
+    match_type: "contains"
420
+