@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.
Files changed (49) hide show
  1. package/dist/components/feedback-primitives.d.ts +66 -0
  2. package/dist/components/feedback-primitives.js +295 -0
  3. package/dist/components/feedback-primitives.js.map +1 -0
  4. package/dist/components/score-why-chips.d.ts +8 -17
  5. package/dist/components/score-why-chips.js +266 -180
  6. package/dist/components/score-why-chips.js.map +1 -1
  7. package/dist/components/signal-priority-popover.d.ts +17 -0
  8. package/dist/components/signal-priority-popover.js +247 -0
  9. package/dist/components/signal-priority-popover.js.map +1 -0
  10. package/dist/components/user-display.d.ts +22 -0
  11. package/dist/components/user-display.js +138 -0
  12. package/dist/components/user-display.js.map +1 -0
  13. package/dist/components/user-pill.d.ts +3 -0
  14. package/dist/components/user-pill.js +5 -0
  15. package/dist/components/user-pill.js.map +1 -0
  16. package/dist/index.d.ts +6 -3
  17. package/dist/index.js +12 -0
  18. package/dist/index.js.map +1 -1
  19. package/dist/lib/user-display.d.ts +31 -0
  20. package/dist/lib/user-display.js +57 -0
  21. package/dist/lib/user-display.js.map +1 -0
  22. package/dist/prototype/index.d.ts +2 -1
  23. package/dist/prototype/prototype-accounts-view.d.ts +2 -1
  24. package/dist/prototype/prototype-admin-view.d.ts +2 -1
  25. package/dist/prototype/prototype-config.d.ts +15 -332
  26. package/dist/prototype/prototype-inbox-view.d.ts +2 -1
  27. package/dist/prototype/prototype-inbox-view.js +11 -12
  28. package/dist/prototype/prototype-inbox-view.js.map +1 -1
  29. package/dist/prototype/prototype-insights-view.d.ts +2 -1
  30. package/dist/prototype/prototype-shell.d.ts +2 -1
  31. package/dist/signal-priority-popover-DQ_VuHac.d.ts +390 -0
  32. package/package.json +1 -1
  33. package/src/components/__tests__/contextual-quick-action-launcher.test.tsx +99 -188
  34. package/src/components/__tests__/feedback-primitives.test.tsx +509 -0
  35. package/src/components/__tests__/score-why-chips.test.tsx +540 -0
  36. package/src/components/__tests__/signal-priority-popover.test.tsx +312 -0
  37. package/src/components/feedback-primitives.tsx +424 -0
  38. package/src/components/score-why-chips.tsx +413 -203
  39. package/src/components/signal-priority-popover.tsx +359 -0
  40. package/src/components/user-display.tsx +96 -0
  41. package/src/components/user-pill.tsx +1 -0
  42. package/src/index.ts +6 -0
  43. package/src/lib/__tests__/user-display.test.ts +43 -0
  44. package/src/lib/user-display.ts +88 -0
  45. package/src/prototype/__tests__/detail-view-score-why.test.tsx +33 -29
  46. package/src/prototype/__tests__/detail-view-title-slots.test.tsx +65 -0
  47. package/src/prototype/prototype-config.ts +28 -4
  48. package/src/prototype/prototype-inbox-view.tsx +8 -10
  49. package/src/prototype/__tests__/detail-view-title-subtext.test.tsx +0 -72
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Tests for SignalPriorityPopover.
3
+ *
4
+ * Covers:
5
+ * - Renders trigger chip with correct urgency label
6
+ * - Popover opens on trigger click and shows content
7
+ * - Factor rows render with correct direction labels
8
+ * - Urgency class maps applied based on data-state
9
+ * - Popover closes on second click (toggle)
10
+ * - Feedback footer expands on "Not helpful" click
11
+ * - onFeedbackSubmit callback fires
12
+ * - Default feedback chips used when feedbackChips prop is omitted
13
+ * - Falls back to Activity icon for unknown icon names
14
+ * - No feedback footer when onFeedbackSubmit is not provided
15
+ */
16
+
17
+ import { describe, it, expect, vi } from "vitest"
18
+ import React from "react"
19
+ import { render, screen, fireEvent } from "@testing-library/react"
20
+ import { SignalPriorityPopover } from "../signal-priority-popover"
21
+ import type { PriorityFactor, SignalPriorityPopoverProps } from "../signal-priority-popover"
22
+ import type { FeedbackChipTree } from "../feedback-primitives"
23
+
24
+ // ─── Mock data ───────────────────────────────────────────────────────────────
25
+
26
+ const mockFactors: PriorityFactor[] = [
27
+ {
28
+ key: "test_severity",
29
+ label: "Test Transaction Severity",
30
+ icon: "radar",
31
+ tone: "alert",
32
+ direction: "raises",
33
+ score: 85,
34
+ rationale: "Multiple test transactions detected in the last 24 hours.",
35
+ },
36
+ {
37
+ key: "account_depth",
38
+ label: "Account Relationship Depth",
39
+ icon: "link-2",
40
+ tone: "info",
41
+ direction: "lowers",
42
+ score: 30,
43
+ rationale: "Long-standing account with deep relationship history.",
44
+ },
45
+ {
46
+ key: "outflow_severity",
47
+ label: "Cumulative Outflow Severity",
48
+ icon: "arrow-up-right",
49
+ tone: "warn",
50
+ direction: "neutral",
51
+ score: 50,
52
+ rationale: "Moderate outflow levels observed.",
53
+ },
54
+ ]
55
+
56
+ const mockFeedbackChips: FeedbackChipTree[] = [
57
+ { label: "Wrong factor weighting" },
58
+ { label: "Missing context" },
59
+ {
60
+ label: "Inaccurate data",
61
+ subPrompt: "Which field?",
62
+ subChips: ["Balance figures", "Counterparty", "Timestamp"],
63
+ },
64
+ ]
65
+
66
+ const defaultProps: SignalPriorityPopoverProps = {
67
+ score: 79,
68
+ urgencyLabel: "High",
69
+ urgencyExplanation: "Multiple risk signals detected across treasury accounts.",
70
+ factors: mockFactors,
71
+ metaText: "Updated 4m ago - model v3.2",
72
+ feedbackChips: mockFeedbackChips,
73
+ onFeedbackSubmit: vi.fn(),
74
+ }
75
+
76
+ // ─── Tests ───────────────────────────────────────────────────────────────────
77
+
78
+ describe("SignalPriorityPopover", () => {
79
+ it("renders the trigger chip with urgency label", () => {
80
+ render(<SignalPriorityPopover {...defaultProps} />)
81
+ const trigger = screen.getByTestId("priority-popover-trigger")
82
+ expect(trigger).toBeTruthy()
83
+ expect(trigger.textContent).toContain("High Priority")
84
+ })
85
+
86
+ it("derives urgency label from score when not provided", () => {
87
+ render(<SignalPriorityPopover {...defaultProps} urgencyLabel={undefined} score={90} />)
88
+ const trigger = screen.getByTestId("priority-popover-trigger")
89
+ expect(trigger.textContent).toContain("Urgent Priority")
90
+ })
91
+
92
+ it("applies urgency-specific default classes to the trigger", () => {
93
+ render(<SignalPriorityPopover {...defaultProps} urgencyLabel="Urgent" />)
94
+ const trigger = screen.getByTestId("priority-popover-trigger")
95
+ expect(trigger.className).toContain("border-red-200")
96
+ expect(trigger.className).toContain("bg-red-50")
97
+ expect(trigger.className).toContain("text-red-700")
98
+ })
99
+
100
+ it("applies Low urgency classes", () => {
101
+ render(<SignalPriorityPopover {...defaultProps} urgencyLabel="Low" score={20} />)
102
+ const trigger = screen.getByTestId("priority-popover-trigger")
103
+ expect(trigger.className).toContain("border-blue-200")
104
+ expect(trigger.className).toContain("bg-blue-50")
105
+ })
106
+
107
+ it("opens the popover on trigger click and shows content", () => {
108
+ render(<SignalPriorityPopover {...defaultProps} />)
109
+
110
+ // Content should not be visible initially
111
+ expect(screen.queryByTestId("priority-popover-content")).toBeNull()
112
+
113
+ // Click to open
114
+ fireEvent.click(screen.getByTestId("priority-popover-trigger"))
115
+
116
+ const content = screen.getByTestId("priority-popover-content")
117
+ expect(content).toBeTruthy()
118
+
119
+ // Check head section
120
+ expect(content.textContent).toContain("Why this is high priority")
121
+ expect(content.textContent).toContain("79")
122
+ expect(content.textContent).toContain("/100")
123
+ expect(content.textContent).toContain("High range")
124
+ expect(content.textContent).toContain("60-79")
125
+ })
126
+
127
+ it("renders the urgency explanation synthesis sentence", () => {
128
+ render(<SignalPriorityPopover {...defaultProps} />)
129
+ fireEvent.click(screen.getByTestId("priority-popover-trigger"))
130
+
131
+ const content = screen.getByTestId("priority-popover-content")
132
+ expect(content.textContent).toContain(
133
+ "Multiple risk signals detected across treasury accounts.",
134
+ )
135
+ })
136
+
137
+ it("renders factor rows with correct direction labels", () => {
138
+ render(<SignalPriorityPopover {...defaultProps} />)
139
+ fireEvent.click(screen.getByTestId("priority-popover-trigger"))
140
+
141
+ // "raises" factor
142
+ const raisesRow = screen.getByTestId("factor-row-test_severity")
143
+ expect(raisesRow.textContent).toContain("Test Transaction Severity")
144
+ expect(raisesRow.textContent).toContain("Raises")
145
+ expect(raisesRow.textContent).toContain("85")
146
+
147
+ // "lowers" factor
148
+ const lowersRow = screen.getByTestId("factor-row-account_depth")
149
+ expect(lowersRow.textContent).toContain("Account Relationship Depth")
150
+ expect(lowersRow.textContent).toContain("Lowers")
151
+ expect(lowersRow.textContent).toContain("30")
152
+
153
+ // "neutral" factor
154
+ const neutralRow = screen.getByTestId("factor-row-outflow_severity")
155
+ expect(neutralRow.textContent).toContain("Cumulative Outflow Severity")
156
+ expect(neutralRow.textContent).toContain("Neutral")
157
+ expect(neutralRow.textContent).toContain("50")
158
+ })
159
+
160
+ it("renders Contributing factors section label", () => {
161
+ render(<SignalPriorityPopover {...defaultProps} />)
162
+ fireEvent.click(screen.getByTestId("priority-popover-trigger"))
163
+
164
+ const content = screen.getByTestId("priority-popover-content")
165
+ expect(content.textContent).toContain("Contributing factors")
166
+ expect(content.textContent).toContain("Score = weighted sum")
167
+ })
168
+
169
+ it("renders score track bars with correct width percentage", () => {
170
+ render(<SignalPriorityPopover {...defaultProps} />)
171
+ fireEvent.click(screen.getByTestId("priority-popover-trigger"))
172
+
173
+ const raisesRow = screen.getByTestId("factor-row-test_severity")
174
+ const trackFill = raisesRow.querySelector(".bg-foreground\\/20") as HTMLElement
175
+ expect(trackFill).toBeTruthy()
176
+ expect(trackFill.style.width).toBe("85%")
177
+ })
178
+
179
+ it("renders factor rationale text", () => {
180
+ render(<SignalPriorityPopover {...defaultProps} />)
181
+ fireEvent.click(screen.getByTestId("priority-popover-trigger"))
182
+
183
+ const content = screen.getByTestId("priority-popover-content")
184
+ expect(content.textContent).toContain(
185
+ "Multiple test transactions detected in the last 24 hours.",
186
+ )
187
+ expect(content.textContent).toContain(
188
+ "Long-standing account with deep relationship history.",
189
+ )
190
+ })
191
+
192
+ it("renders feedback footer with 'Not helpful' button when onFeedbackSubmit is provided", () => {
193
+ render(<SignalPriorityPopover {...defaultProps} />)
194
+ fireEvent.click(screen.getByTestId("priority-popover-trigger"))
195
+
196
+ const content = screen.getByTestId("priority-popover-content")
197
+ expect(content.textContent).toContain("Not helpful")
198
+ expect(content.textContent).toContain("Helpful")
199
+ })
200
+
201
+ it("does not render feedback footer when onFeedbackSubmit is not provided", () => {
202
+ render(
203
+ <SignalPriorityPopover {...defaultProps} onFeedbackSubmit={undefined} />,
204
+ )
205
+ fireEvent.click(screen.getByTestId("priority-popover-trigger"))
206
+
207
+ const content = screen.getByTestId("priority-popover-content")
208
+ expect(content.textContent).not.toContain("Not helpful")
209
+ })
210
+
211
+ it("expands negative feedback chips on 'Not helpful' click", () => {
212
+ render(<SignalPriorityPopover {...defaultProps} />)
213
+ fireEvent.click(screen.getByTestId("priority-popover-trigger"))
214
+
215
+ // Click "Not helpful" button
216
+ const notHelpfulBtn = screen.getByRole("button", { name: /not helpful/i })
217
+ fireEvent.click(notHelpfulBtn)
218
+
219
+ const content = screen.getByTestId("priority-popover-content")
220
+ // The negative chips should now be visible
221
+ expect(content.textContent).toContain("Wrong factor weighting")
222
+ expect(content.textContent).toContain("Missing context")
223
+ expect(content.textContent).toContain("Inaccurate data")
224
+ })
225
+
226
+ it("does not render Contributing factors section when factors array is empty", () => {
227
+ render(<SignalPriorityPopover {...defaultProps} factors={[]} />)
228
+ fireEvent.click(screen.getByTestId("priority-popover-trigger"))
229
+
230
+ const content = screen.getByTestId("priority-popover-content")
231
+ expect(content.textContent).not.toContain("Contributing factors")
232
+ })
233
+
234
+ it("does not render urgency explanation when not provided", () => {
235
+ render(
236
+ <SignalPriorityPopover
237
+ {...defaultProps}
238
+ urgencyExplanation={undefined}
239
+ />,
240
+ )
241
+ fireEvent.click(screen.getByTestId("priority-popover-trigger"))
242
+
243
+ const content = screen.getByTestId("priority-popover-content")
244
+ // Should still show the title
245
+ expect(content.textContent).toContain("Why this is high priority")
246
+ // But not the explanation
247
+ expect(content.textContent).not.toContain(
248
+ "Multiple risk signals detected across treasury accounts.",
249
+ )
250
+ })
251
+
252
+ it("clamps score track width between 0% and 100%", () => {
253
+ const edgeFactors: PriorityFactor[] = [
254
+ {
255
+ key: "over",
256
+ label: "Over 100",
257
+ icon: "activity",
258
+ tone: "alert",
259
+ direction: "raises",
260
+ score: 150,
261
+ rationale: "Exceeds max",
262
+ },
263
+ {
264
+ key: "under",
265
+ label: "Under 0",
266
+ icon: "activity",
267
+ tone: "info",
268
+ direction: "lowers",
269
+ score: -10,
270
+ rationale: "Below min",
271
+ },
272
+ ]
273
+ render(<SignalPriorityPopover {...defaultProps} factors={edgeFactors} />)
274
+ fireEvent.click(screen.getByTestId("priority-popover-trigger"))
275
+
276
+ const overRow = screen.getByTestId("factor-row-over")
277
+ const overFill = overRow.querySelector(".bg-foreground\\/20") as HTMLElement
278
+ expect(overFill.style.width).toBe("100%")
279
+
280
+ const underRow = screen.getByTestId("factor-row-under")
281
+ const underFill = underRow.querySelector(".bg-foreground\\/20") as HTMLElement
282
+ expect(underFill.style.width).toBe("0%")
283
+ })
284
+
285
+ it("renders tone-specific icon background classes", () => {
286
+ render(<SignalPriorityPopover {...defaultProps} />)
287
+ fireEvent.click(screen.getByTestId("priority-popover-trigger"))
288
+
289
+ // Alert tone should have bg-red-50 text-red-600
290
+ const alertRow = screen.getByTestId("factor-row-test_severity")
291
+ const alertIcon = alertRow.querySelector(".bg-red-50")
292
+ expect(alertIcon).toBeTruthy()
293
+
294
+ // Info tone should have bg-blue-50 text-blue-600
295
+ const infoRow = screen.getByTestId("factor-row-account_depth")
296
+ const infoIcon = infoRow.querySelector(".bg-blue-50")
297
+ expect(infoIcon).toBeTruthy()
298
+
299
+ // Warn tone should have bg-amber-50 text-amber-600
300
+ const warnRow = screen.getByTestId("factor-row-outflow_severity")
301
+ const warnIcon = warnRow.querySelector(".bg-amber-50")
302
+ expect(warnIcon).toBeTruthy()
303
+ })
304
+
305
+ it("applies Medium urgency classes correctly", () => {
306
+ render(<SignalPriorityPopover {...defaultProps} urgencyLabel="Medium" score={45} />)
307
+ const trigger = screen.getByTestId("priority-popover-trigger")
308
+ expect(trigger.className).toContain("border-amber-200")
309
+ expect(trigger.className).toContain("bg-amber-50")
310
+ expect(trigger.textContent).toContain("Medium Priority")
311
+ })
312
+ })