@handled-ai/design-system 0.17.1 → 0.18.1

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 (101) hide show
  1. package/dist/charts/empty-chart-state.d.ts +11 -0
  2. package/dist/charts/empty-chart-state.js +70 -0
  3. package/dist/charts/empty-chart-state.js.map +1 -0
  4. package/dist/charts/index.d.ts +1 -0
  5. package/dist/charts/index.js +1 -0
  6. package/dist/charts/index.js.map +1 -1
  7. package/dist/charts/pipeline-overview.d.ts +2 -1
  8. package/dist/charts/pipeline-overview.js +29 -1
  9. package/dist/charts/pipeline-overview.js.map +1 -1
  10. package/dist/components/actor-byline.d.ts +3 -0
  11. package/dist/components/actor-byline.js +5 -0
  12. package/dist/components/actor-byline.js.map +1 -0
  13. package/dist/components/days-open-cell.d.ts +16 -0
  14. package/dist/components/days-open-cell.js +73 -0
  15. package/dist/components/days-open-cell.js.map +1 -0
  16. package/dist/components/detail-drawer.d.ts +16 -0
  17. package/dist/components/detail-drawer.js +45 -0
  18. package/dist/components/detail-drawer.js.map +1 -0
  19. package/dist/components/feedback-primitives.d.ts +66 -0
  20. package/dist/components/feedback-primitives.js +295 -0
  21. package/dist/components/feedback-primitives.js.map +1 -0
  22. package/dist/components/insights-filter-bar.d.ts +2 -1
  23. package/dist/components/insights-filter-bar.js +13 -5
  24. package/dist/components/insights-filter-bar.js.map +1 -1
  25. package/dist/components/linked-entity-cell.d.ts +14 -0
  26. package/dist/components/linked-entity-cell.js +96 -0
  27. package/dist/components/linked-entity-cell.js.map +1 -0
  28. package/dist/components/metric-card.d.ts +14 -1
  29. package/dist/components/metric-card.js +86 -0
  30. package/dist/components/metric-card.js.map +1 -1
  31. package/dist/components/performance-metrics-table.d.ts +2 -1
  32. package/dist/components/performance-metrics-table.js +78 -46
  33. package/dist/components/performance-metrics-table.js.map +1 -1
  34. package/dist/components/pill.d.ts +26 -0
  35. package/dist/components/pill.js +77 -0
  36. package/dist/components/pill.js.map +1 -0
  37. package/dist/components/quick-segment.d.ts +13 -0
  38. package/dist/components/quick-segment.js +96 -0
  39. package/dist/components/quick-segment.js.map +1 -0
  40. package/dist/components/score-why-chips.d.ts +8 -17
  41. package/dist/components/score-why-chips.js +266 -180
  42. package/dist/components/score-why-chips.js.map +1 -1
  43. package/dist/components/signal-priority-popover.d.ts +17 -0
  44. package/dist/components/signal-priority-popover.js +247 -0
  45. package/dist/components/signal-priority-popover.js.map +1 -0
  46. package/dist/components/user-display.d.ts +22 -0
  47. package/dist/components/user-display.js +138 -0
  48. package/dist/components/user-display.js.map +1 -0
  49. package/dist/components/user-pill.d.ts +3 -0
  50. package/dist/components/user-pill.js +5 -0
  51. package/dist/components/user-pill.js.map +1 -0
  52. package/dist/index.d.ts +13 -4
  53. package/dist/index.js +17 -0
  54. package/dist/index.js.map +1 -1
  55. package/dist/lib/user-display.d.ts +31 -0
  56. package/dist/lib/user-display.js +57 -0
  57. package/dist/lib/user-display.js.map +1 -0
  58. package/dist/prototype/index.d.ts +2 -1
  59. package/dist/prototype/prototype-accounts-view.d.ts +2 -1
  60. package/dist/prototype/prototype-admin-view.d.ts +2 -1
  61. package/dist/prototype/prototype-config.d.ts +15 -332
  62. package/dist/prototype/prototype-inbox-view.d.ts +2 -1
  63. package/dist/prototype/prototype-inbox-view.js +11 -12
  64. package/dist/prototype/prototype-inbox-view.js.map +1 -1
  65. package/dist/prototype/prototype-insights-view.d.ts +2 -1
  66. package/dist/prototype/prototype-shell.d.ts +2 -1
  67. package/dist/signal-priority-popover-DQ_VuHac.d.ts +390 -0
  68. package/package.json +1 -1
  69. package/src/charts/__tests__/insights-charts.test.tsx +62 -0
  70. package/src/charts/empty-chart-state.tsx +44 -0
  71. package/src/charts/index.ts +1 -0
  72. package/src/charts/pipeline-overview.tsx +38 -1
  73. package/src/components/__tests__/contextual-quick-action-launcher.test.tsx +99 -188
  74. package/src/components/__tests__/feedback-primitives.test.tsx +509 -0
  75. package/src/components/__tests__/insights-primitives.test.tsx +117 -0
  76. package/src/components/__tests__/performance-metrics-table.test.tsx +54 -0
  77. package/src/components/__tests__/score-why-chips.test.tsx +540 -0
  78. package/src/components/__tests__/signal-priority-popover.test.tsx +312 -0
  79. package/src/components/__tests__/user-display.test.tsx +75 -0
  80. package/src/components/actor-byline.tsx +1 -0
  81. package/src/components/days-open-cell.tsx +50 -0
  82. package/src/components/detail-drawer.tsx +60 -0
  83. package/src/components/feedback-primitives.tsx +424 -0
  84. package/src/components/insights-filter-bar.tsx +13 -4
  85. package/src/components/linked-entity-cell.tsx +74 -0
  86. package/src/components/metric-card.tsx +82 -0
  87. package/src/components/performance-metrics-table.tsx +99 -63
  88. package/src/components/pill.tsx +67 -0
  89. package/src/components/quick-segment.tsx +68 -0
  90. package/src/components/score-why-chips.tsx +413 -203
  91. package/src/components/signal-priority-popover.tsx +359 -0
  92. package/src/components/user-display.tsx +96 -0
  93. package/src/components/user-pill.tsx +1 -0
  94. package/src/index.ts +11 -0
  95. package/src/lib/__tests__/user-display.test.ts +85 -0
  96. package/src/lib/user-display.ts +88 -0
  97. package/src/prototype/__tests__/detail-view-score-why.test.tsx +33 -29
  98. package/src/prototype/__tests__/detail-view-title-slots.test.tsx +65 -0
  99. package/src/prototype/prototype-config.ts +28 -4
  100. package/src/prototype/prototype-inbox-view.tsx +8 -10
  101. package/src/prototype/__tests__/detail-view-title-subtext.test.tsx +0 -72
@@ -80,16 +80,12 @@ describe("DetailView corrected compact score WHY UX", () => {
80
80
 
81
81
  fireEvent.click(screen.getByRole("button", { name: /urgent priority/i }));
82
82
 
83
+ // The new popover shows urgency explanation and score info in a different format
83
84
  expect(screen.getByText("Customer activity spiked today.")).toBeInTheDocument();
84
- expect(screen.getByText("Score 82/100")).toBeInTheDocument();
85
- expect(screen.getByText("Urgent range: 80-100")).toBeInTheDocument();
86
- expect(screen.getByText("Why now")).toBeInTheDocument();
87
- const priorityPanel = screen.getByRole("region", { name: /priority explanation/i });
88
- expect(within(priorityPanel).getByText("Strong signals detected.")).toBeInTheDocument();
89
- expect(screen.getByText("Top factor")).toBeInTheDocument();
90
- expect(screen.getAllByText("Strong signal").length).toBeGreaterThan(0);
91
- expect(screen.getByText("Trigger strength")).toBeInTheDocument();
92
- expect(screen.getByText("Company fit")).toBeInTheDocument();
85
+ expect(screen.getByText(/82/)).toBeInTheDocument();
86
+ expect(screen.getByText(/\/100/)).toBeInTheDocument();
87
+ // Popover shows "Why this is urgent priority" heading
88
+ expect(screen.getByText(/why this is urgent priority/i)).toBeInTheDocument();
93
89
  expect(screen.queryByText("How's this score?")).toBeNull();
94
90
  });
95
91
 
@@ -116,21 +112,40 @@ describe("DetailView corrected compact score WHY UX", () => {
116
112
  expect(screen.getByRole("region", { name: /treasury activity details/i })).toBeInTheDocument();
117
113
  });
118
114
 
119
- it("resets selected bucket and priority panel when item id changes", () => {
120
- const { rerender } = render(<DetailView {...baseProps()} />);
115
+ it("resets selected bucket when item id changes", () => {
116
+ const { rerender } = render(
117
+ <DetailView
118
+ {...baseProps({
119
+ getSignalScore: () =>
120
+ makeSignalScore({
121
+ explanationBuckets: [
122
+ { key: "signal-a", label: "Treasury activity", kind: "signal", primarySignalId: "sig-1", signals: [{ id: "sig-1", label: "Treasury signal" }] },
123
+ ],
124
+ }),
125
+ })}
126
+ />,
127
+ );
121
128
 
122
- fireEvent.click(screen.getByRole("button", { name: /urgent priority/i }));
123
- expect(screen.getByRole("region", { name: /priority explanation/i })).toBeInTheDocument();
129
+ // Expand a WHY bucket
130
+ fireEvent.click(screen.getByRole("button", { name: /treasury activity/i }));
131
+ expect(screen.getByRole("region", { name: /treasury activity details/i })).toBeInTheDocument();
124
132
 
125
133
  rerender(
126
134
  <DetailView
127
135
  {...baseProps({
128
136
  item: { ...baseItem, id: "case-2", title: "Second Signal" },
137
+ getSignalScore: () =>
138
+ makeSignalScore({
139
+ explanationBuckets: [
140
+ { key: "signal-a", label: "Treasury activity", kind: "signal", primarySignalId: "sig-1", signals: [{ id: "sig-1", label: "Treasury signal" }] },
141
+ ],
142
+ }),
129
143
  })}
130
144
  />,
131
145
  );
132
146
 
133
- expect(screen.queryByRole("region", { name: /priority explanation/i })).toBeNull();
147
+ // Bucket should be collapsed after item change
148
+ expect(screen.queryByRole("region", { name: /treasury activity details/i })).toBeNull();
134
149
  });
135
150
 
136
151
  it("does not render factor-derived WHY chips when no signal buckets are present", () => {
@@ -167,7 +182,7 @@ describe("DetailView corrected compact score WHY UX", () => {
167
182
  expect(screen.queryByRole("button", { name: /company fit/i })).toBeNull();
168
183
  });
169
184
 
170
- it("exposes aria-expanded and aria-controls for priority and bucket triggers", () => {
185
+ it("exposes aria-expanded and aria-controls for bucket triggers", () => {
171
186
  render(
172
187
  <DetailView
173
188
  {...baseProps({
@@ -181,13 +196,6 @@ describe("DetailView corrected compact score WHY UX", () => {
181
196
  />,
182
197
  );
183
198
 
184
- const priorityButton = screen.getByRole("button", { name: /urgent priority/i });
185
- expect(priorityButton.getAttribute("aria-expanded")).toBe("false");
186
- expect(priorityButton.getAttribute("aria-controls")).toBeTruthy();
187
- fireEvent.click(priorityButton);
188
- expect(priorityButton.getAttribute("aria-expanded")).toBe("true");
189
- expect(document.getElementById(priorityButton.getAttribute("aria-controls")!)).toBeInTheDocument();
190
-
191
199
  const bucketButton = screen.getByRole("button", { name: /signal a/i });
192
200
  expect(bucketButton.getAttribute("aria-expanded")).toBe("false");
193
201
  expect(bucketButton.getAttribute("aria-controls")).toBeTruthy();
@@ -213,7 +221,8 @@ describe("DetailView corrected compact score WHY UX", () => {
213
221
  );
214
222
 
215
223
  expect(screen.getAllByRole("button", { name: /treasury activity/i })).toHaveLength(1);
216
- expect(screen.getByText("×3")).toBeInTheDocument();
224
+ // Task 3 redesigned WHY pills: count badge now uses "x{N}" format
225
+ expect(screen.getByText(/x\s*3/)).toBeInTheDocument();
217
226
 
218
227
  fireEvent.click(screen.getByRole("button", { name: /treasury activity/i }));
219
228
  const matchingSignals = screen.getByRole("list", { name: /matching signals/i });
@@ -297,7 +306,7 @@ describe("DetailView corrected compact score WHY UX", () => {
297
306
  });
298
307
  });
299
308
 
300
- it("shows factor feedback in the priority panel, not signal WHY panels", () => {
309
+ it("signal WHY panels do not render factor feedback UI", () => {
301
310
  const onFactorFeedback = vi.fn();
302
311
  render(
303
312
  <DetailView
@@ -317,10 +326,5 @@ describe("DetailView corrected compact score WHY UX", () => {
317
326
  fireEvent.click(screen.getByRole("button", { name: /signal only/i }));
318
327
  const signalPanel = screen.getByRole("region", { name: /signal only details/i });
319
328
  expect(within(signalPanel).queryByTitle("This factor is accurate")).toBeNull();
320
-
321
- fireEvent.click(screen.getByRole("button", { name: /urgent priority/i }));
322
- const priorityPanel = screen.getByRole("region", { name: /priority explanation/i });
323
- expect(within(priorityPanel).getAllByTitle("This factor is accurate").length).toBeGreaterThan(0);
324
- expect(within(priorityPanel).getByText("Needs review")).toBeInTheDocument();
325
329
  });
326
330
  });
@@ -0,0 +1,65 @@
1
+ import { render, screen } from "@testing-library/react"
2
+ import { describe, expect, it, vi } from "vitest"
3
+
4
+ import { DetailView } from "../prototype-inbox-view"
5
+ import type { DetailViewProps } from "../prototype-inbox-view"
6
+
7
+ const baseItem = {
8
+ id: "case-1",
9
+ title: "Churn risk case title",
10
+ details: "Case details",
11
+ statusColor: "red",
12
+ time: "1h ago",
13
+ company: "Acme Corp",
14
+ tag1: "Signal",
15
+ }
16
+
17
+ function renderDetailView(overrides: Partial<DetailViewProps> = {}) {
18
+ const props: DetailViewProps = {
19
+ item: baseItem,
20
+ sections: { signalBrief: false, suggestedActions: false, timeline: false },
21
+ getSignalScore: () => ({
22
+ score: 72,
23
+ factors: [],
24
+ whyNow: "Why now",
25
+ evidence: [],
26
+ confidence: 80,
27
+ urgencyLabel: "High",
28
+ urgencyExplanation: "High priority",
29
+ }),
30
+ buildSuggestedActions: () => [],
31
+ buildSourceItems: () => [],
32
+ accountContacts: [],
33
+ emailSignature: "",
34
+ iconMap: {},
35
+ ...overrides,
36
+ }
37
+
38
+ return render(<DetailView {...props} />)
39
+ }
40
+
41
+ describe("DetailView title slots", () => {
42
+ it("renders supporting title text below the main title", () => {
43
+ renderDetailView({
44
+ renderTitleSubtext: (item) => <p data-testid="title-subtext">Full title: {item.title}</p>,
45
+ })
46
+
47
+ expect(screen.getByTestId("title-subtext").textContent).toBe(
48
+ "Full title: Churn risk case title",
49
+ )
50
+ })
51
+
52
+ it("renders title extra content beside the main title", () => {
53
+ const onClick = vi.fn()
54
+
55
+ renderDetailView({
56
+ renderTitleExtra: () => (
57
+ <button type="button" onClick={onClick}>
58
+ Quick action
59
+ </button>
60
+ ),
61
+ })
62
+
63
+ expect(screen.getByRole("button", { name: "Quick action" })).toBeTruthy()
64
+ })
65
+ })
@@ -15,6 +15,8 @@ import type {
15
15
  import type { TimelineEvent } from "../components/timeline-activity"
16
16
  import type { ApprovalState } from "../components/signal-feedback-inline"
17
17
  import type { LucideIcon } from "lucide-react"
18
+ import type { PriorityFactor } from "../components/signal-priority-popover"
19
+ import type { FeedbackChipTree, FeedbackSubmitData } from "../components/feedback-primitives"
18
20
 
19
21
  // ---------------------------------------------------------------------------
20
22
  // Shared
@@ -45,6 +47,16 @@ export interface SignalScoreExplanationSignal {
45
47
  source?: string
46
48
  time?: string
47
49
  metric?: string
50
+ /** Signal type name (e.g., "treasury_liquidation"). Used for combined signal component identification. */
51
+ signalTypeName?: string
52
+ /** Primary display value (e.g., "-$1,724,310.11"). */
53
+ primaryValue?: string
54
+ /** Qualifier text (e.g., "100% of balance"). */
55
+ qualifier?: string
56
+ /** Counterparty / destination (e.g., "-> JPMorgan Chase --6042"). */
57
+ counterparty?: string
58
+ /** Component breakdown for combined signals. */
59
+ components?: Array<{ type: string; count: number }>
48
60
  }
49
61
 
50
62
  export interface SignalScoreExplanationBucket {
@@ -62,6 +74,10 @@ export interface SignalScoreExplanationBucket {
62
74
  signalIds?: string[]
63
75
  primarySignalId?: string
64
76
  factorKeys?: string[]
77
+ /** Lucide icon name for the bucket type (e.g., "trending-down"). */
78
+ icon?: string
79
+ /** Tonal styling hint for the bucket. */
80
+ tone?: "alert" | "warn" | "info"
65
81
  }
66
82
 
67
83
  export interface SignalScoreData {
@@ -81,6 +97,14 @@ export interface SignalScoreData {
81
97
  /** @deprecated The compact score UX no longer renders score-level thumbs by default. */
82
98
  initialScoreFeedback?: { type: "up" | "down"; pills: string[]; detail: string } | null
83
99
  initialFactorFeedback?: Record<string, { type: "up" | "down"; detail: string }>
100
+ /** Priority factors for the popover breakdown. */
101
+ priorityFactors?: PriorityFactor[]
102
+ /** Negative feedback chip tree for the priority popover. */
103
+ priorityFeedbackChips?: FeedbackChipTree[]
104
+ /** Callback when user submits priority-level feedback. */
105
+ onPriorityFeedback?: (data: FeedbackSubmitData) => void
106
+ /** Callback when user submits bucket-level feedback. */
107
+ onBucketFeedback?: (bucketKey: string, data: FeedbackSubmitData) => void
84
108
  /** AI-generated signal brief text. When present, rendered in a dedicated section. */
85
109
  signalBrief?: string
86
110
  /** Compact label for time-remaining chip (e.g., "13 days left"). */
@@ -166,16 +190,16 @@ export interface InboxViewConfig {
166
190
  briefStyleVariant?: BriefStyleVariant
167
191
  /** Render extra content at the end of the detail view, below the suggested actions section. */
168
192
  renderDetailExtra?: (item: QueueItem) => React.ReactNode
169
- /** Render extra content inline with the detail title. */
170
- renderTitleExtra?: (item: QueueItem) => React.ReactNode
171
- /** Render supporting content below the detail title. */
172
- renderTitleSubtext?: (item: QueueItem) => React.ReactNode
173
193
  /** Render content between the signal brief text and the signal score bar (e.g. "Signals on Case" chips). */
174
194
  renderBeforeScore?: (item: QueueItem) => React.ReactNode
175
195
  /** Render content between the signal score section and the activity timeline (e.g. OpportunityPanel). */
176
196
  renderAfterScore?: (item: QueueItem) => React.ReactNode
177
197
  /** Formatted string for "Last activity X ago" in the collapsed timeline header. If omitted, falls back to the first event's time. */
178
198
  lastActivityTime?: string
199
+ /** Render extra content inline with the detail title. */
200
+ renderTitleExtra?: (item: QueueItem) => React.ReactNode
201
+ /** Render supporting content below the detail title. */
202
+ renderTitleSubtext?: (item: QueueItem) => React.ReactNode
179
203
  /** Sort options for the inbox. When provided, a sort dropdown is rendered in the split view toolbar. */
180
204
  sortOptions?: InboxSortOption[]
181
205
  /** Currently active sort option id. */
@@ -38,7 +38,8 @@ import {
38
38
  } from "../components/inbox-toolbar"
39
39
  import { GroupedListView, type GroupedListGroup } from "../components/item-list"
40
40
  import { SignalApproval, type ApprovalState, type OpportunityPreview } from "../components/signal-feedback-inline"
41
- import { ScoreWhyChips, SignalPriorityChip, SignalPriorityPanel } from "../components/score-why-chips"
41
+ import { ScoreWhyChips } from "../components/score-why-chips"
42
+ import { SignalPriorityPopover } from "../components/signal-priority-popover"
42
43
  import { type SourceDef } from "../components/detail-view"
43
44
  import {
44
45
  SuggestedActions,
@@ -186,13 +187,10 @@ export function DetailView({
186
187
  }: DetailViewProps) {
187
188
  const [showTimeline, setShowTimeline] = React.useState(false)
188
189
  const [extraActions, setExtraActions] = React.useState<SuggestedAction[]>([])
189
- const [priorityOpen, setPriorityOpen] = React.useState(false)
190
- const priorityPanelId = React.useId()
191
190
 
192
191
  React.useEffect(() => {
193
192
  setShowTimeline(false)
194
193
  setExtraActions([])
195
- setPriorityOpen(false)
196
194
  }, [item.id])
197
195
 
198
196
  const signalData = React.useMemo(
@@ -266,12 +264,14 @@ export function DetailView({
266
264
  </div>
267
265
 
268
266
  <div className="mb-6 flex flex-wrap items-center gap-2">
269
- <SignalPriorityChip
267
+ <SignalPriorityPopover
270
268
  score={signalData.score}
271
269
  urgencyLabel={signalData.urgencyLabel}
272
- isOpen={priorityOpen}
273
- controlsId={priorityPanelId}
274
- onClick={() => setPriorityOpen((prev) => !prev)}
270
+ urgencyExplanation={signalData.urgencyExplanation ?? signalData.signalBrief}
271
+ factors={signalData.priorityFactors ?? []}
272
+ metaText={undefined}
273
+ feedbackChips={signalData.priorityFeedbackChips}
274
+ onFeedbackSubmit={signalData.onPriorityFeedback}
275
275
  />
276
276
  {signalData.timeChipLabel && (
277
277
  <Badge variant="outline" title={signalData.timeChipDetail ?? undefined}>
@@ -292,8 +292,6 @@ export function DetailView({
292
292
  {renderMetadataExtra?.(item)}
293
293
  </div>
294
294
 
295
- {priorityOpen && <SignalPriorityPanel id={priorityPanelId} signalData={signalData} className="mb-6" />}
296
-
297
295
  {/* Signal Brief */}
298
296
  {sections.signalBrief && (() => {
299
297
  const briefHeading = signalBriefCopy?.heading ?? "Signal brief"
@@ -1,72 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import React from "react";
3
- import { render, screen } from "@testing-library/react";
4
- import { DetailView, type DetailViewProps } from "../prototype-inbox-view";
5
- import type { QueueItem, SignalScoreData } from "../prototype-config";
6
-
7
- const baseItem: QueueItem = {
8
- id: "case-1",
9
- title: "QA Placement Churn Risk Signal",
10
- details: "Some details",
11
- statusColor: "red",
12
- time: "2h ago",
13
- company: "QA Placement Account",
14
- tag1: "Urgent Priority",
15
- };
16
-
17
- function signalScore(): SignalScoreData {
18
- return {
19
- score: 82,
20
- factors: [],
21
- whyNow: "Strong signal detected.",
22
- evidence: [],
23
- confidence: 75,
24
- };
25
- }
26
-
27
- function baseProps(overrides: Partial<DetailViewProps> = {}): DetailViewProps {
28
- return {
29
- item: baseItem,
30
- sections: { signalBrief: true, suggestedActions: false, timeline: false },
31
- getSignalScore: () => signalScore(),
32
- buildSuggestedActions: () => [],
33
- buildSourceItems: () => [],
34
- accountContacts: [],
35
- emailSignature: "",
36
- iconMap: {},
37
- ...overrides,
38
- };
39
- }
40
-
41
- describe("DetailView title slots", () => {
42
- it("renders supporting title text below the main title", () => {
43
- render(
44
- <DetailView
45
- {...baseProps({
46
- renderTitleSubtext: (item) => (
47
- <p data-testid="title-subtext">Full case: {item.title}</p>
48
- ),
49
- })}
50
- />,
51
- );
52
-
53
- expect(screen.getByRole("heading", { name: "QA Placement Churn Risk Signal" })).toBeTruthy();
54
- expect(screen.getByTestId("title-subtext").textContent).toBe(
55
- "Full case: QA Placement Churn Risk Signal",
56
- );
57
- });
58
-
59
- it("renders title extra content beside the main title", () => {
60
- render(
61
- <DetailView
62
- {...baseProps({
63
- renderTitleExtra: () => (
64
- <button type="button" data-testid="title-extra">Quick action</button>
65
- ),
66
- })}
67
- />,
68
- );
69
-
70
- expect(screen.getByTestId("title-extra").textContent).toBe("Quick action");
71
- });
72
- });