auq-mcp-server 0.1.0
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/LICENSE +25 -0
- package/README.md +176 -0
- package/dist/__tests__/schema-validation.test.js +137 -0
- package/dist/__tests__/server.integration.test.js +263 -0
- package/dist/add.js +1 -0
- package/dist/add.test.js +5 -0
- package/dist/bin/auq.js +245 -0
- package/dist/bin/test-session-menu.js +28 -0
- package/dist/bin/test-tabbar.js +42 -0
- package/dist/file-utils.js +59 -0
- package/dist/format/ResponseFormatter.js +206 -0
- package/dist/format/__tests__/ResponseFormatter.test.js +380 -0
- package/dist/package.json +74 -0
- package/dist/server.js +107 -0
- package/dist/session/ResponseFormatter.js +130 -0
- package/dist/session/SessionManager.js +474 -0
- package/dist/session/__tests__/ResponseFormatter.test.js +417 -0
- package/dist/session/__tests__/SessionManager.test.js +553 -0
- package/dist/session/__tests__/atomic-operations.test.js +345 -0
- package/dist/session/__tests__/file-watcher.test.js +311 -0
- package/dist/session/__tests__/workflow.integration.test.js +334 -0
- package/dist/session/atomic-operations.js +307 -0
- package/dist/session/file-watcher.js +218 -0
- package/dist/session/index.js +7 -0
- package/dist/session/types.js +20 -0
- package/dist/session/utils.js +125 -0
- package/dist/session-manager.js +171 -0
- package/dist/session-watcher.js +110 -0
- package/dist/src/__tests__/schema-validation.test.js +170 -0
- package/dist/src/__tests__/server.integration.test.js +274 -0
- package/dist/src/add.js +1 -0
- package/dist/src/add.test.js +5 -0
- package/dist/src/server.js +163 -0
- package/dist/src/session/ResponseFormatter.js +163 -0
- package/dist/src/session/SessionManager.js +572 -0
- package/dist/src/session/__tests__/ResponseFormatter.test.js +741 -0
- package/dist/src/session/__tests__/SessionManager.test.js +593 -0
- package/dist/src/session/__tests__/atomic-operations.test.js +346 -0
- package/dist/src/session/__tests__/file-watcher.test.js +311 -0
- package/dist/src/session/atomic-operations.js +307 -0
- package/dist/src/session/file-watcher.js +227 -0
- package/dist/src/session/index.js +7 -0
- package/dist/src/session/types.js +20 -0
- package/dist/src/session/utils.js +180 -0
- package/dist/src/tui/__tests__/session-watcher.test.js +368 -0
- package/dist/src/tui/components/AnimatedGradient.js +45 -0
- package/dist/src/tui/components/ConfirmationDialog.js +89 -0
- package/dist/src/tui/components/CustomInput.js +14 -0
- package/dist/src/tui/components/Footer.js +55 -0
- package/dist/src/tui/components/Header.js +35 -0
- package/dist/src/tui/components/MultiLineTextInput.js +65 -0
- package/dist/src/tui/components/OptionsList.js +115 -0
- package/dist/src/tui/components/QuestionDisplay.js +36 -0
- package/dist/src/tui/components/ReviewScreen.js +57 -0
- package/dist/src/tui/components/SessionSelectionMenu.js +151 -0
- package/dist/src/tui/components/StepperView.js +166 -0
- package/dist/src/tui/components/TabBar.js +42 -0
- package/dist/src/tui/components/Toast.js +19 -0
- package/dist/src/tui/components/WaitingScreen.js +20 -0
- package/dist/src/tui/session-watcher.js +195 -0
- package/dist/src/tui/theme.js +114 -0
- package/dist/src/tui/utils/gradientText.js +24 -0
- package/dist/tui/__tests__/session-watcher.test.js +368 -0
- package/dist/tui/session-watcher.js +183 -0
- package/package.json +78 -0
- package/scripts/postinstall.cjs +51 -0
|
@@ -0,0 +1,741 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for Response Formatter
|
|
3
|
+
*/
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { ResponseFormatter } from "../ResponseFormatter.js";
|
|
6
|
+
describe("ResponseFormatter", () => {
|
|
7
|
+
describe("formatUserResponse", () => {
|
|
8
|
+
it("should format a single question with selected option", () => {
|
|
9
|
+
const questions = [
|
|
10
|
+
{
|
|
11
|
+
options: [
|
|
12
|
+
{
|
|
13
|
+
description: "Dynamic web language",
|
|
14
|
+
label: "JavaScript",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
description: "Type-safe JavaScript",
|
|
18
|
+
label: "TypeScript",
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
prompt: "What is your favorite programming language?",
|
|
22
|
+
title: "Language",
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
const answers = {
|
|
26
|
+
answers: [
|
|
27
|
+
{
|
|
28
|
+
questionIndex: 0,
|
|
29
|
+
selectedOption: "TypeScript",
|
|
30
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
sessionId: "test-123",
|
|
34
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
35
|
+
};
|
|
36
|
+
const result = ResponseFormatter.formatUserResponse(answers, questions);
|
|
37
|
+
expect(result).toBe("Here are the user's answers:\n\n" +
|
|
38
|
+
"1. What is your favorite programming language?\n" +
|
|
39
|
+
"→ TypeScript — Type-safe JavaScript");
|
|
40
|
+
});
|
|
41
|
+
it("should format multiple questions with selected options", () => {
|
|
42
|
+
const questions = [
|
|
43
|
+
{
|
|
44
|
+
options: [
|
|
45
|
+
{
|
|
46
|
+
description: "Dynamic web language",
|
|
47
|
+
label: "JavaScript",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
description: "Type-safe JavaScript",
|
|
51
|
+
label: "TypeScript",
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
prompt: "What is your favorite programming language?",
|
|
55
|
+
title: "Language",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
options: [
|
|
59
|
+
{
|
|
60
|
+
description: "Frontend or backend web application",
|
|
61
|
+
label: "Web",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
description: "Command-line tool",
|
|
65
|
+
label: "CLI",
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
prompt: "What type of application are you building?",
|
|
69
|
+
title: "Application Type",
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
const answers = {
|
|
73
|
+
answers: [
|
|
74
|
+
{
|
|
75
|
+
questionIndex: 0,
|
|
76
|
+
selectedOption: "TypeScript",
|
|
77
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
questionIndex: 1,
|
|
81
|
+
selectedOption: "Web",
|
|
82
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
sessionId: "test-123",
|
|
86
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
87
|
+
};
|
|
88
|
+
const result = ResponseFormatter.formatUserResponse(answers, questions);
|
|
89
|
+
expect(result).toBe("Here are the user's answers:\n\n" +
|
|
90
|
+
"1. What is your favorite programming language?\n" +
|
|
91
|
+
"→ TypeScript — Type-safe JavaScript\n\n" +
|
|
92
|
+
"2. What type of application are you building?\n" +
|
|
93
|
+
"→ Web — Frontend or backend web application");
|
|
94
|
+
});
|
|
95
|
+
it("should format custom text answer", () => {
|
|
96
|
+
const questions = [
|
|
97
|
+
{
|
|
98
|
+
options: [
|
|
99
|
+
{
|
|
100
|
+
description: "Frontend or backend web application",
|
|
101
|
+
label: "Web",
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
description: "Command-line tool",
|
|
105
|
+
label: "CLI",
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
prompt: "What type of application are you building?",
|
|
109
|
+
title: "Application Type",
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
const answers = {
|
|
113
|
+
answers: [
|
|
114
|
+
{
|
|
115
|
+
customText: "Desktop app with Electron",
|
|
116
|
+
questionIndex: 0,
|
|
117
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
sessionId: "test-123",
|
|
121
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
122
|
+
};
|
|
123
|
+
const result = ResponseFormatter.formatUserResponse(answers, questions);
|
|
124
|
+
expect(result).toBe("Here are the user's answers:\n\n" +
|
|
125
|
+
"1. What type of application are you building?\n" +
|
|
126
|
+
"→ Other: 'Desktop app with Electron'");
|
|
127
|
+
});
|
|
128
|
+
it("should format mix of selected and custom answers", () => {
|
|
129
|
+
const questions = [
|
|
130
|
+
{
|
|
131
|
+
options: [
|
|
132
|
+
{
|
|
133
|
+
description: "Dynamic web language",
|
|
134
|
+
label: "JavaScript",
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
description: "Type-safe JavaScript",
|
|
138
|
+
label: "TypeScript",
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
prompt: "What is your favorite programming language?",
|
|
142
|
+
title: "Language",
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
options: [
|
|
146
|
+
{
|
|
147
|
+
description: "Frontend or backend web application",
|
|
148
|
+
label: "Web",
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
description: "Command-line tool",
|
|
152
|
+
label: "CLI",
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
prompt: "What type of application are you building?",
|
|
156
|
+
title: "Application Type",
|
|
157
|
+
},
|
|
158
|
+
];
|
|
159
|
+
const answers = {
|
|
160
|
+
answers: [
|
|
161
|
+
{
|
|
162
|
+
questionIndex: 0,
|
|
163
|
+
selectedOption: "TypeScript",
|
|
164
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
customText: "Desktop app with Electron",
|
|
168
|
+
questionIndex: 1,
|
|
169
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
sessionId: "test-123",
|
|
173
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
174
|
+
};
|
|
175
|
+
const result = ResponseFormatter.formatUserResponse(answers, questions);
|
|
176
|
+
expect(result).toBe("Here are the user's answers:\n\n" +
|
|
177
|
+
"1. What is your favorite programming language?\n" +
|
|
178
|
+
"→ TypeScript — Type-safe JavaScript\n\n" +
|
|
179
|
+
"2. What type of application are you building?\n" +
|
|
180
|
+
"→ Other: 'Desktop app with Electron'");
|
|
181
|
+
});
|
|
182
|
+
it("should handle option without description", () => {
|
|
183
|
+
const questions = [
|
|
184
|
+
{
|
|
185
|
+
options: [
|
|
186
|
+
{
|
|
187
|
+
label: "Red",
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
label: "Blue",
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
prompt: "Choose a color",
|
|
194
|
+
title: "Color",
|
|
195
|
+
},
|
|
196
|
+
];
|
|
197
|
+
const answers = {
|
|
198
|
+
answers: [
|
|
199
|
+
{
|
|
200
|
+
questionIndex: 0,
|
|
201
|
+
selectedOption: "Red",
|
|
202
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
sessionId: "test-123",
|
|
206
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
207
|
+
};
|
|
208
|
+
const result = ResponseFormatter.formatUserResponse(answers, questions);
|
|
209
|
+
expect(result).toBe("Here are the user's answers:\n\n" + "1. Choose a color\n" + "→ Red");
|
|
210
|
+
});
|
|
211
|
+
it("should escape single quotes in custom text", () => {
|
|
212
|
+
const questions = [
|
|
213
|
+
{
|
|
214
|
+
options: [
|
|
215
|
+
{
|
|
216
|
+
label: "Good",
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
prompt: "What is your feedback?",
|
|
220
|
+
title: "Feedback",
|
|
221
|
+
},
|
|
222
|
+
];
|
|
223
|
+
const answers = {
|
|
224
|
+
answers: [
|
|
225
|
+
{
|
|
226
|
+
customText: "It's great! I'm loving it!",
|
|
227
|
+
questionIndex: 0,
|
|
228
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
sessionId: "test-123",
|
|
232
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
233
|
+
};
|
|
234
|
+
const result = ResponseFormatter.formatUserResponse(answers, questions);
|
|
235
|
+
expect(result).toContain("It\\'s great! I\\'m loving it!");
|
|
236
|
+
});
|
|
237
|
+
it("should throw error for empty answers", () => {
|
|
238
|
+
const questions = [
|
|
239
|
+
{
|
|
240
|
+
options: [{ label: "Option 1" }],
|
|
241
|
+
prompt: "Test question",
|
|
242
|
+
title: "Test Question",
|
|
243
|
+
},
|
|
244
|
+
];
|
|
245
|
+
const answers = {
|
|
246
|
+
answers: [],
|
|
247
|
+
sessionId: "test-123",
|
|
248
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
249
|
+
};
|
|
250
|
+
expect(() => ResponseFormatter.formatUserResponse(answers, questions)).toThrow("No answers provided in session");
|
|
251
|
+
});
|
|
252
|
+
it("should throw error for empty questions", () => {
|
|
253
|
+
const questions = [];
|
|
254
|
+
const answers = {
|
|
255
|
+
answers: [
|
|
256
|
+
{
|
|
257
|
+
questionIndex: 0,
|
|
258
|
+
selectedOption: "Option 1",
|
|
259
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
sessionId: "test-123",
|
|
263
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
264
|
+
};
|
|
265
|
+
expect(() => ResponseFormatter.formatUserResponse(answers, questions)).toThrow("No questions provided");
|
|
266
|
+
});
|
|
267
|
+
it("should handle missing answer for a question gracefully", () => {
|
|
268
|
+
const questions = [
|
|
269
|
+
{
|
|
270
|
+
options: [{ label: "Option 1" }],
|
|
271
|
+
prompt: "Question 1",
|
|
272
|
+
title: "Question 1",
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
options: [{ label: "Option 2" }],
|
|
276
|
+
prompt: "Question 2",
|
|
277
|
+
title: "Question 2",
|
|
278
|
+
},
|
|
279
|
+
];
|
|
280
|
+
const answers = {
|
|
281
|
+
answers: [
|
|
282
|
+
// Only answer for question 0, missing answer for question 1
|
|
283
|
+
{
|
|
284
|
+
questionIndex: 0,
|
|
285
|
+
selectedOption: "Option 1",
|
|
286
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
sessionId: "test-123",
|
|
290
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
291
|
+
};
|
|
292
|
+
const result = ResponseFormatter.formatUserResponse(answers, questions);
|
|
293
|
+
// Should only include the answered question
|
|
294
|
+
expect(result).toBe("Here are the user's answers:\n\n" + "1. Question 1\n" + "→ Option 1");
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
describe("validateAnswers", () => {
|
|
298
|
+
it("should validate correct answers", () => {
|
|
299
|
+
const questions = [
|
|
300
|
+
{
|
|
301
|
+
options: [{ label: "Option 1" }, { label: "Option 2" }],
|
|
302
|
+
prompt: "Test question",
|
|
303
|
+
title: "Test Question",
|
|
304
|
+
},
|
|
305
|
+
];
|
|
306
|
+
const answers = {
|
|
307
|
+
answers: [
|
|
308
|
+
{
|
|
309
|
+
questionIndex: 0,
|
|
310
|
+
selectedOption: "Option 1",
|
|
311
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
312
|
+
},
|
|
313
|
+
],
|
|
314
|
+
sessionId: "test-123",
|
|
315
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
316
|
+
};
|
|
317
|
+
expect(() => ResponseFormatter.validateAnswers(answers, questions)).not.toThrow();
|
|
318
|
+
});
|
|
319
|
+
it("should throw error for invalid question index", () => {
|
|
320
|
+
const questions = [
|
|
321
|
+
{
|
|
322
|
+
options: [{ label: "Option 1" }],
|
|
323
|
+
prompt: "Test question",
|
|
324
|
+
title: "Test Question",
|
|
325
|
+
},
|
|
326
|
+
];
|
|
327
|
+
const answers = {
|
|
328
|
+
answers: [
|
|
329
|
+
{
|
|
330
|
+
questionIndex: 99, // Invalid index
|
|
331
|
+
selectedOption: "Option 1",
|
|
332
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
sessionId: "test-123",
|
|
336
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
337
|
+
};
|
|
338
|
+
expect(() => ResponseFormatter.validateAnswers(answers, questions)).toThrow("Answer references invalid question index: 99");
|
|
339
|
+
});
|
|
340
|
+
it("should throw error for answer with neither selectedOption nor customText", () => {
|
|
341
|
+
const questions = [
|
|
342
|
+
{
|
|
343
|
+
options: [{ label: "Option 1" }],
|
|
344
|
+
prompt: "Test question",
|
|
345
|
+
title: "Test Question",
|
|
346
|
+
},
|
|
347
|
+
];
|
|
348
|
+
const answers = {
|
|
349
|
+
answers: [
|
|
350
|
+
{
|
|
351
|
+
questionIndex: 0,
|
|
352
|
+
// Neither selectedOption nor customText provided
|
|
353
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
sessionId: "test-123",
|
|
357
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
358
|
+
};
|
|
359
|
+
expect(() => ResponseFormatter.validateAnswers(answers, questions)).toThrow("has neither selectedOption, selectedOptions, nor customText");
|
|
360
|
+
});
|
|
361
|
+
it("should throw error for non-existent option", () => {
|
|
362
|
+
const questions = [
|
|
363
|
+
{
|
|
364
|
+
options: [{ label: "Option 1" }, { label: "Option 2" }],
|
|
365
|
+
prompt: "Test question",
|
|
366
|
+
title: "Test Question",
|
|
367
|
+
},
|
|
368
|
+
];
|
|
369
|
+
const answers = {
|
|
370
|
+
answers: [
|
|
371
|
+
{
|
|
372
|
+
questionIndex: 0,
|
|
373
|
+
selectedOption: "Option 3", // Non-existent option
|
|
374
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
375
|
+
},
|
|
376
|
+
],
|
|
377
|
+
sessionId: "test-123",
|
|
378
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
379
|
+
};
|
|
380
|
+
expect(() => ResponseFormatter.validateAnswers(answers, questions)).toThrow("references non-existent option: Option 3");
|
|
381
|
+
});
|
|
382
|
+
it("should validate custom text answers", () => {
|
|
383
|
+
const questions = [
|
|
384
|
+
{
|
|
385
|
+
options: [{ label: "Option 1" }],
|
|
386
|
+
prompt: "Test question",
|
|
387
|
+
title: "Test Question",
|
|
388
|
+
},
|
|
389
|
+
];
|
|
390
|
+
const answers = {
|
|
391
|
+
answers: [
|
|
392
|
+
{
|
|
393
|
+
customText: "My custom answer",
|
|
394
|
+
questionIndex: 0,
|
|
395
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
396
|
+
},
|
|
397
|
+
],
|
|
398
|
+
sessionId: "test-123",
|
|
399
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
400
|
+
};
|
|
401
|
+
expect(() => ResponseFormatter.validateAnswers(answers, questions)).not.toThrow();
|
|
402
|
+
});
|
|
403
|
+
it("should throw error for empty answers array", () => {
|
|
404
|
+
const questions = [
|
|
405
|
+
{
|
|
406
|
+
options: [{ label: "Option 1" }],
|
|
407
|
+
prompt: "Test question",
|
|
408
|
+
title: "Test Question",
|
|
409
|
+
},
|
|
410
|
+
];
|
|
411
|
+
const answers = {
|
|
412
|
+
answers: [],
|
|
413
|
+
sessionId: "test-123",
|
|
414
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
415
|
+
};
|
|
416
|
+
expect(() => ResponseFormatter.validateAnswers(answers, questions)).toThrow("No answers provided");
|
|
417
|
+
});
|
|
418
|
+
it("should throw error for empty questions array", () => {
|
|
419
|
+
const questions = [];
|
|
420
|
+
const answers = {
|
|
421
|
+
answers: [
|
|
422
|
+
{
|
|
423
|
+
questionIndex: 0,
|
|
424
|
+
selectedOption: "Option 1",
|
|
425
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
426
|
+
},
|
|
427
|
+
],
|
|
428
|
+
sessionId: "test-123",
|
|
429
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
430
|
+
};
|
|
431
|
+
expect(() => ResponseFormatter.validateAnswers(answers, questions)).toThrow("No questions provided");
|
|
432
|
+
});
|
|
433
|
+
it("should validate multi-select answers with selectedOptions array", () => {
|
|
434
|
+
const questions = [
|
|
435
|
+
{
|
|
436
|
+
options: [
|
|
437
|
+
{ label: "Feature A" },
|
|
438
|
+
{ label: "Feature B" },
|
|
439
|
+
{ label: "Feature C" },
|
|
440
|
+
],
|
|
441
|
+
prompt: "Which features do you want?",
|
|
442
|
+
title: "Features",
|
|
443
|
+
multiSelect: true,
|
|
444
|
+
},
|
|
445
|
+
];
|
|
446
|
+
const answers = {
|
|
447
|
+
answers: [
|
|
448
|
+
{
|
|
449
|
+
questionIndex: 0,
|
|
450
|
+
selectedOptions: ["Feature A", "Feature C"],
|
|
451
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
452
|
+
},
|
|
453
|
+
],
|
|
454
|
+
sessionId: "test-123",
|
|
455
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
456
|
+
};
|
|
457
|
+
expect(() => ResponseFormatter.validateAnswers(answers, questions)).not.toThrow();
|
|
458
|
+
});
|
|
459
|
+
it("should throw error for invalid option in selectedOptions array", () => {
|
|
460
|
+
const questions = [
|
|
461
|
+
{
|
|
462
|
+
options: [
|
|
463
|
+
{ label: "Feature A" },
|
|
464
|
+
{ label: "Feature B" },
|
|
465
|
+
{ label: "Feature C" },
|
|
466
|
+
],
|
|
467
|
+
prompt: "Which features do you want?",
|
|
468
|
+
title: "Features",
|
|
469
|
+
multiSelect: true,
|
|
470
|
+
},
|
|
471
|
+
];
|
|
472
|
+
const answers = {
|
|
473
|
+
answers: [
|
|
474
|
+
{
|
|
475
|
+
questionIndex: 0,
|
|
476
|
+
selectedOptions: ["Feature A", "Invalid Feature"],
|
|
477
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
478
|
+
},
|
|
479
|
+
],
|
|
480
|
+
sessionId: "test-123",
|
|
481
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
482
|
+
};
|
|
483
|
+
expect(() => ResponseFormatter.validateAnswers(answers, questions)).toThrow("references non-existent option: Invalid Feature");
|
|
484
|
+
});
|
|
485
|
+
it("should validate empty selectedOptions array (no selections)", () => {
|
|
486
|
+
const questions = [
|
|
487
|
+
{
|
|
488
|
+
options: [
|
|
489
|
+
{ label: "Feature A" },
|
|
490
|
+
{ label: "Feature B" },
|
|
491
|
+
],
|
|
492
|
+
prompt: "Which features do you want?",
|
|
493
|
+
title: "Features",
|
|
494
|
+
multiSelect: true,
|
|
495
|
+
},
|
|
496
|
+
];
|
|
497
|
+
const answers = {
|
|
498
|
+
answers: [
|
|
499
|
+
{
|
|
500
|
+
questionIndex: 0,
|
|
501
|
+
selectedOptions: [],
|
|
502
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
503
|
+
},
|
|
504
|
+
],
|
|
505
|
+
sessionId: "test-123",
|
|
506
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
507
|
+
};
|
|
508
|
+
expect(() => ResponseFormatter.validateAnswers(answers, questions)).not.toThrow();
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
describe("formatUserResponse - Multi-Select", () => {
|
|
512
|
+
it("should format multi-select question with multiple selections (with descriptions)", () => {
|
|
513
|
+
const questions = [
|
|
514
|
+
{
|
|
515
|
+
options: [
|
|
516
|
+
{
|
|
517
|
+
description: "Authentication support",
|
|
518
|
+
label: "Auth",
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
description: "Database integration",
|
|
522
|
+
label: "Database",
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
description: "API endpoints",
|
|
526
|
+
label: "API",
|
|
527
|
+
},
|
|
528
|
+
],
|
|
529
|
+
prompt: "Which features do you want to enable?",
|
|
530
|
+
title: "Features",
|
|
531
|
+
multiSelect: true,
|
|
532
|
+
},
|
|
533
|
+
];
|
|
534
|
+
const answers = {
|
|
535
|
+
answers: [
|
|
536
|
+
{
|
|
537
|
+
questionIndex: 0,
|
|
538
|
+
selectedOptions: ["Auth", "API"],
|
|
539
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
540
|
+
},
|
|
541
|
+
],
|
|
542
|
+
sessionId: "test-123",
|
|
543
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
544
|
+
};
|
|
545
|
+
const result = ResponseFormatter.formatUserResponse(answers, questions);
|
|
546
|
+
expect(result).toBe("Here are the user's answers:\n\n" +
|
|
547
|
+
"1. Which features do you want to enable?\n" +
|
|
548
|
+
"→ Auth — Authentication support\n" +
|
|
549
|
+
"→ API — API endpoints");
|
|
550
|
+
});
|
|
551
|
+
it("should format multi-select question with multiple selections (without descriptions)", () => {
|
|
552
|
+
const questions = [
|
|
553
|
+
{
|
|
554
|
+
options: [
|
|
555
|
+
{ label: "Red" },
|
|
556
|
+
{ label: "Green" },
|
|
557
|
+
{ label: "Blue" },
|
|
558
|
+
],
|
|
559
|
+
prompt: "Select your favorite colors",
|
|
560
|
+
title: "Colors",
|
|
561
|
+
multiSelect: true,
|
|
562
|
+
},
|
|
563
|
+
];
|
|
564
|
+
const answers = {
|
|
565
|
+
answers: [
|
|
566
|
+
{
|
|
567
|
+
questionIndex: 0,
|
|
568
|
+
selectedOptions: ["Red", "Blue"],
|
|
569
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
570
|
+
},
|
|
571
|
+
],
|
|
572
|
+
sessionId: "test-123",
|
|
573
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
574
|
+
};
|
|
575
|
+
const result = ResponseFormatter.formatUserResponse(answers, questions);
|
|
576
|
+
expect(result).toBe("Here are the user's answers:\n\n" +
|
|
577
|
+
"1. Select your favorite colors\n" +
|
|
578
|
+
"→ Red\n" +
|
|
579
|
+
"→ Blue");
|
|
580
|
+
});
|
|
581
|
+
it("should format multi-select question with empty selections", () => {
|
|
582
|
+
const questions = [
|
|
583
|
+
{
|
|
584
|
+
options: [
|
|
585
|
+
{ label: "Feature A" },
|
|
586
|
+
{ label: "Feature B" },
|
|
587
|
+
],
|
|
588
|
+
prompt: "Which optional features do you want?",
|
|
589
|
+
title: "Optional Features",
|
|
590
|
+
multiSelect: true,
|
|
591
|
+
},
|
|
592
|
+
];
|
|
593
|
+
const answers = {
|
|
594
|
+
answers: [
|
|
595
|
+
{
|
|
596
|
+
questionIndex: 0,
|
|
597
|
+
selectedOptions: [],
|
|
598
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
599
|
+
},
|
|
600
|
+
],
|
|
601
|
+
sessionId: "test-123",
|
|
602
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
603
|
+
};
|
|
604
|
+
const result = ResponseFormatter.formatUserResponse(answers, questions);
|
|
605
|
+
expect(result).toBe("Here are the user's answers:\n\n" +
|
|
606
|
+
"1. Which optional features do you want?\n" +
|
|
607
|
+
"→ (No selection)");
|
|
608
|
+
});
|
|
609
|
+
it("should format mixed single-select and multi-select questions", () => {
|
|
610
|
+
const questions = [
|
|
611
|
+
{
|
|
612
|
+
options: [
|
|
613
|
+
{
|
|
614
|
+
description: "Type-safe JavaScript",
|
|
615
|
+
label: "TypeScript",
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
description: "Dynamic web language",
|
|
619
|
+
label: "JavaScript",
|
|
620
|
+
},
|
|
621
|
+
],
|
|
622
|
+
prompt: "What is your primary language?",
|
|
623
|
+
title: "Language",
|
|
624
|
+
multiSelect: false,
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
options: [
|
|
628
|
+
{
|
|
629
|
+
description: "Authentication support",
|
|
630
|
+
label: "Auth",
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
description: "Database integration",
|
|
634
|
+
label: "Database",
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
description: "API endpoints",
|
|
638
|
+
label: "API",
|
|
639
|
+
},
|
|
640
|
+
],
|
|
641
|
+
prompt: "Which features do you want?",
|
|
642
|
+
title: "Features",
|
|
643
|
+
multiSelect: true,
|
|
644
|
+
},
|
|
645
|
+
];
|
|
646
|
+
const answers = {
|
|
647
|
+
answers: [
|
|
648
|
+
{
|
|
649
|
+
questionIndex: 0,
|
|
650
|
+
selectedOption: "TypeScript",
|
|
651
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
questionIndex: 1,
|
|
655
|
+
selectedOptions: ["Auth", "Database"],
|
|
656
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
657
|
+
},
|
|
658
|
+
],
|
|
659
|
+
sessionId: "test-123",
|
|
660
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
661
|
+
};
|
|
662
|
+
const result = ResponseFormatter.formatUserResponse(answers, questions);
|
|
663
|
+
expect(result).toBe("Here are the user's answers:\n\n" +
|
|
664
|
+
"1. What is your primary language?\n" +
|
|
665
|
+
"→ TypeScript — Type-safe JavaScript\n\n" +
|
|
666
|
+
"2. Which features do you want?\n" +
|
|
667
|
+
"→ Auth — Authentication support\n" +
|
|
668
|
+
"→ Database — Database integration");
|
|
669
|
+
});
|
|
670
|
+
it("should format multi-select with single selection (edge case)", () => {
|
|
671
|
+
const questions = [
|
|
672
|
+
{
|
|
673
|
+
options: [
|
|
674
|
+
{ label: "Option A" },
|
|
675
|
+
{ label: "Option B" },
|
|
676
|
+
{ label: "Option C" },
|
|
677
|
+
],
|
|
678
|
+
prompt: "Select any options you like",
|
|
679
|
+
title: "Options",
|
|
680
|
+
multiSelect: true,
|
|
681
|
+
},
|
|
682
|
+
];
|
|
683
|
+
const answers = {
|
|
684
|
+
answers: [
|
|
685
|
+
{
|
|
686
|
+
questionIndex: 0,
|
|
687
|
+
selectedOptions: ["Option B"],
|
|
688
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
689
|
+
},
|
|
690
|
+
],
|
|
691
|
+
sessionId: "test-123",
|
|
692
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
693
|
+
};
|
|
694
|
+
const result = ResponseFormatter.formatUserResponse(answers, questions);
|
|
695
|
+
expect(result).toBe("Here are the user's answers:\n\n" +
|
|
696
|
+
"1. Select any options you like\n" +
|
|
697
|
+
"→ Option B");
|
|
698
|
+
});
|
|
699
|
+
it("should format multi-select with both selected options AND custom text", () => {
|
|
700
|
+
const questions = [
|
|
701
|
+
{
|
|
702
|
+
options: [
|
|
703
|
+
{
|
|
704
|
+
description: "Authentication support",
|
|
705
|
+
label: "Auth",
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
description: "Database integration",
|
|
709
|
+
label: "Database",
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
description: "API endpoints",
|
|
713
|
+
label: "API",
|
|
714
|
+
},
|
|
715
|
+
],
|
|
716
|
+
prompt: "Which features do you want?",
|
|
717
|
+
title: "Features",
|
|
718
|
+
multiSelect: true,
|
|
719
|
+
},
|
|
720
|
+
];
|
|
721
|
+
const answers = {
|
|
722
|
+
answers: [
|
|
723
|
+
{
|
|
724
|
+
questionIndex: 0,
|
|
725
|
+
selectedOptions: ["Auth", "Database"],
|
|
726
|
+
customText: "Also need caching system",
|
|
727
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
728
|
+
},
|
|
729
|
+
],
|
|
730
|
+
sessionId: "test-123",
|
|
731
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
732
|
+
};
|
|
733
|
+
const result = ResponseFormatter.formatUserResponse(answers, questions);
|
|
734
|
+
expect(result).toBe("Here are the user's answers:\n\n" +
|
|
735
|
+
"1. Which features do you want?\n" +
|
|
736
|
+
"→ Auth — Authentication support\n" +
|
|
737
|
+
"→ Database — Database integration\n" +
|
|
738
|
+
"→ Other: 'Also need caching system'");
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
});
|