@handled-ai/design-system 0.18.6 → 0.18.8
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/badge.d.ts +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/data-table-filter.d.ts +8 -0
- package/dist/components/data-table-filter.js +8 -1
- package/dist/components/data-table-filter.js.map +1 -1
- package/dist/components/pill.d.ts +1 -1
- package/dist/components/score-why-chips.d.ts +1 -1
- package/dist/components/signal-priority-popover.d.ts +1 -1
- package/dist/components/tabs.d.ts +1 -1
- package/dist/components/timeline-activity.d.ts +16 -1
- package/dist/components/timeline-activity.js +69 -1
- package/dist/components/timeline-activity.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/prototype/index.d.ts +1 -1
- package/dist/prototype/prototype-accounts-view.d.ts +1 -1
- package/dist/prototype/prototype-admin-view.d.ts +1 -1
- package/dist/prototype/prototype-config.d.ts +1 -1
- package/dist/prototype/prototype-inbox-view.d.ts +15 -3
- package/dist/prototype/prototype-inbox-view.js +156 -36
- package/dist/prototype/prototype-inbox-view.js.map +1 -1
- package/dist/prototype/prototype-insights-view.d.ts +1 -1
- package/dist/prototype/prototype-shell.d.ts +1 -1
- package/dist/{signal-priority-popover-DWaAMhPI.d.ts → signal-priority-popover-BT6CPYNs.d.ts} +17 -1
- package/package.json +2 -1
- package/src/components/__tests__/data-table-filter.test.tsx +41 -0
- package/src/components/__tests__/timeline-activity.test.tsx +152 -0
- package/src/components/data-table-filter.tsx +17 -2
- package/src/components/timeline-activity.tsx +112 -1
- package/src/prototype/__tests__/detail-view-attention.test.tsx +2 -2
- package/src/prototype/__tests__/detail-view-timeline-system-events.test.tsx +409 -0
- package/src/prototype/prototype-config.ts +21 -0
- package/src/prototype/prototype-inbox-view.tsx +227 -30
|
@@ -271,6 +271,47 @@ describe("DataTableFilter", () => {
|
|
|
271
271
|
expect(document.querySelector('[data-slot="condition-filter"]')).toBeNull();
|
|
272
272
|
});
|
|
273
273
|
|
|
274
|
+
it("shows submenu search for categories that opt in even below the global threshold", () => {
|
|
275
|
+
const category: DataTableFilterCategory = {
|
|
276
|
+
id: "owner",
|
|
277
|
+
label: "Owner",
|
|
278
|
+
icon: ListFilter,
|
|
279
|
+
options: ["Avery", "Jordan"],
|
|
280
|
+
searchable: true,
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
render(
|
|
284
|
+
<DataTableFilter
|
|
285
|
+
categories={[category]}
|
|
286
|
+
selectedFilters={{}}
|
|
287
|
+
onToggleFilter={() => {}}
|
|
288
|
+
/>
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
expect(screen.getByPlaceholderText("Search...")).toBeDefined();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("uses per-category search thresholds when provided", () => {
|
|
295
|
+
const category: DataTableFilterCategory = {
|
|
296
|
+
id: "stage",
|
|
297
|
+
label: "Stage",
|
|
298
|
+
icon: ListFilter,
|
|
299
|
+
options: ["Open", "Closed"],
|
|
300
|
+
searchable: { threshold: 1 },
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
render(
|
|
304
|
+
<DataTableFilter
|
|
305
|
+
categories={[category]}
|
|
306
|
+
selectedFilters={{}}
|
|
307
|
+
onToggleFilter={() => {}}
|
|
308
|
+
optionSearchThreshold={8}
|
|
309
|
+
/>
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
expect(screen.getByPlaceholderText("Search...")).toBeDefined();
|
|
313
|
+
});
|
|
314
|
+
|
|
274
315
|
it("exposes a condition builder popover entry point when condition fields are provided", () => {
|
|
275
316
|
const conditionFields: ConditionFieldDef[] = [
|
|
276
317
|
{ id: "balance", label: "Account Balance", type: "currency" },
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest"
|
|
2
|
+
import React from "react"
|
|
3
|
+
import { render, screen } from "@testing-library/react"
|
|
4
|
+
import {
|
|
5
|
+
TimelineActivity,
|
|
6
|
+
TONE_CLASSES,
|
|
7
|
+
type TimelineEvent,
|
|
8
|
+
} from "../timeline-activity"
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Helpers
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
function minimal(overrides: Partial<TimelineEvent> = {}): TimelineEvent {
|
|
15
|
+
return {
|
|
16
|
+
id: "e1",
|
|
17
|
+
icon: React.createElement("span", { "data-testid": "icon" }, "⚡"),
|
|
18
|
+
title: "Test event",
|
|
19
|
+
time: "2h ago",
|
|
20
|
+
...overrides,
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Tests
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
describe("TimelineActivity", () => {
|
|
29
|
+
// --- Tone rendering ---
|
|
30
|
+
|
|
31
|
+
it("renders red dot classes when tone is 'red'", () => {
|
|
32
|
+
const event = minimal({ tone: "red" })
|
|
33
|
+
const { container } = render(<TimelineActivity events={[event]} />)
|
|
34
|
+
const dot = container.querySelector('[data-testid="timeline-dot"]')!
|
|
35
|
+
expect(dot).not.toBeNull()
|
|
36
|
+
const cls = dot.className
|
|
37
|
+
// Should contain all the red tone dot classes
|
|
38
|
+
expect(cls).toContain("bg-red-50")
|
|
39
|
+
expect(cls).toContain("border-red-200")
|
|
40
|
+
// Should contain the red icon classes
|
|
41
|
+
expect(cls).toContain("text-red-600")
|
|
42
|
+
// Should NOT contain neutral classes
|
|
43
|
+
expect(cls).not.toContain("border-border/60")
|
|
44
|
+
expect(cls).not.toContain("text-muted-foreground")
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it("renders neutral dot classes when tone is absent", () => {
|
|
48
|
+
const event = minimal()
|
|
49
|
+
const { container } = render(<TimelineActivity events={[event]} />)
|
|
50
|
+
const dot = container.querySelector('[data-testid="timeline-dot"]')!
|
|
51
|
+
expect(dot).not.toBeNull()
|
|
52
|
+
const cls = dot.className
|
|
53
|
+
expect(cls).toContain("border-border/60")
|
|
54
|
+
expect(cls).toContain("bg-background")
|
|
55
|
+
expect(cls).toContain("text-muted-foreground")
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// --- Actor byline ---
|
|
59
|
+
|
|
60
|
+
it("renders actor byline with name when actor.kind is 'user'", () => {
|
|
61
|
+
const event = minimal({
|
|
62
|
+
actor: { kind: "user", name: "Alice" },
|
|
63
|
+
})
|
|
64
|
+
render(<TimelineActivity events={[event]} />)
|
|
65
|
+
const byline = screen.getByTestId("actor-byline")
|
|
66
|
+
expect(byline).not.toBeNull()
|
|
67
|
+
expect(byline.textContent).toContain("Alice")
|
|
68
|
+
expect(byline.textContent).toContain("performed this action")
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it("renders no byline when actor.kind is 'system'", () => {
|
|
72
|
+
const event = minimal({
|
|
73
|
+
actor: { kind: "system" },
|
|
74
|
+
})
|
|
75
|
+
const { container } = render(<TimelineActivity events={[event]} />)
|
|
76
|
+
const byline = container.querySelector('[data-testid="actor-byline"]')
|
|
77
|
+
expect(byline).toBeNull()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it("renders 'Integration' text when actor.kind is 'integration'", () => {
|
|
81
|
+
const event = minimal({
|
|
82
|
+
actor: { kind: "integration" },
|
|
83
|
+
})
|
|
84
|
+
render(<TimelineActivity events={[event]} />)
|
|
85
|
+
const byline = screen.getByTestId("actor-byline")
|
|
86
|
+
expect(byline).not.toBeNull()
|
|
87
|
+
expect(byline.textContent).toContain("Integration")
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it("renders custom verb for user actor", () => {
|
|
91
|
+
const event = minimal({
|
|
92
|
+
actor: { kind: "user", name: "Bob", verb: "approved this case" },
|
|
93
|
+
})
|
|
94
|
+
render(<TimelineActivity events={[event]} />)
|
|
95
|
+
const byline = screen.getByTestId("actor-byline")
|
|
96
|
+
expect(byline.textContent).toContain("approved this case")
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it("does not render name span when actor.name is undefined", () => {
|
|
100
|
+
const event = minimal({
|
|
101
|
+
actor: { kind: "user" },
|
|
102
|
+
})
|
|
103
|
+
render(<TimelineActivity events={[event]} />)
|
|
104
|
+
const byline = screen.getByTestId("actor-byline")
|
|
105
|
+
expect(byline).not.toBeNull()
|
|
106
|
+
// Should show "?" initials and the default verb, but no empty name span
|
|
107
|
+
expect(byline.textContent).toContain("?")
|
|
108
|
+
expect(byline.textContent).toContain("performed this action")
|
|
109
|
+
// No font-medium span should be present (that's the name span)
|
|
110
|
+
const nameSpans = byline.querySelectorAll("span.text-foreground.font-medium")
|
|
111
|
+
expect(nameSpans.length).toBe(0)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it("renders no byline when actor is absent", () => {
|
|
115
|
+
const event = minimal()
|
|
116
|
+
const { container } = render(<TimelineActivity events={[event]} />)
|
|
117
|
+
const byline = container.querySelector('[data-testid="actor-byline"]')
|
|
118
|
+
expect(byline).toBeNull()
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
// --- Backwards compatibility ---
|
|
122
|
+
|
|
123
|
+
it("renders correctly with minimal TimelineEvent (only id, icon, title, time)", () => {
|
|
124
|
+
const event: TimelineEvent = {
|
|
125
|
+
id: "min-1",
|
|
126
|
+
icon: React.createElement("span", null, "📌"),
|
|
127
|
+
title: "Minimal event",
|
|
128
|
+
time: "5m ago",
|
|
129
|
+
}
|
|
130
|
+
const { container } = render(<TimelineActivity events={[event]} />)
|
|
131
|
+
// Should render without errors
|
|
132
|
+
expect(container.textContent).toContain("Minimal event")
|
|
133
|
+
expect(container.textContent).toContain("5m ago")
|
|
134
|
+
// Dot should have neutral classes
|
|
135
|
+
const dot = container.querySelector('[data-testid="timeline-dot"]')!
|
|
136
|
+
expect(dot.className).toContain("border-border/60")
|
|
137
|
+
// No byline
|
|
138
|
+
const byline = container.querySelector('[data-testid="actor-byline"]')
|
|
139
|
+
expect(byline).toBeNull()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// --- TONE_CLASSES export ---
|
|
143
|
+
|
|
144
|
+
it("exports TONE_CLASSES with all expected tones", () => {
|
|
145
|
+
const tones = ["red", "amber", "emerald", "violet", "blue", "slate", "salesforce", "gmail"] as const
|
|
146
|
+
for (const tone of tones) {
|
|
147
|
+
expect(TONE_CLASSES[tone]).toBeDefined()
|
|
148
|
+
expect(TONE_CLASSES[tone].dot).toBeTruthy()
|
|
149
|
+
expect(TONE_CLASSES[tone].icon).toBeTruthy()
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
})
|
|
@@ -34,6 +34,12 @@ export interface DataTableFilterCategory {
|
|
|
34
34
|
options: (string | FilterOption)[]
|
|
35
35
|
/** Filter behavior. Defaults to "multi" (checkbox multi-select). */
|
|
36
36
|
type?: "multi" | "single" | "boolean"
|
|
37
|
+
/**
|
|
38
|
+
* Submenu search behavior. Defaults to the DataTableFilter
|
|
39
|
+
* optionSearchThreshold prop. Use true to always show search or false to
|
|
40
|
+
* hide it for a specific category.
|
|
41
|
+
*/
|
|
42
|
+
searchable?: boolean | { threshold?: number }
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
function getOptionValue(option: string | FilterOption): string {
|
|
@@ -208,6 +214,15 @@ export function DataTableFilter({
|
|
|
208
214
|
getOptionLabel(opt).toLowerCase().includes(subQuery)
|
|
209
215
|
)
|
|
210
216
|
: category.options
|
|
217
|
+
const shouldShowSubmenuSearch = (() => {
|
|
218
|
+
if (category.searchable === true) return true
|
|
219
|
+
if (category.searchable === false) return false
|
|
220
|
+
const threshold =
|
|
221
|
+
typeof category.searchable === "object"
|
|
222
|
+
? (category.searchable.threshold ?? optionSearchThreshold)
|
|
223
|
+
: optionSearchThreshold
|
|
224
|
+
return category.options.length > threshold
|
|
225
|
+
})()
|
|
211
226
|
|
|
212
227
|
return (
|
|
213
228
|
<DropdownMenuSub
|
|
@@ -227,8 +242,8 @@ export function DataTableFilter({
|
|
|
227
242
|
{category.label}
|
|
228
243
|
</DropdownMenuSubTrigger>
|
|
229
244
|
<DropdownMenuSubContent className="max-h-[320px] w-52 overflow-y-auto p-1">
|
|
230
|
-
{/* Submenu search —
|
|
231
|
-
{
|
|
245
|
+
{/* Submenu search — shown for long lists or categories that opt in. */}
|
|
246
|
+
{shouldShowSubmenuSearch && (
|
|
232
247
|
<div className="sticky top-0 z-10 border-b border-border bg-popover p-1.5">
|
|
233
248
|
<div className="relative">
|
|
234
249
|
<Search className="absolute left-2 top-1/2 h-3 w-3 -translate-y-1/2 text-muted-foreground" />
|
|
@@ -4,6 +4,24 @@ import * as React from "react"
|
|
|
4
4
|
import { cn } from "../lib/utils"
|
|
5
5
|
import { ChevronDown, ChevronUp, ExternalLink } from "lucide-react"
|
|
6
6
|
|
|
7
|
+
export type TimelineEventTone =
|
|
8
|
+
| "red"
|
|
9
|
+
| "amber"
|
|
10
|
+
| "emerald"
|
|
11
|
+
| "violet"
|
|
12
|
+
| "blue"
|
|
13
|
+
| "slate"
|
|
14
|
+
| "salesforce"
|
|
15
|
+
| "gmail"
|
|
16
|
+
|
|
17
|
+
export interface TimelineEventActor {
|
|
18
|
+
kind: "user" | "integration" | "system"
|
|
19
|
+
name?: string
|
|
20
|
+
initials?: string
|
|
21
|
+
avatarUrl?: string
|
|
22
|
+
verb?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
7
25
|
export interface TimelineEvent {
|
|
8
26
|
id: string
|
|
9
27
|
icon: React.ReactNode
|
|
@@ -28,8 +46,57 @@ export interface TimelineEvent {
|
|
|
28
46
|
defaultExpanded?: boolean
|
|
29
47
|
isInteractive?: boolean
|
|
30
48
|
onSourceClick?: () => void
|
|
49
|
+
tone?: TimelineEventTone
|
|
50
|
+
actor?: TimelineEventActor
|
|
51
|
+
isSystemNoise?: boolean
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Tone class map — every class is a complete static string literal so
|
|
56
|
+
// Tailwind's JIT scanner can detect them. NO interpolation.
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
export const TONE_CLASSES: Record<
|
|
60
|
+
TimelineEventTone,
|
|
61
|
+
{ dot: string; icon: string }
|
|
62
|
+
> = {
|
|
63
|
+
red: {
|
|
64
|
+
dot: "bg-red-50 border-red-200 dark:bg-red-950/30 dark:border-red-900/40",
|
|
65
|
+
icon: "text-red-600 dark:text-red-300",
|
|
66
|
+
},
|
|
67
|
+
amber: {
|
|
68
|
+
dot: "bg-amber-50 border-amber-200 dark:bg-amber-950/30 dark:border-amber-900/40",
|
|
69
|
+
icon: "text-amber-600 dark:text-amber-300",
|
|
70
|
+
},
|
|
71
|
+
emerald: {
|
|
72
|
+
dot: "bg-emerald-50 border-emerald-200 dark:bg-emerald-950/30 dark:border-emerald-900/40",
|
|
73
|
+
icon: "text-emerald-600 dark:text-emerald-300",
|
|
74
|
+
},
|
|
75
|
+
violet: {
|
|
76
|
+
dot: "bg-violet-50 border-violet-200 dark:bg-violet-950/30 dark:border-violet-900/40",
|
|
77
|
+
icon: "text-violet-600 dark:text-violet-300",
|
|
78
|
+
},
|
|
79
|
+
blue: {
|
|
80
|
+
dot: "bg-blue-50 border-blue-200 dark:bg-blue-950/30 dark:border-blue-900/40",
|
|
81
|
+
icon: "text-blue-600 dark:text-blue-300",
|
|
82
|
+
},
|
|
83
|
+
slate: {
|
|
84
|
+
dot: "bg-slate-100 border-slate-200 dark:bg-slate-800/50 dark:border-slate-700",
|
|
85
|
+
icon: "text-slate-500 dark:text-slate-300",
|
|
86
|
+
},
|
|
87
|
+
salesforce: {
|
|
88
|
+
dot: "bg-white border-[#00A1E0]/25 dark:bg-background dark:border-[#00A1E0]/25",
|
|
89
|
+
icon: "text-[#00A1E0]",
|
|
90
|
+
},
|
|
91
|
+
gmail: {
|
|
92
|
+
dot: "bg-white border-red-200 dark:bg-background dark:border-red-900/40",
|
|
93
|
+
icon: "text-red-500 dark:text-red-300",
|
|
94
|
+
},
|
|
31
95
|
}
|
|
32
96
|
|
|
97
|
+
const NEUTRAL_DOT_CLASSES = "border-border/60 bg-background"
|
|
98
|
+
const NEUTRAL_ICON_CLASSES = "text-muted-foreground"
|
|
99
|
+
|
|
33
100
|
export interface TimelineActivityProps {
|
|
34
101
|
events: TimelineEvent[]
|
|
35
102
|
className?: string
|
|
@@ -49,12 +116,54 @@ export function TimelineActivity({ events, className }: TimelineActivityProps) {
|
|
|
49
116
|
)
|
|
50
117
|
}
|
|
51
118
|
|
|
119
|
+
function ActorByline({ actor, time }: { actor: TimelineEventActor; time: string }) {
|
|
120
|
+
if (actor.kind === "system") return null
|
|
121
|
+
|
|
122
|
+
if (actor.kind === "integration") {
|
|
123
|
+
return (
|
|
124
|
+
<div className="mt-1 flex items-center gap-1.5 text-xs text-muted-foreground" data-testid="actor-byline">
|
|
125
|
+
<span>Integration</span>
|
|
126
|
+
<span className="text-muted-foreground/40">·</span>
|
|
127
|
+
<span>{time}</span>
|
|
128
|
+
</div>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// actor.kind === "user"
|
|
133
|
+
const verb = actor.verb ?? "performed this action"
|
|
134
|
+
const displayInitials = actor.initials ?? (actor.name ? actor.name.charAt(0).toUpperCase() : "?")
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<div className="mt-1 flex items-center gap-1.5 text-xs text-muted-foreground" data-testid="actor-byline">
|
|
138
|
+
{actor.avatarUrl ? (
|
|
139
|
+
<img
|
|
140
|
+
src={actor.avatarUrl}
|
|
141
|
+
alt={actor.name ?? "User"}
|
|
142
|
+
className="h-4 w-4 rounded-full object-cover"
|
|
143
|
+
/>
|
|
144
|
+
) : (
|
|
145
|
+
<span className="flex h-4 w-4 items-center justify-center rounded-full bg-muted-foreground/10 text-[8px] font-semibold text-muted-foreground">
|
|
146
|
+
{displayInitials}
|
|
147
|
+
</span>
|
|
148
|
+
)}
|
|
149
|
+
{actor.name && <span className="text-foreground font-medium">{actor.name}</span>}
|
|
150
|
+
<span>{verb}</span>
|
|
151
|
+
<span className="text-muted-foreground/40">·</span>
|
|
152
|
+
<span>{time}</span>
|
|
153
|
+
</div>
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
52
157
|
function TimelineItem({ event, isLast }: { event: TimelineEvent; isLast: boolean }) {
|
|
53
158
|
const [expanded, setExpanded] = React.useState(event.defaultExpanded ?? false)
|
|
54
159
|
const [showAllRecipients, setShowAllRecipients] = React.useState(false)
|
|
55
160
|
const hasContent = !!event.content
|
|
56
161
|
const hasEmail = !!event.email
|
|
57
162
|
|
|
163
|
+
const toneStyle = event.tone ? TONE_CLASSES[event.tone] : null
|
|
164
|
+
const dotClasses = toneStyle ? toneStyle.dot : NEUTRAL_DOT_CLASSES
|
|
165
|
+
const iconClasses = toneStyle ? toneStyle.icon : NEUTRAL_ICON_CLASSES
|
|
166
|
+
|
|
58
167
|
return (
|
|
59
168
|
<div className="group relative flex gap-3.5">
|
|
60
169
|
{!isLast && (
|
|
@@ -62,7 +171,7 @@ function TimelineItem({ event, isLast }: { event: TimelineEvent; isLast: boolean
|
|
|
62
171
|
)}
|
|
63
172
|
|
|
64
173
|
<div className="relative z-10 mt-1 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-background">
|
|
65
|
-
<div className="flex h-4.5 w-4.5 items-center justify-center rounded-full border
|
|
174
|
+
<div className={cn("flex h-4.5 w-4.5 items-center justify-center rounded-full border ring-4 ring-background", dotClasses, iconClasses)} data-testid="timeline-dot">
|
|
66
175
|
{event.icon}
|
|
67
176
|
</div>
|
|
68
177
|
</div>
|
|
@@ -77,6 +186,8 @@ function TimelineItem({ event, isLast }: { event: TimelineEvent; isLast: boolean
|
|
|
77
186
|
</span>
|
|
78
187
|
</div>
|
|
79
188
|
|
|
189
|
+
{event.actor && <ActorByline actor={event.actor} time={event.time} />}
|
|
190
|
+
|
|
80
191
|
{(hasContent || hasEmail) && (
|
|
81
192
|
<div className="mt-2">
|
|
82
193
|
{event.isInteractive ? (
|
|
@@ -89,8 +89,8 @@ describe("DetailView attentionCount", () => {
|
|
|
89
89
|
expect(pill).not.toBeNull();
|
|
90
90
|
expect(pill!.textContent).toContain("5");
|
|
91
91
|
|
|
92
|
-
// Click the timeline
|
|
93
|
-
const timelineButton = container.querySelector("
|
|
92
|
+
// Click the timeline collapse button to expand
|
|
93
|
+
const timelineButton = container.querySelector('[data-testid="timeline-collapse-btn"]') as HTMLElement;
|
|
94
94
|
expect(timelineButton).not.toBeNull();
|
|
95
95
|
fireEvent.click(timelineButton);
|
|
96
96
|
|