@handled-ai/design-system 0.17.1 → 0.17.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/dist/components/feedback-primitives.d.ts +66 -0
- package/dist/components/feedback-primitives.js +295 -0
- package/dist/components/feedback-primitives.js.map +1 -0
- package/dist/components/score-why-chips.d.ts +8 -17
- package/dist/components/score-why-chips.js +266 -180
- package/dist/components/score-why-chips.js.map +1 -1
- package/dist/components/signal-priority-popover.d.ts +17 -0
- package/dist/components/signal-priority-popover.js +247 -0
- package/dist/components/signal-priority-popover.js.map +1 -0
- package/dist/components/user-display.d.ts +22 -0
- package/dist/components/user-display.js +138 -0
- package/dist/components/user-display.js.map +1 -0
- package/dist/components/user-pill.d.ts +3 -0
- package/dist/components/user-pill.js +5 -0
- package/dist/components/user-pill.js.map +1 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/user-display.d.ts +31 -0
- package/dist/lib/user-display.js +57 -0
- package/dist/lib/user-display.js.map +1 -0
- package/dist/prototype/index.d.ts +2 -1
- package/dist/prototype/prototype-accounts-view.d.ts +2 -1
- package/dist/prototype/prototype-admin-view.d.ts +2 -1
- package/dist/prototype/prototype-config.d.ts +15 -332
- package/dist/prototype/prototype-inbox-view.d.ts +2 -1
- package/dist/prototype/prototype-inbox-view.js +11 -12
- package/dist/prototype/prototype-inbox-view.js.map +1 -1
- package/dist/prototype/prototype-insights-view.d.ts +2 -1
- package/dist/prototype/prototype-shell.d.ts +2 -1
- package/dist/signal-priority-popover-DQ_VuHac.d.ts +390 -0
- package/package.json +1 -1
- package/src/components/__tests__/contextual-quick-action-launcher.test.tsx +99 -188
- package/src/components/__tests__/feedback-primitives.test.tsx +509 -0
- package/src/components/__tests__/score-why-chips.test.tsx +540 -0
- package/src/components/__tests__/signal-priority-popover.test.tsx +312 -0
- package/src/components/feedback-primitives.tsx +424 -0
- package/src/components/score-why-chips.tsx +413 -203
- package/src/components/signal-priority-popover.tsx +359 -0
- package/src/components/user-display.tsx +96 -0
- package/src/components/user-pill.tsx +1 -0
- package/src/index.ts +6 -0
- package/src/lib/__tests__/user-display.test.ts +43 -0
- package/src/lib/user-display.ts +88 -0
- package/src/prototype/__tests__/detail-view-score-why.test.tsx +33 -29
- package/src/prototype/__tests__/detail-view-title-slots.test.tsx +65 -0
- package/src/prototype/prototype-config.ts +28 -4
- package/src/prototype/prototype-inbox-view.tsx +8 -10
- package/src/prototype/__tests__/detail-view-title-subtext.test.tsx +0 -72
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for feedback primitives.
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - FeedbackFooter renders thumbs buttons
|
|
6
|
+
* - Clicking "Not helpful" expands the negative feedback area
|
|
7
|
+
* - Clicking "Helpful" expands the positive feedback area
|
|
8
|
+
* - Chip selection toggles (tier-1)
|
|
9
|
+
* - Tier-2 expansion when a tier-1 chip with sub-chips is selected
|
|
10
|
+
* - Submit callback fires with correct structured FeedbackSubmitData shape
|
|
11
|
+
* - Cancel resets state and calls onFeedbackChange(null)
|
|
12
|
+
* - FeedbackChipGroup renders chips with correct colors per flavor
|
|
13
|
+
* - FeedbackInput renders and accepts text
|
|
14
|
+
* - FeedbackActions renders submit/cancel buttons
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { describe, it, expect, vi } from "vitest"
|
|
18
|
+
import React from "react"
|
|
19
|
+
import { render, screen, fireEvent, act } from "@testing-library/react"
|
|
20
|
+
import {
|
|
21
|
+
FeedbackFooter,
|
|
22
|
+
FeedbackChipGroup,
|
|
23
|
+
FeedbackInput,
|
|
24
|
+
FeedbackActions,
|
|
25
|
+
} from "../feedback-primitives"
|
|
26
|
+
import type {
|
|
27
|
+
FeedbackChipTree,
|
|
28
|
+
FeedbackSubmitData,
|
|
29
|
+
} from "../feedback-primitives"
|
|
30
|
+
|
|
31
|
+
// ─── Mock data ───────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
const mockNegativeChips: FeedbackChipTree[] = [
|
|
34
|
+
{
|
|
35
|
+
label: "Inaccurate data",
|
|
36
|
+
subPrompt: "Which field?",
|
|
37
|
+
subChips: ["Balance figures", "Counterparty", "Timestamp", "Other"],
|
|
38
|
+
},
|
|
39
|
+
{ label: "Bad timing" },
|
|
40
|
+
{ label: "Not relevant" },
|
|
41
|
+
{ label: "Already handled" },
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
const mockPositiveChips = ["Right timing", "Accurate data", "Actionable"]
|
|
45
|
+
|
|
46
|
+
// ─── FeedbackFooter ──────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
describe("FeedbackFooter", () => {
|
|
49
|
+
it("renders Helpful and Not helpful buttons", () => {
|
|
50
|
+
render(
|
|
51
|
+
<FeedbackFooter
|
|
52
|
+
feedback={null}
|
|
53
|
+
onFeedbackChange={vi.fn()}
|
|
54
|
+
onSubmit={vi.fn()}
|
|
55
|
+
/>,
|
|
56
|
+
)
|
|
57
|
+
expect(screen.getByText("Helpful")).toBeDefined()
|
|
58
|
+
expect(screen.getByText("Not helpful")).toBeDefined()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it("renders meta text when provided", () => {
|
|
62
|
+
render(
|
|
63
|
+
<FeedbackFooter
|
|
64
|
+
feedback={null}
|
|
65
|
+
onFeedbackChange={vi.fn()}
|
|
66
|
+
onSubmit={vi.fn()}
|
|
67
|
+
metaText="Updated 4m ago"
|
|
68
|
+
/>,
|
|
69
|
+
)
|
|
70
|
+
expect(screen.getByText("Updated 4m ago")).toBeDefined()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it("expands negative feedback area when 'Not helpful' is clicked", async () => {
|
|
74
|
+
const onFeedbackChange = vi.fn()
|
|
75
|
+
render(
|
|
76
|
+
<FeedbackFooter
|
|
77
|
+
feedback={null}
|
|
78
|
+
onFeedbackChange={onFeedbackChange}
|
|
79
|
+
onSubmit={vi.fn()}
|
|
80
|
+
negativeChips={mockNegativeChips}
|
|
81
|
+
negativePrompt="What's the issue?"
|
|
82
|
+
/>,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
// Click "Not helpful"
|
|
86
|
+
await act(async () => {
|
|
87
|
+
fireEvent.click(screen.getByText("Not helpful"))
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
expect(onFeedbackChange).toHaveBeenCalledWith("negative")
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it("shows negative prompt and chips when feedback is negative", () => {
|
|
94
|
+
render(
|
|
95
|
+
<FeedbackFooter
|
|
96
|
+
feedback="negative"
|
|
97
|
+
onFeedbackChange={vi.fn()}
|
|
98
|
+
onSubmit={vi.fn()}
|
|
99
|
+
negativeChips={mockNegativeChips}
|
|
100
|
+
negativePrompt="What's the issue?"
|
|
101
|
+
/>,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
// Click to expand
|
|
105
|
+
fireEvent.click(screen.getByText("Not helpful"))
|
|
106
|
+
|
|
107
|
+
expect(screen.getByText("What's the issue?")).toBeDefined()
|
|
108
|
+
expect(screen.getByText("Inaccurate data")).toBeDefined()
|
|
109
|
+
expect(screen.getByText("Bad timing")).toBeDefined()
|
|
110
|
+
expect(screen.getByText("Not relevant")).toBeDefined()
|
|
111
|
+
expect(screen.getByText("Already handled")).toBeDefined()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it("expands positive feedback area when 'Helpful' is clicked", () => {
|
|
115
|
+
const onFeedbackChange = vi.fn()
|
|
116
|
+
render(
|
|
117
|
+
<FeedbackFooter
|
|
118
|
+
feedback={null}
|
|
119
|
+
onFeedbackChange={onFeedbackChange}
|
|
120
|
+
onSubmit={vi.fn()}
|
|
121
|
+
positiveChips={mockPositiveChips}
|
|
122
|
+
positivePrompt="Thanks! What was good?"
|
|
123
|
+
/>,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
fireEvent.click(screen.getByText("Helpful"))
|
|
127
|
+
|
|
128
|
+
expect(onFeedbackChange).toHaveBeenCalledWith("positive")
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it("shows positive prompt and chips when feedback is positive and expanded", () => {
|
|
132
|
+
render(
|
|
133
|
+
<FeedbackFooter
|
|
134
|
+
feedback="positive"
|
|
135
|
+
onFeedbackChange={vi.fn()}
|
|
136
|
+
onSubmit={vi.fn()}
|
|
137
|
+
positiveChips={mockPositiveChips}
|
|
138
|
+
positivePrompt="Thanks! What was good?"
|
|
139
|
+
/>,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
// Click Helpful to expand
|
|
143
|
+
fireEvent.click(screen.getByText("Helpful"))
|
|
144
|
+
|
|
145
|
+
expect(screen.getByText("Thanks! What was good?")).toBeDefined()
|
|
146
|
+
expect(screen.getByText("Right timing")).toBeDefined()
|
|
147
|
+
expect(screen.getByText("Accurate data")).toBeDefined()
|
|
148
|
+
expect(screen.getByText("Actionable")).toBeDefined()
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it("tier-1 chip selection toggles and tier-2 expansion works", () => {
|
|
152
|
+
render(
|
|
153
|
+
<FeedbackFooter
|
|
154
|
+
feedback="negative"
|
|
155
|
+
onFeedbackChange={vi.fn()}
|
|
156
|
+
onSubmit={vi.fn()}
|
|
157
|
+
negativeChips={mockNegativeChips}
|
|
158
|
+
negativePrompt="What's the issue?"
|
|
159
|
+
/>,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
// Expand
|
|
163
|
+
fireEvent.click(screen.getByText("Not helpful"))
|
|
164
|
+
|
|
165
|
+
// Select "Inaccurate data" (has sub-chips)
|
|
166
|
+
fireEvent.click(screen.getByText("Inaccurate data"))
|
|
167
|
+
|
|
168
|
+
// Should show the sub-prompt and sub-chips
|
|
169
|
+
expect(screen.getByText("Which field?")).toBeDefined()
|
|
170
|
+
expect(screen.getByText("Balance figures")).toBeDefined()
|
|
171
|
+
expect(screen.getByText("Counterparty")).toBeDefined()
|
|
172
|
+
expect(screen.getByText("Timestamp")).toBeDefined()
|
|
173
|
+
|
|
174
|
+
// Select a tier-2 sub-chip
|
|
175
|
+
fireEvent.click(screen.getByText("Balance figures"))
|
|
176
|
+
|
|
177
|
+
// Now deselect the tier-1 chip to hide sub-chips
|
|
178
|
+
fireEvent.click(screen.getByText("Inaccurate data"))
|
|
179
|
+
|
|
180
|
+
// Sub-chips should be hidden
|
|
181
|
+
expect(screen.queryByText("Which field?")).toBeNull()
|
|
182
|
+
expect(screen.queryByText("Balance figures")).toBeNull()
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it("submit callback fires with correct FeedbackSubmitData shape", () => {
|
|
186
|
+
const onSubmit = vi.fn()
|
|
187
|
+
render(
|
|
188
|
+
<FeedbackFooter
|
|
189
|
+
feedback="negative"
|
|
190
|
+
onFeedbackChange={vi.fn()}
|
|
191
|
+
onSubmit={onSubmit}
|
|
192
|
+
negativeChips={mockNegativeChips}
|
|
193
|
+
negativePrompt="What's the issue?"
|
|
194
|
+
/>,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
// Expand
|
|
198
|
+
fireEvent.click(screen.getByText("Not helpful"))
|
|
199
|
+
|
|
200
|
+
// Select tier-1
|
|
201
|
+
fireEvent.click(screen.getByText("Inaccurate data"))
|
|
202
|
+
|
|
203
|
+
// Select tier-2
|
|
204
|
+
fireEvent.click(screen.getByText("Counterparty"))
|
|
205
|
+
|
|
206
|
+
// Select an additional pill (second tier-1 chip)
|
|
207
|
+
fireEvent.click(screen.getByText("Bad timing"))
|
|
208
|
+
|
|
209
|
+
// Type detail text
|
|
210
|
+
const input = screen.getByPlaceholderText("Add optional detail…")
|
|
211
|
+
fireEvent.change(input, { target: { value: "Data was stale" } })
|
|
212
|
+
|
|
213
|
+
// Submit
|
|
214
|
+
fireEvent.click(screen.getByText("Submit"))
|
|
215
|
+
|
|
216
|
+
expect(onSubmit).toHaveBeenCalledTimes(1)
|
|
217
|
+
const data: FeedbackSubmitData = onSubmit.mock.calls[0][0]
|
|
218
|
+
expect(data.sentiment).toBe("negative")
|
|
219
|
+
expect(data.reasonTop).toBe("Inaccurate data")
|
|
220
|
+
expect(data.reasonSub).toBe("Counterparty")
|
|
221
|
+
expect(data.pills).toEqual(["Bad timing"])
|
|
222
|
+
expect(data.detail).toBe("Data was stale")
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it("submit with positive feedback sends correct shape", () => {
|
|
226
|
+
const onSubmit = vi.fn()
|
|
227
|
+
render(
|
|
228
|
+
<FeedbackFooter
|
|
229
|
+
feedback="positive"
|
|
230
|
+
onFeedbackChange={vi.fn()}
|
|
231
|
+
onSubmit={onSubmit}
|
|
232
|
+
positiveChips={mockPositiveChips}
|
|
233
|
+
positivePrompt="Thanks!"
|
|
234
|
+
/>,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
// Expand
|
|
238
|
+
fireEvent.click(screen.getByText("Helpful"))
|
|
239
|
+
|
|
240
|
+
// Select a chip
|
|
241
|
+
fireEvent.click(screen.getByText("Right timing"))
|
|
242
|
+
|
|
243
|
+
// Submit
|
|
244
|
+
fireEvent.click(screen.getByText("Submit"))
|
|
245
|
+
|
|
246
|
+
expect(onSubmit).toHaveBeenCalledTimes(1)
|
|
247
|
+
const data: FeedbackSubmitData = onSubmit.mock.calls[0][0]
|
|
248
|
+
expect(data.sentiment).toBe("positive")
|
|
249
|
+
expect(data.reasonTop).toBe("Right timing")
|
|
250
|
+
expect(data.reasonSub).toBeUndefined()
|
|
251
|
+
expect(data.pills).toEqual([])
|
|
252
|
+
expect(data.detail).toBe("")
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
it("cancel resets state and calls onFeedbackChange(null)", () => {
|
|
256
|
+
const onFeedbackChange = vi.fn()
|
|
257
|
+
render(
|
|
258
|
+
<FeedbackFooter
|
|
259
|
+
feedback="negative"
|
|
260
|
+
onFeedbackChange={onFeedbackChange}
|
|
261
|
+
onSubmit={vi.fn()}
|
|
262
|
+
negativeChips={mockNegativeChips}
|
|
263
|
+
negativePrompt="What's the issue?"
|
|
264
|
+
/>,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
// Expand
|
|
268
|
+
fireEvent.click(screen.getByText("Not helpful"))
|
|
269
|
+
|
|
270
|
+
// Select a chip
|
|
271
|
+
fireEvent.click(screen.getByText("Inaccurate data"))
|
|
272
|
+
|
|
273
|
+
// Cancel
|
|
274
|
+
fireEvent.click(screen.getByText("Cancel"))
|
|
275
|
+
|
|
276
|
+
// Should call onFeedbackChange(null)
|
|
277
|
+
expect(onFeedbackChange).toHaveBeenCalledWith(null)
|
|
278
|
+
|
|
279
|
+
// After cancel, the expanded area should be gone
|
|
280
|
+
expect(screen.queryByText("What's the issue?")).toBeNull()
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
it("submit with no chips selected still sends correct shape with no reasonTop", () => {
|
|
284
|
+
const onSubmit = vi.fn()
|
|
285
|
+
render(
|
|
286
|
+
<FeedbackFooter
|
|
287
|
+
feedback="negative"
|
|
288
|
+
onFeedbackChange={vi.fn()}
|
|
289
|
+
onSubmit={onSubmit}
|
|
290
|
+
negativeChips={mockNegativeChips}
|
|
291
|
+
/>,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
// Expand
|
|
295
|
+
fireEvent.click(screen.getByText("Not helpful"))
|
|
296
|
+
|
|
297
|
+
// Submit without selecting any chips
|
|
298
|
+
fireEvent.click(screen.getByText("Submit"))
|
|
299
|
+
|
|
300
|
+
expect(onSubmit).toHaveBeenCalledTimes(1)
|
|
301
|
+
const data: FeedbackSubmitData = onSubmit.mock.calls[0][0]
|
|
302
|
+
expect(data.sentiment).toBe("negative")
|
|
303
|
+
expect(data.reasonTop).toBeUndefined()
|
|
304
|
+
expect(data.reasonSub).toBeUndefined()
|
|
305
|
+
expect(data.pills).toEqual([])
|
|
306
|
+
expect(data.detail).toBe("")
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
it("enter key in detail input triggers submit", () => {
|
|
310
|
+
const onSubmit = vi.fn()
|
|
311
|
+
render(
|
|
312
|
+
<FeedbackFooter
|
|
313
|
+
feedback="negative"
|
|
314
|
+
onFeedbackChange={vi.fn()}
|
|
315
|
+
onSubmit={onSubmit}
|
|
316
|
+
negativeChips={mockNegativeChips}
|
|
317
|
+
/>,
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
// Expand
|
|
321
|
+
fireEvent.click(screen.getByText("Not helpful"))
|
|
322
|
+
|
|
323
|
+
// Type and press enter
|
|
324
|
+
const input = screen.getByPlaceholderText("Add optional detail…")
|
|
325
|
+
fireEvent.change(input, { target: { value: "test" } })
|
|
326
|
+
fireEvent.keyDown(input, { key: "Enter" })
|
|
327
|
+
|
|
328
|
+
expect(onSubmit).toHaveBeenCalledTimes(1)
|
|
329
|
+
})
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
// ─── FeedbackChipGroup ──────────────────────────────────────────────────────
|
|
333
|
+
|
|
334
|
+
describe("FeedbackChipGroup", () => {
|
|
335
|
+
it("renders all chips", () => {
|
|
336
|
+
render(
|
|
337
|
+
<FeedbackChipGroup
|
|
338
|
+
chips={["Alpha", "Beta", "Gamma"]}
|
|
339
|
+
selected={[]}
|
|
340
|
+
onToggle={vi.fn()}
|
|
341
|
+
flavor="negative"
|
|
342
|
+
/>,
|
|
343
|
+
)
|
|
344
|
+
expect(screen.getByText("Alpha")).toBeDefined()
|
|
345
|
+
expect(screen.getByText("Beta")).toBeDefined()
|
|
346
|
+
expect(screen.getByText("Gamma")).toBeDefined()
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
it("applies negative selected classes to selected chips", () => {
|
|
350
|
+
render(
|
|
351
|
+
<FeedbackChipGroup
|
|
352
|
+
chips={["Alpha", "Beta"]}
|
|
353
|
+
selected={["Alpha"]}
|
|
354
|
+
onToggle={vi.fn()}
|
|
355
|
+
flavor="negative"
|
|
356
|
+
/>,
|
|
357
|
+
)
|
|
358
|
+
const alphaButton = screen.getByText("Alpha")
|
|
359
|
+
// Selected negative chip should have red styling
|
|
360
|
+
expect(alphaButton.className).toContain("bg-red-50")
|
|
361
|
+
expect(alphaButton.className).toContain("text-red-700")
|
|
362
|
+
expect(alphaButton.className).toContain("border-red-200")
|
|
363
|
+
|
|
364
|
+
// Unselected chip should have idle styling
|
|
365
|
+
const betaButton = screen.getByText("Beta")
|
|
366
|
+
expect(betaButton.className).toContain("bg-background")
|
|
367
|
+
expect(betaButton.className).toContain("text-muted-foreground")
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
it("applies positive selected classes to selected chips", () => {
|
|
371
|
+
render(
|
|
372
|
+
<FeedbackChipGroup
|
|
373
|
+
chips={["Alpha", "Beta"]}
|
|
374
|
+
selected={["Alpha"]}
|
|
375
|
+
onToggle={vi.fn()}
|
|
376
|
+
flavor="positive"
|
|
377
|
+
/>,
|
|
378
|
+
)
|
|
379
|
+
const alphaButton = screen.getByText("Alpha")
|
|
380
|
+
// Selected positive chip should have muted styling
|
|
381
|
+
expect(alphaButton.className).toContain("bg-muted")
|
|
382
|
+
expect(alphaButton.className).toContain("text-foreground")
|
|
383
|
+
expect(alphaButton.className).toContain("border-border")
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
it("calls onToggle when a chip is clicked", () => {
|
|
387
|
+
const onToggle = vi.fn()
|
|
388
|
+
render(
|
|
389
|
+
<FeedbackChipGroup
|
|
390
|
+
chips={["Alpha", "Beta"]}
|
|
391
|
+
selected={[]}
|
|
392
|
+
onToggle={onToggle}
|
|
393
|
+
flavor="negative"
|
|
394
|
+
/>,
|
|
395
|
+
)
|
|
396
|
+
fireEvent.click(screen.getByText("Alpha"))
|
|
397
|
+
expect(onToggle).toHaveBeenCalledWith("Alpha")
|
|
398
|
+
})
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
// ─── FeedbackInput ──────────────────────────────────────────────────────────
|
|
402
|
+
|
|
403
|
+
describe("FeedbackInput", () => {
|
|
404
|
+
it("renders with placeholder text", () => {
|
|
405
|
+
render(
|
|
406
|
+
<FeedbackInput
|
|
407
|
+
placeholder="Type here…"
|
|
408
|
+
value=""
|
|
409
|
+
onChange={vi.fn()}
|
|
410
|
+
/>,
|
|
411
|
+
)
|
|
412
|
+
expect(screen.getByPlaceholderText("Type here…")).toBeDefined()
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
it("calls onChange when text is entered", () => {
|
|
416
|
+
const onChange = vi.fn()
|
|
417
|
+
render(
|
|
418
|
+
<FeedbackInput
|
|
419
|
+
placeholder="Type here…"
|
|
420
|
+
value=""
|
|
421
|
+
onChange={onChange}
|
|
422
|
+
/>,
|
|
423
|
+
)
|
|
424
|
+
const input = screen.getByPlaceholderText("Type here…")
|
|
425
|
+
fireEvent.change(input, { target: { value: "hello" } })
|
|
426
|
+
expect(onChange).toHaveBeenCalledWith("hello")
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
it("calls onSubmit when Enter is pressed", () => {
|
|
430
|
+
const onSubmit = vi.fn()
|
|
431
|
+
render(
|
|
432
|
+
<FeedbackInput
|
|
433
|
+
placeholder="Type here…"
|
|
434
|
+
value="test"
|
|
435
|
+
onChange={vi.fn()}
|
|
436
|
+
onSubmit={onSubmit}
|
|
437
|
+
/>,
|
|
438
|
+
)
|
|
439
|
+
const input = screen.getByPlaceholderText("Type here…")
|
|
440
|
+
fireEvent.keyDown(input, { key: "Enter" })
|
|
441
|
+
expect(onSubmit).toHaveBeenCalledTimes(1)
|
|
442
|
+
})
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
// ─── FeedbackActions ─────────────────────────────────────────────────────────
|
|
446
|
+
|
|
447
|
+
describe("FeedbackActions", () => {
|
|
448
|
+
it("renders submit and cancel buttons with default labels", () => {
|
|
449
|
+
render(
|
|
450
|
+
<FeedbackActions onSubmit={vi.fn()} onCancel={vi.fn()} />,
|
|
451
|
+
)
|
|
452
|
+
expect(screen.getByText("Submit")).toBeDefined()
|
|
453
|
+
expect(screen.getByText("Cancel")).toBeDefined()
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
it("renders custom labels", () => {
|
|
457
|
+
render(
|
|
458
|
+
<FeedbackActions
|
|
459
|
+
onSubmit={vi.fn()}
|
|
460
|
+
onCancel={vi.fn()}
|
|
461
|
+
submitLabel="Send"
|
|
462
|
+
cancelLabel="Discard"
|
|
463
|
+
/>,
|
|
464
|
+
)
|
|
465
|
+
expect(screen.getByText("Send")).toBeDefined()
|
|
466
|
+
expect(screen.getByText("Discard")).toBeDefined()
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
it("renders hint text", () => {
|
|
470
|
+
render(
|
|
471
|
+
<FeedbackActions
|
|
472
|
+
onSubmit={vi.fn()}
|
|
473
|
+
onCancel={vi.fn()}
|
|
474
|
+
hint="Routes to model training queue"
|
|
475
|
+
/>,
|
|
476
|
+
)
|
|
477
|
+
expect(screen.getByText("Routes to model training queue")).toBeDefined()
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
it("calls onSubmit when submit button is clicked", () => {
|
|
481
|
+
const onSubmit = vi.fn()
|
|
482
|
+
render(
|
|
483
|
+
<FeedbackActions onSubmit={onSubmit} onCancel={vi.fn()} />,
|
|
484
|
+
)
|
|
485
|
+
fireEvent.click(screen.getByText("Submit"))
|
|
486
|
+
expect(onSubmit).toHaveBeenCalledTimes(1)
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
it("calls onCancel when cancel button is clicked", () => {
|
|
490
|
+
const onCancel = vi.fn()
|
|
491
|
+
render(
|
|
492
|
+
<FeedbackActions onSubmit={vi.fn()} onCancel={onCancel} />,
|
|
493
|
+
)
|
|
494
|
+
fireEvent.click(screen.getByText("Cancel"))
|
|
495
|
+
expect(onCancel).toHaveBeenCalledTimes(1)
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
it("disables submit button when submitDisabled is true", () => {
|
|
499
|
+
render(
|
|
500
|
+
<FeedbackActions
|
|
501
|
+
onSubmit={vi.fn()}
|
|
502
|
+
onCancel={vi.fn()}
|
|
503
|
+
submitDisabled={true}
|
|
504
|
+
/>,
|
|
505
|
+
)
|
|
506
|
+
const submitButton = screen.getByText("Submit")
|
|
507
|
+
expect(submitButton.hasAttribute("disabled")).toBe(true)
|
|
508
|
+
})
|
|
509
|
+
})
|