@zhushanwen/pi-ask-user 6.0.1 → 7.0.2
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.
- package/ARCHITECTURE.md +68 -49
- package/package.json +5 -4
- package/src/__tests__/answer-codec.test.ts +102 -0
- package/src/__tests__/channel-handler.test.ts +16 -99
- package/src/__tests__/component-keymap.test.ts +2 -30
- package/src/__tests__/component.test.ts +59 -170
- package/src/__tests__/e2e.test.ts +6 -53
- package/src/__tests__/fixtures.ts +0 -15
- package/src/__tests__/index.test.ts +24 -92
- package/src/__tests__/question-view.test.ts +1 -30
- package/src/__tests__/submit-view.test.ts +13 -19
- package/src/__tests__/types.test.ts +0 -1
- package/src/__tests__/validate.test.ts +4 -10
- package/src/__tests__/w2-draft-hint.test.ts +1 -21
- package/src/__tests__/w3-regression.test.ts +3 -31
- package/src/answer-codec.ts +33 -0
- package/src/channel-handler.ts +18 -49
- package/src/component.ts +23 -47
- package/src/index.ts +34 -35
- package/src/question-view.ts +11 -43
- package/src/submit-view.ts +23 -12
- package/src/types.ts +12 -9
- package/src/validate.ts +2 -2
- package/src/__tests__/answer-format.test.ts +0 -275
- package/src/answer-format.ts +0 -127
|
@@ -11,11 +11,9 @@ import {
|
|
|
11
11
|
LEFT,
|
|
12
12
|
mockTui,
|
|
13
13
|
multiQ,
|
|
14
|
-
multiQWithComment,
|
|
15
14
|
RIGHT,
|
|
16
15
|
singleQ,
|
|
17
16
|
singleQMulti,
|
|
18
|
-
singleQWithComment,
|
|
19
17
|
stubTheme,
|
|
20
18
|
TAB,
|
|
21
19
|
UP,
|
|
@@ -44,14 +42,14 @@ describe("AskUserComponent — single question", () => {
|
|
|
44
42
|
c.handleInput(ENTER);
|
|
45
43
|
expect(result.val).not.toBeUndefined();
|
|
46
44
|
expect(result.val!.cancelled).toBe(false);
|
|
47
|
-
expect(result.val!.answers["Which DB?"]).
|
|
45
|
+
expect(result.val!.answers["Which DB?"]).toEqual({ selected: ["Postgres"], other: null });
|
|
48
46
|
});
|
|
49
47
|
|
|
50
48
|
it("C-3: moves cursor down then confirms second option", () => {
|
|
51
49
|
const { c, result } = make([singleQ]);
|
|
52
50
|
c.handleInput(DOWN);
|
|
53
51
|
c.handleInput(ENTER);
|
|
54
|
-
expect(result.val!.answers["Which DB?"]).
|
|
52
|
+
expect(result.val!.answers["Which DB?"]).toEqual({ selected: ["SQLite"], other: null });
|
|
55
53
|
});
|
|
56
54
|
|
|
57
55
|
it("C-4: Esc shows confirm overlay; second Esc cancels (single)", () => {
|
|
@@ -110,9 +108,9 @@ describe("AskUserComponent — multi question tab nav", () => {
|
|
|
110
108
|
c.handleInput(ENTER);
|
|
111
109
|
// Submit tab: Enter
|
|
112
110
|
c.handleInput(ENTER);
|
|
113
|
-
expect(result.val!.answers["Q1"]).
|
|
114
|
-
expect(result.val!.answers["Q2"]).
|
|
115
|
-
expect(result.val!.answers["Q3"]).
|
|
111
|
+
expect(result.val!.answers["Q1"]).toEqual({ selected: ["A"], other: null });
|
|
112
|
+
expect(result.val!.answers["Q2"]).toEqual({ selected: ["X"], other: null });
|
|
113
|
+
expect(result.val!.answers["Q3"]).toEqual({ selected: ["M"], other: null });
|
|
116
114
|
});
|
|
117
115
|
|
|
118
116
|
it("C-9: Submit tab blocks when not all confirmed", () => {
|
|
@@ -140,22 +138,31 @@ describe("AskUserComponent — multi question tab nav", () => {
|
|
|
140
138
|
c.handleInput(ENTER); // Q2 → Q3
|
|
141
139
|
c.handleInput(ENTER); // Q3 → Submit
|
|
142
140
|
c.handleInput(ENTER); // Submit
|
|
143
|
-
expect(result.val!.answers["Q1"]).
|
|
141
|
+
expect(result.val!.answers["Q1"]).toEqual({ selected: ["B"], other: null });
|
|
144
142
|
});
|
|
145
143
|
|
|
146
|
-
it("C-15: leaving multi-select tab auto-confirms answered selection", () => {
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
144
|
+
it("C-15/C-S3: leaving multi-select tab auto-confirms answered selection (Submit 直达 + 中间 tab)", () => {
|
|
145
|
+
// C-15 与 C-S3 合并(TC-04):两用例同走 handleOptionsInput ←/→ → gotoTab →
|
|
146
|
+
// autoConfirmIfAnswered()(component.ts:420-421 同一分支),并入后保留双方独有断言:
|
|
147
|
+
// - C-15:multi-select toggle 后 → 直达 Submit tab → Enter 提交成功(all confirmed)
|
|
148
|
+
// - C-S3:multi-select toggle 后 → 切到中间 tab(非 Submit)auto-confirm 也生效
|
|
149
|
+
const threeQ: Question[] = [
|
|
150
|
+
{ question: "Q1", header: "First", options: [{ label: "A" }, { label: "B" }], multiSelect: true },
|
|
151
|
+
{ question: "Q2", header: "Second", options: [{ label: "X" }, { label: "Y" }] },
|
|
152
|
+
{ question: "Q3", header: "Third", options: [{ label: "M" }, { label: "N" }], multiSelect: true },
|
|
151
153
|
];
|
|
152
|
-
const { c, result } = make(
|
|
153
|
-
|
|
154
|
-
c.handleInput(" "); //
|
|
155
|
-
c.handleInput(RIGHT); // →
|
|
156
|
-
c.handleInput(ENTER); //
|
|
157
|
-
|
|
158
|
-
|
|
154
|
+
const { c, result } = make(threeQ);
|
|
155
|
+
// C-S3 路径:Q1 multi toggle A(不 Enter-confirm)→ RIGHT 切到中间 tab Q2 → Q1 被 auto-confirm
|
|
156
|
+
c.handleInput(" "); // Q1 toggle A
|
|
157
|
+
c.handleInput(RIGHT); // → Q2(中间 tab),auto-confirm Q1
|
|
158
|
+
c.handleInput(ENTER); // Q2 select X → Q3
|
|
159
|
+
// C-15 路径:Q3 multi toggle M(不 Enter-confirm)→ RIGHT 直达 Submit → Q3 被 auto-confirm
|
|
160
|
+
c.handleInput(" "); // Q3 toggle M
|
|
161
|
+
c.handleInput(RIGHT); // → Submit,auto-confirm Q3
|
|
162
|
+
c.handleInput(ENTER); // Submit(all confirmed:Q1/Q3 由 auto-confirm,Q2 由 Enter-confirm)
|
|
163
|
+
expect(result.val!.answers["Q1"]).toEqual({ selected: ["A"], other: null }); // auto-confirm 生效(C-S3 断言)
|
|
164
|
+
expect(result.val!.answers["Q2"]).toEqual({ selected: ["X"], other: null });
|
|
165
|
+
expect(result.val!.answers["Q3"]).toEqual({ selected: ["M"], other: null }); // auto-confirm 生效(C-15 断言)
|
|
159
166
|
});
|
|
160
167
|
|
|
161
168
|
it("C-S1: multi-select Enter 同时选中光标项再确认(与单选 Enter 对称)", () => {
|
|
@@ -163,29 +170,12 @@ describe("AskUserComponent — multi question tab nav", () => {
|
|
|
163
170
|
const { c, result } = make([singleQMulti]);
|
|
164
171
|
// singleQMulti: [Auth, Search],光标初始在 Auth(0)
|
|
165
172
|
c.handleInput(DOWN); // 光标移到 Search(1),未 toggle
|
|
166
|
-
c.handleInput(ENTER); // Enter 应同时选中 Search + confirm
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
expect(lines.some((l) => l.toLowerCase().includes("comment"))).toBe(true);
|
|
170
|
-
c.handleInput(ENTER); // 跳过评论 → submit(单问题)
|
|
171
|
-
expect(result.val!.answers["Which features?"]).toBe("Search");
|
|
173
|
+
c.handleInput(ENTER); // Enter 应同时选中 Search + confirm → submit(单问题)
|
|
174
|
+
expect(result.val).not.toBeUndefined();
|
|
175
|
+
expect(result.val!.answers["Which features?"]).toEqual({ selected: ["Search"], other: null });
|
|
172
176
|
});
|
|
173
177
|
|
|
174
|
-
|
|
175
|
-
// S-3 锁定:allowComment 的问题,←/→ 切走只 auto-confirm,不进评论模式
|
|
176
|
-
const twoQMulti: Question[] = [
|
|
177
|
-
{ question: "Q1", header: "First", options: [{ label: "A" }, { label: "B" }], multiSelect: true, allowComment: true },
|
|
178
|
-
{ question: "Q2", header: "Second", options: [{ label: "X" }, { label: "Y" }] },
|
|
179
|
-
];
|
|
180
|
-
const { c, result } = make(twoQMulti);
|
|
181
|
-
c.handleInput(" "); // Q1 toggle A
|
|
182
|
-
c.handleInput(RIGHT); // → Q2,auto-confirm Q1,不进评论
|
|
183
|
-
// 验证:当前在 Q2(非 Q1 的评论模式)。Q2 选 X → Submit
|
|
184
|
-
c.handleInput(ENTER); // Q2 select X → Submit
|
|
185
|
-
c.handleInput(ENTER); // Submit
|
|
186
|
-
expect(result.val!.answers["Q1"]).toBe("A"); // auto-confirm 生效
|
|
187
|
-
expect(result.val!.answers["Q2"]).toBe("X");
|
|
188
|
-
});
|
|
178
|
+
|
|
189
179
|
|
|
190
180
|
it("C-REG-R6: Other 录入→重进清空→Submit 应回到未答(confirmed 不变式)", () => {
|
|
191
181
|
// 回归 MUST_FIX: freeform 空 Enter 清空 freeTextValue 后须重置 confirmed=false
|
|
@@ -247,7 +237,7 @@ describe("AskUserComponent — single-select", () => {
|
|
|
247
237
|
c.handleInput("o");
|
|
248
238
|
c.handleInput("m");
|
|
249
239
|
c.handleInput(ENTER); // save freeText → submit (single question)
|
|
250
|
-
expect(result.val!.answers["Which DB?"]).
|
|
240
|
+
expect(result.val!.answers["Which DB?"]).toEqual({ selected: [], other: "custom" });
|
|
251
241
|
});
|
|
252
242
|
});
|
|
253
243
|
|
|
@@ -268,13 +258,12 @@ describe("AskUserComponent — multi-select toggle", () => {
|
|
|
268
258
|
expect(lines.some((l) => l.includes("[ ]") && l.includes("Auth"))).toBe(true);
|
|
269
259
|
});
|
|
270
260
|
|
|
271
|
-
it("C-24 (AC-18): multi-select toggle does NOT
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
261
|
+
it("C-24 (AC-18): multi-select toggle does NOT confirm/advance", () => {
|
|
262
|
+
const { c, result } = make([singleQMulti]);
|
|
263
|
+
c.handleInput(" "); // toggle — 只标记选中,不确认不离开
|
|
264
|
+
expect(result.val).toBeUndefined();
|
|
275
265
|
const lines = c.render(60);
|
|
276
|
-
|
|
277
|
-
expect(lines.some((l) => l.toLowerCase().includes("comment"))).toBe(false);
|
|
266
|
+
expect(lines.some((l) => l.includes("[✓]") && l.includes("Auth"))).toBe(true);
|
|
278
267
|
});
|
|
279
268
|
});
|
|
280
269
|
|
|
@@ -345,7 +334,7 @@ describe("AskUserComponent — Other free-text editor", () => {
|
|
|
345
334
|
c.handleInput("x");
|
|
346
335
|
c.handleInput(ENTER);
|
|
347
336
|
expect(result.val).not.toBeUndefined();
|
|
348
|
-
expect(result.val!.answers["Which DB?"]).
|
|
337
|
+
expect(result.val!.answers["Which DB?"]).toEqual({ selected: [], other: "x" });
|
|
349
338
|
});
|
|
350
339
|
|
|
351
340
|
it("C-30: editor Enter empty clears freeText (single → stays in form)", () => {
|
|
@@ -472,108 +461,6 @@ describe("AskUserComponent — multi-char paste in editor", () => {
|
|
|
472
461
|
});
|
|
473
462
|
});
|
|
474
463
|
|
|
475
|
-
// ── 5f. 评论流程(FR-4.6 / FR-11 / AC-6/12/17)─────────
|
|
476
|
-
describe("AskUserComponent — comment flow", () => {
|
|
477
|
-
it("C-33: single-select + allowComment Enter enters comment mode", () => {
|
|
478
|
-
const { c } = make([singleQWithComment]);
|
|
479
|
-
c.handleInput(ENTER); // select Postgres
|
|
480
|
-
const lines = c.render(60);
|
|
481
|
-
expect(lines.some((l) => l.toLowerCase().includes("comment"))).toBe(true);
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
it("C-34 (AC-12): comment Enter empty skips and submits (single)", () => {
|
|
485
|
-
const { c, result } = make([singleQWithComment]);
|
|
486
|
-
c.handleInput(ENTER); // select → comment mode
|
|
487
|
-
c.handleInput(ENTER); // empty comment → skip → submit
|
|
488
|
-
expect(result.val).not.toBeUndefined();
|
|
489
|
-
expect(result.val!.answers["Which DB? (with comment)"]).toBe("Postgres");
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
it("C-35: comment Enter with text saves comment", () => {
|
|
493
|
-
const { c, result } = make([singleQWithComment]);
|
|
494
|
-
c.handleInput(ENTER); // select → comment mode
|
|
495
|
-
c.handleInput("f");
|
|
496
|
-
c.handleInput("a");
|
|
497
|
-
c.handleInput("s");
|
|
498
|
-
c.handleInput("t");
|
|
499
|
-
c.handleInput(ENTER); // save comment → submit
|
|
500
|
-
expect(result.val!.answers["Which DB? (with comment)"]).toBe("Postgres — fast");
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
it("C-38: multi-select + allowComment: Enter after toggle enters comment", () => {
|
|
504
|
-
const { c } = make([singleQMulti]);
|
|
505
|
-
c.handleInput(" "); // toggle Auth
|
|
506
|
-
c.handleInput(ENTER); // confirm → comment mode
|
|
507
|
-
const lines = c.render(60);
|
|
508
|
-
expect(lines.some((l) => l.toLowerCase().includes("comment"))).toBe(true);
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
it("C-39: Other + allowComment: freeText then comment", () => {
|
|
512
|
-
const { c, result } = make([singleQWithComment]);
|
|
513
|
-
// Navigate to Other
|
|
514
|
-
c.handleInput(DOWN);
|
|
515
|
-
c.handleInput(DOWN);
|
|
516
|
-
c.handleInput(ENTER); // open freeform
|
|
517
|
-
c.handleInput("c");
|
|
518
|
-
c.handleInput("u");
|
|
519
|
-
c.handleInput("s");
|
|
520
|
-
c.handleInput("t");
|
|
521
|
-
c.handleInput("o");
|
|
522
|
-
c.handleInput("m");
|
|
523
|
-
c.handleInput(ENTER); // save freeText → allowComment → comment mode
|
|
524
|
-
c.handleInput(ENTER); // empty comment → skip → submit
|
|
525
|
-
expect(result.val!.answers["Which DB? (with comment)"]).toBe("custom");
|
|
526
|
-
});
|
|
527
|
-
|
|
528
|
-
it("C-36 (AC-17): comment Esc skips comment and advances (single)", () => {
|
|
529
|
-
const { c, result } = make([singleQWithComment]);
|
|
530
|
-
c.handleInput(ENTER); // select Postgres → comment mode
|
|
531
|
-
c.handleInput(ESC); // Esc in comment = skip comment → advance → submit
|
|
532
|
-
expect(result.val).not.toBeUndefined();
|
|
533
|
-
// commentValue stays null (no prior comment), answer is the selected option
|
|
534
|
-
expect(result.val!.answers["Which DB? (with comment)"]).toBe("Postgres");
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
it("C-36b (AC-17): comment Esc advances to next tab (multi-question)", () => {
|
|
538
|
-
const { c, result } = make(multiQWithComment);
|
|
539
|
-
// Q1 (allowComment): select A → comment mode
|
|
540
|
-
c.handleInput(ENTER); // select A → comment mode
|
|
541
|
-
c.handleInput(ESC); // Esc in comment = skip → advance to Q2
|
|
542
|
-
// Q2: select X → Submit
|
|
543
|
-
c.handleInput(ENTER); // select X → Submit
|
|
544
|
-
c.handleInput(ENTER); // Submit
|
|
545
|
-
expect(result.val).not.toBeUndefined();
|
|
546
|
-
expect(result.val!.answers["Q1"]).toBe("A");
|
|
547
|
-
expect(result.val!.answers["Q2"]).toBe("X");
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
it("C-36c (AC-17): Esc-in-comment discards typed text (vs Enter which saves)", () => {
|
|
551
|
-
// Contrast: typing then Enter would save commentValue and append " — keep".
|
|
552
|
-
// Esc should discard the typed editor text and advance without attaching it.
|
|
553
|
-
const { c, result } = make([singleQWithComment]);
|
|
554
|
-
c.handleInput(ENTER); // select Postgres → comment mode
|
|
555
|
-
c.handleInput("k");
|
|
556
|
-
c.handleInput("e");
|
|
557
|
-
c.handleInput("e");
|
|
558
|
-
c.handleInput("p");
|
|
559
|
-
c.handleInput(ESC); // Esc in comment = discard typed text → advance → submit
|
|
560
|
-
expect(result.val).not.toBeUndefined();
|
|
561
|
-
// No " — keep" suffix: Esc did not commit the typed text
|
|
562
|
-
expect(result.val!.answers["Which DB? (with comment)"]).toBe("Postgres");
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
it("C-37: answer + comment combined format 'label — note'", () => {
|
|
566
|
-
const { c, result } = make([singleQWithComment]);
|
|
567
|
-
c.handleInput(ENTER); // select Postgres → comment mode
|
|
568
|
-
c.handleInput("n");
|
|
569
|
-
c.handleInput("o");
|
|
570
|
-
c.handleInput("t");
|
|
571
|
-
c.handleInput("e");
|
|
572
|
-
c.handleInput(ENTER); // save comment → submit
|
|
573
|
-
expect(result.val!.answers["Which DB? (with comment)"]).toBe("Postgres — note");
|
|
574
|
-
});
|
|
575
|
-
});
|
|
576
|
-
|
|
577
464
|
// ── 5g. 防重入(FR-12)─────────────────────────────────
|
|
578
465
|
describe("AskUserComponent — re-entry guard", () => {
|
|
579
466
|
it("C-40: ignores input after resolution (submit)", () => {
|
|
@@ -658,16 +545,17 @@ describe("AskUserComponent — Submit tab", () => {
|
|
|
658
545
|
});
|
|
659
546
|
|
|
660
547
|
it("C-46: Submit Enter when all confirmed submits", () => {
|
|
661
|
-
const
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
c.handleInput(ENTER); // →
|
|
548
|
+
const twoQ: Question[] = [
|
|
549
|
+
{ question: "Q1", header: "First", options: [{ label: "A" }, { label: "B" }] },
|
|
550
|
+
{ question: "Q2", header: "Second", options: [{ label: "X" }, { label: "Y" }] },
|
|
551
|
+
];
|
|
552
|
+
const { c, result } = make(twoQ);
|
|
553
|
+
c.handleInput(ENTER); // Q1 select A → Q2
|
|
554
|
+
c.handleInput(ENTER); // Q2 select X → Submit
|
|
667
555
|
c.handleInput(ENTER); // Submit
|
|
668
556
|
expect(result.val).not.toBeUndefined();
|
|
669
|
-
expect(result.val!.answers["Q1"]).
|
|
670
|
-
expect(result.val!.answers["Q2"]).
|
|
557
|
+
expect(result.val!.answers["Q1"]).toEqual({ selected: ["A"], other: null });
|
|
558
|
+
expect(result.val!.answers["Q2"]).toEqual({ selected: ["X"], other: null });
|
|
671
559
|
});
|
|
672
560
|
|
|
673
561
|
it("C-S12: Submit tab Left navigates to last question tab", () => {
|
|
@@ -806,7 +694,7 @@ describe("AskUserComponent — confirm-checkmark, Esc-back, Tab browsing", () =>
|
|
|
806
694
|
// ── 5l. 新行为:←/→ 不切 tab、Other Enter 切 freeform 原生、Submit tab focus ──
|
|
807
695
|
describe("AskUserComponent — new behavior (post-refactor)", () => {
|
|
808
696
|
it("C-NEW-1: multi-select Other + Enter opens freeform; Other row turns into [ ] <input>█ in-place", () => {
|
|
809
|
-
// singleQMulti: [Auth, Search],多选
|
|
697
|
+
// singleQMulti: [Auth, Search],多选
|
|
810
698
|
const { c, result } = make([singleQMulti]);
|
|
811
699
|
// 1) Space toggle Auth
|
|
812
700
|
c.handleInput(" ");
|
|
@@ -827,16 +715,15 @@ describe("AskUserComponent — new behavior (post-refactor)", () => {
|
|
|
827
715
|
expect(lines.some((l) => l.includes("Search"))).toBe(true);
|
|
828
716
|
// [✓] 标记的 Auth 仍存在(toggle 状态保留)
|
|
829
717
|
expect(lines.some((l) => l.includes("[✓]") && l.includes("Auth"))).toBe(true);
|
|
830
|
-
// 4) 输 "redis" → Enter 保存 →
|
|
718
|
+
// 4) 输 "redis" → Enter 保存 → submit(单问题)
|
|
831
719
|
c.handleInput("r");
|
|
832
720
|
c.handleInput("e");
|
|
833
721
|
c.handleInput("d");
|
|
834
722
|
c.handleInput("i");
|
|
835
723
|
c.handleInput("s");
|
|
836
|
-
c.handleInput(ENTER); // 保存 freeText →
|
|
837
|
-
c.handleInput(ENTER); // 跳过评论 → submit(单问题)
|
|
724
|
+
c.handleInput(ENTER); // 保存 freeText → submit
|
|
838
725
|
// 答案含多选 toggle 项 + Other 自定义
|
|
839
|
-
expect(result.val!.answers["Which features?"]).
|
|
726
|
+
expect(result.val!.answers["Which features?"]).toEqual({ selected: ["Auth"], other: "redis" });
|
|
840
727
|
});
|
|
841
728
|
|
|
842
729
|
it("C-NEW-2: ←/→ on question tab switches tabs (→ next, ← previous; ← no wrap at first)", () => {
|
|
@@ -877,17 +764,19 @@ describe("AskUserComponent — new behavior (post-refactor)", () => {
|
|
|
877
764
|
});
|
|
878
765
|
|
|
879
766
|
it("C-NEW-4: Submit tab Enter on Submit focus (all confirmed) submits", () => {
|
|
880
|
-
|
|
881
|
-
|
|
767
|
+
const twoQ: Question[] = [
|
|
768
|
+
{ question: "Q1", header: "First", options: [{ label: "A" }, { label: "B" }] },
|
|
769
|
+
{ question: "Q2", header: "Second", options: [{ label: "X" }, { label: "Y" }] },
|
|
770
|
+
];
|
|
771
|
+
const { c, result } = make(twoQ);
|
|
882
772
|
// 答完 Q1 + Q2
|
|
883
|
-
c.handleInput(ENTER); // Q1 select A →
|
|
884
|
-
c.handleInput(ENTER); // skip comment → Q2
|
|
773
|
+
c.handleInput(ENTER); // Q1 select A → Q2
|
|
885
774
|
c.handleInput(ENTER); // Q2 select X → Submit tab(Q2 是最后一个问题,advance 到 Submit)
|
|
886
775
|
// 已经在 Submit tab,focus=Submit,按 Enter 提交
|
|
887
776
|
c.handleInput(ENTER);
|
|
888
777
|
expect(result.val).not.toBeUndefined();
|
|
889
|
-
expect(result.val!.answers["Q1"]).
|
|
890
|
-
expect(result.val!.answers["Q2"]).
|
|
778
|
+
expect(result.val!.answers["Q1"]).toEqual({ selected: ["A"], other: null });
|
|
779
|
+
expect(result.val!.answers["Q2"]).toEqual({ selected: ["X"], other: null });
|
|
891
780
|
});
|
|
892
781
|
|
|
893
782
|
it("C-NEW-5: Submit tab Enter on Cancel focus cancels (no confirm overlay)", () => {
|
|
@@ -8,7 +8,7 @@ import { describe, expect, it } from "vitest";
|
|
|
8
8
|
import { makeE2E } from "./e2e-harness";
|
|
9
9
|
|
|
10
10
|
// ── E2E-1: 单问题无评论 — 选第二项提交 ─────────────────
|
|
11
|
-
describe("E2E-1: single question
|
|
11
|
+
describe("E2E-1: single question — pick 2nd option", () => {
|
|
12
12
|
const questions = [
|
|
13
13
|
{
|
|
14
14
|
question: "Which DB?",
|
|
@@ -27,58 +27,11 @@ describe("E2E-1: single question, no comment — pick 2nd option", () => {
|
|
|
27
27
|
expect(result.content[0].text).toContain("SQLite");
|
|
28
28
|
// Data contract
|
|
29
29
|
expect(details.cancelled).toBe(false);
|
|
30
|
-
expect(details.answers["Which DB?"]).
|
|
30
|
+
expect(details.answers["Which DB?"]).toEqual({ selected: ["SQLite"], other: null });
|
|
31
31
|
expect(details.questions.length).toBe(1);
|
|
32
32
|
});
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
// ── E2E-2: 单问题 + allowComment — 选项 + 评论拼接 ─────
|
|
36
|
-
describe("E2E-2: single question + allowComment — option + comment joined", () => {
|
|
37
|
-
const questions = [
|
|
38
|
-
{
|
|
39
|
-
question: "Which DB?",
|
|
40
|
-
allowComment: true,
|
|
41
|
-
options: [{ label: "Postgres" }, { label: "SQLite" }],
|
|
42
|
-
},
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
it("joins selected option with comment via ' — '", async () => {
|
|
46
|
-
const e = makeE2E(questions);
|
|
47
|
-
// Enter 选 Postgres → 进评论模式 → 输 "fast" → Enter 保存(allowComment 分支)
|
|
48
|
-
e.keys(["\r", "f", "a", "s", "t", "\r"]);
|
|
49
|
-
const result = await e.getExecuted();
|
|
50
|
-
const details = result.details;
|
|
51
|
-
|
|
52
|
-
expect(details.cancelled).toBe(false);
|
|
53
|
-
expect(details.answers["Which DB?"]).toBe("Postgres — fast");
|
|
54
|
-
expect(result.content[0].text).toContain("Postgres — fast");
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// ── E2E-3: 单问题 + allowComment — Enter 空评论跳过 ─────
|
|
59
|
-
describe("E2E-3: single question + allowComment — Enter in comment skips", () => {
|
|
60
|
-
const questions = [
|
|
61
|
-
{
|
|
62
|
-
question: "Which DB?",
|
|
63
|
-
allowComment: true,
|
|
64
|
-
options: [{ label: "Postgres" }, { label: "SQLite" }],
|
|
65
|
-
},
|
|
66
|
-
];
|
|
67
|
-
|
|
68
|
-
it("empty Enter in comment mode keeps option without ' — ' suffix", async () => {
|
|
69
|
-
const e = makeE2E(questions);
|
|
70
|
-
// Enter 选 Postgres → 直接 Enter 评论模式跳过(AC-12)
|
|
71
|
-
e.keys(["\r", "\r"]);
|
|
72
|
-
const result = await e.getExecuted();
|
|
73
|
-
const details = result.details;
|
|
74
|
-
|
|
75
|
-
expect(details.cancelled).toBe(false);
|
|
76
|
-
// 不含 " — " 分隔符
|
|
77
|
-
expect(details.answers["Which DB?"]).toBe("Postgres");
|
|
78
|
-
expect(details.answers["Which DB?"]).not.toContain("—");
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
35
|
// ── E2E-4: 多问题提交 — 逐题选择后 Submit tab 提交(S-11)──────
|
|
83
36
|
describe("E2E-4: multi-question submit — answer each then Submit tab", () => {
|
|
84
37
|
const questions = [
|
|
@@ -96,8 +49,8 @@ describe("E2E-4: multi-question submit — answer each then Submit tab", () => {
|
|
|
96
49
|
const details = result.details;
|
|
97
50
|
|
|
98
51
|
expect(details.cancelled).toBe(false);
|
|
99
|
-
expect(details.answers["Q1"]).
|
|
100
|
-
expect(details.answers["Q2"]).
|
|
52
|
+
expect(details.answers["Q1"]).toEqual({ selected: ["A"], other: null });
|
|
53
|
+
expect(details.answers["Q2"]).toEqual({ selected: ["X"], other: null });
|
|
101
54
|
expect(result.content[0].text).toContain("Q1");
|
|
102
55
|
expect(result.content[0].text).toContain("Q2");
|
|
103
56
|
});
|
|
@@ -121,7 +74,7 @@ describe("E2E-5: multi-select — Space toggle two options then Enter", () => {
|
|
|
121
74
|
const details = result.details;
|
|
122
75
|
|
|
123
76
|
expect(details.cancelled).toBe(false);
|
|
124
|
-
expect(details.answers["Which features?"]).
|
|
77
|
+
expect(details.answers["Which features?"]).toEqual({ selected: ["Auth", "Search"], other: null });
|
|
125
78
|
});
|
|
126
79
|
});
|
|
127
80
|
|
|
@@ -140,7 +93,7 @@ describe("E2E-6: Other free-text — type custom answer", () => {
|
|
|
140
93
|
const details = result.details;
|
|
141
94
|
|
|
142
95
|
expect(details.cancelled).toBe(false);
|
|
143
|
-
expect(details.answers["Which DB?"]).
|
|
96
|
+
expect(details.answers["Which DB?"]).toEqual({ selected: [], other: "redis" });
|
|
144
97
|
});
|
|
145
98
|
});
|
|
146
99
|
|
|
@@ -40,19 +40,9 @@ export const singleQ: Question = {
|
|
|
40
40
|
],
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
export const singleQWithComment: Question = {
|
|
44
|
-
question: "Which DB? (with comment)",
|
|
45
|
-
allowComment: true,
|
|
46
|
-
options: [
|
|
47
|
-
{ label: "Postgres", description: "Battle-tested" },
|
|
48
|
-
{ label: "SQLite", description: "Embedded" },
|
|
49
|
-
],
|
|
50
|
-
};
|
|
51
|
-
|
|
52
43
|
export const singleQMulti: Question = {
|
|
53
44
|
question: "Which features?",
|
|
54
45
|
multiSelect: true,
|
|
55
|
-
allowComment: true,
|
|
56
46
|
options: [
|
|
57
47
|
{ label: "Auth", description: "OAuth + session" },
|
|
58
48
|
{ label: "Search", description: "Full-text" },
|
|
@@ -70,11 +60,6 @@ export const multiQ: Question[] = [
|
|
|
70
60
|
{ question: "Q3", header: "Third", options: [{ label: "M" }, { label: "N" }] },
|
|
71
61
|
];
|
|
72
62
|
|
|
73
|
-
export const multiQWithComment: Question[] = [
|
|
74
|
-
{ question: "Q1", header: "First", allowComment: true, options: [{ label: "A" }, { label: "B" }] },
|
|
75
|
-
{ question: "Q2", header: "Second", options: [{ label: "X" }, { label: "Y" }] },
|
|
76
|
-
];
|
|
77
|
-
|
|
78
63
|
// ── Aliases (PAGE_UP/PAGE_DOWN for W1 tests, PGUP/PGDN for W3 tests) ──
|
|
79
64
|
export const PAGE_UP = "\x1b[5~";
|
|
80
65
|
export const PAGE_DOWN = "\x1b[6~";
|