@agent-native/toolkit 0.8.3 → 0.9.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.
- package/README.md +42 -3
- package/agent-native.eject.json +33 -0
- package/dist/chat-history/ChatHistoryRail.d.ts +19 -0
- package/dist/chat-history/ChatHistoryRail.d.ts.map +1 -0
- package/dist/chat-history/ChatHistoryRail.js +22 -0
- package/dist/chat-history/ChatHistoryRail.js.map +1 -0
- package/dist/chat-history/ChatHistoryRail.spec.d.ts +2 -0
- package/dist/chat-history/ChatHistoryRail.spec.d.ts.map +1 -0
- package/dist/chat-history/ChatHistoryRail.spec.js +81 -0
- package/dist/chat-history/ChatHistoryRail.spec.js.map +1 -0
- package/dist/chat-history/index.d.ts +1 -0
- package/dist/chat-history/index.d.ts.map +1 -1
- package/dist/chat-history/index.js +1 -0
- package/dist/chat-history/index.js.map +1 -1
- package/dist/chat-history.css +59 -0
- package/dist/composer/RealtimeVoiceMode.d.ts +5 -1
- package/dist/composer/RealtimeVoiceMode.d.ts.map +1 -1
- package/dist/composer/RealtimeVoiceMode.js +27 -8
- package/dist/composer/RealtimeVoiceMode.js.map +1 -1
- package/dist/composer/RealtimeVoiceMode.spec.js +51 -11
- package/dist/composer/RealtimeVoiceMode.spec.js.map +1 -1
- package/dist/composer/VoiceButton.d.ts +2 -0
- package/dist/composer/VoiceButton.d.ts.map +1 -1
- package/dist/composer/VoiceButton.js +33 -1
- package/dist/composer/VoiceButton.js.map +1 -1
- package/dist/composer/VoiceButton.spec.js +9 -1
- package/dist/composer/VoiceButton.spec.js.map +1 -1
- package/dist/composer/realtime-voice-audio-level.d.ts.map +1 -1
- package/dist/composer/realtime-voice-audio-level.js +2 -1
- package/dist/composer/realtime-voice-audio-level.js.map +1 -1
- package/dist/composer/realtime-voice-audio-level.spec.js +7 -0
- package/dist/composer/realtime-voice-audio-level.spec.js.map +1 -1
- package/dist/composer/useRealtimeVoiceMode.d.ts.map +1 -1
- package/dist/composer/useRealtimeVoiceMode.js +4 -4
- package/dist/composer/useRealtimeVoiceMode.js.map +1 -1
- package/dist/dashboard/DataTable.d.ts +20 -0
- package/dist/dashboard/DataTable.d.ts.map +1 -0
- package/dist/dashboard/DataTable.js +87 -0
- package/dist/dashboard/DataTable.js.map +1 -0
- package/dist/dashboard/DateRangePicker.d.ts +15 -0
- package/dist/dashboard/DateRangePicker.d.ts.map +1 -0
- package/dist/dashboard/DateRangePicker.js +15 -0
- package/dist/dashboard/DateRangePicker.js.map +1 -0
- package/dist/dashboard/GenericChartPanel.d.ts +57 -0
- package/dist/dashboard/GenericChartPanel.d.ts.map +1 -0
- package/dist/dashboard/GenericChartPanel.js +119 -0
- package/dist/dashboard/GenericChartPanel.js.map +1 -0
- package/dist/dashboard/GenericChartPanel.spec.d.ts +2 -0
- package/dist/dashboard/GenericChartPanel.spec.d.ts.map +1 -0
- package/dist/dashboard/GenericChartPanel.spec.js +41 -0
- package/dist/dashboard/GenericChartPanel.spec.js.map +1 -0
- package/dist/dashboard/MetricCard.d.ts +14 -0
- package/dist/dashboard/MetricCard.d.ts.map +1 -0
- package/dist/dashboard/MetricCard.js +10 -0
- package/dist/dashboard/MetricCard.js.map +1 -0
- package/dist/dashboard/StatsCard.d.ts +16 -0
- package/dist/dashboard/StatsCard.d.ts.map +1 -0
- package/dist/dashboard/StatsCard.js +8 -0
- package/dist/dashboard/StatsCard.js.map +1 -0
- package/dist/dashboard/dashboard-layout.d.ts +49 -0
- package/dist/dashboard/dashboard-layout.d.ts.map +1 -0
- package/dist/dashboard/dashboard-layout.js +231 -0
- package/dist/dashboard/dashboard-layout.js.map +1 -0
- package/dist/dashboard/dashboard-layout.spec.d.ts +2 -0
- package/dist/dashboard/dashboard-layout.spec.d.ts.map +1 -0
- package/dist/dashboard/dashboard-layout.spec.js +52 -0
- package/dist/dashboard/dashboard-layout.spec.js.map +1 -0
- package/dist/dashboard/index.d.ts +7 -0
- package/dist/dashboard/index.d.ts.map +1 -0
- package/dist/dashboard/index.js +7 -0
- package/dist/dashboard/index.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/styles.css +35 -0
- package/package.json +15 -1
- package/src/chat-history/ChatHistoryRail.spec.tsx +138 -0
- package/src/chat-history/ChatHistoryRail.tsx +89 -0
- package/src/chat-history/index.ts +5 -0
- package/src/chat-history.css +59 -0
- package/src/composer/RealtimeVoiceMode.spec.tsx +116 -11
- package/src/composer/RealtimeVoiceMode.tsx +106 -36
- package/src/composer/VoiceButton.spec.ts +17 -1
- package/src/composer/VoiceButton.tsx +49 -0
- package/src/composer/realtime-voice-audio-level.spec.ts +15 -0
- package/src/composer/realtime-voice-audio-level.ts +2 -1
- package/src/composer/useRealtimeVoiceMode.tsx +5 -3
- package/src/dashboard/DataTable.tsx +273 -0
- package/src/dashboard/DateRangePicker.tsx +57 -0
- package/src/dashboard/GenericChartPanel.spec.tsx +53 -0
- package/src/dashboard/GenericChartPanel.tsx +461 -0
- package/src/dashboard/MetricCard.tsx +54 -0
- package/src/dashboard/StatsCard.tsx +49 -0
- package/src/dashboard/dashboard-layout.spec.ts +79 -0
- package/src/dashboard/dashboard-layout.ts +329 -0
- package/src/dashboard/index.ts +6 -0
- package/src/index.ts +1 -0
- package/src/styles.css +35 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IconArrowsUpDown,
|
|
3
|
+
IconChevronLeft,
|
|
4
|
+
IconChevronRight,
|
|
5
|
+
} from "@tabler/icons-react";
|
|
6
|
+
import { useMemo, useState } from "react";
|
|
7
|
+
|
|
8
|
+
import { Button } from "../ui/button.js";
|
|
9
|
+
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card.js";
|
|
10
|
+
import {
|
|
11
|
+
Select,
|
|
12
|
+
SelectContent,
|
|
13
|
+
SelectItem,
|
|
14
|
+
SelectTrigger,
|
|
15
|
+
SelectValue,
|
|
16
|
+
} from "../ui/select.js";
|
|
17
|
+
import { Skeleton } from "../ui/skeleton.js";
|
|
18
|
+
import {
|
|
19
|
+
Table,
|
|
20
|
+
TableBody,
|
|
21
|
+
TableCell,
|
|
22
|
+
TableHead,
|
|
23
|
+
TableHeader,
|
|
24
|
+
TableRow,
|
|
25
|
+
} from "../ui/table.js";
|
|
26
|
+
import { cn } from "../utils.js";
|
|
27
|
+
|
|
28
|
+
const DEFAULT_PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
|
|
29
|
+
const TABLE_MIN_HEIGHT_CLASS = "min-h-[386px]";
|
|
30
|
+
|
|
31
|
+
export interface DataTableLabels {
|
|
32
|
+
noData: string;
|
|
33
|
+
rowsPerPage: string;
|
|
34
|
+
of: string;
|
|
35
|
+
previousPage: string;
|
|
36
|
+
nextPage: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface DataTableProps {
|
|
40
|
+
title?: string;
|
|
41
|
+
data: Record<string, unknown>[];
|
|
42
|
+
columns?: string[];
|
|
43
|
+
loading?: boolean;
|
|
44
|
+
error?: string;
|
|
45
|
+
maxRows?: number;
|
|
46
|
+
pageSizeOptions?: number[];
|
|
47
|
+
labels?: Partial<DataTableLabels>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const DEFAULT_LABELS: DataTableLabels = {
|
|
51
|
+
noData: "No data available.",
|
|
52
|
+
rowsPerPage: "Rows per page",
|
|
53
|
+
of: "of",
|
|
54
|
+
previousPage: "Previous page",
|
|
55
|
+
nextPage: "Next page",
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** A source-agnostic, sortable and paginated dashboard table. */
|
|
59
|
+
export function DataTable({
|
|
60
|
+
title,
|
|
61
|
+
data,
|
|
62
|
+
columns: columnsProp,
|
|
63
|
+
loading,
|
|
64
|
+
error,
|
|
65
|
+
maxRows,
|
|
66
|
+
pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,
|
|
67
|
+
labels: labelOverrides,
|
|
68
|
+
}: DataTableProps) {
|
|
69
|
+
const labels = { ...DEFAULT_LABELS, ...labelOverrides };
|
|
70
|
+
const [sortColumn, setSortColumn] = useState<string | null>(null);
|
|
71
|
+
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("desc");
|
|
72
|
+
const [page, setPage] = useState(0);
|
|
73
|
+
const [pageSize, setPageSize] = useState(pageSizeOptions[0] ?? 10);
|
|
74
|
+
|
|
75
|
+
const columns = useMemo(() => {
|
|
76
|
+
if (columnsProp) return columnsProp;
|
|
77
|
+
return data.length === 0 ? [] : Object.keys(data[0]);
|
|
78
|
+
}, [columnsProp, data]);
|
|
79
|
+
const rows = useMemo(
|
|
80
|
+
() =>
|
|
81
|
+
maxRows != null && data.length > maxRows ? data.slice(0, maxRows) : data,
|
|
82
|
+
[data, maxRows],
|
|
83
|
+
);
|
|
84
|
+
const sortedRows = useMemo(() => {
|
|
85
|
+
if (!sortColumn) return rows;
|
|
86
|
+
return [...rows].sort((a, b) => {
|
|
87
|
+
const aValue = a[sortColumn];
|
|
88
|
+
const bValue = b[sortColumn];
|
|
89
|
+
if (aValue == null && bValue == null) return 0;
|
|
90
|
+
if (aValue == null) return 1;
|
|
91
|
+
if (bValue == null) return -1;
|
|
92
|
+
const comparison =
|
|
93
|
+
typeof aValue === "number" && typeof bValue === "number"
|
|
94
|
+
? aValue - bValue
|
|
95
|
+
: String(aValue).localeCompare(String(bValue));
|
|
96
|
+
return sortDirection === "asc" ? comparison : -comparison;
|
|
97
|
+
});
|
|
98
|
+
}, [rows, sortColumn, sortDirection]);
|
|
99
|
+
const pageCount = Math.ceil(sortedRows.length / pageSize);
|
|
100
|
+
const pagedRows = sortedRows.slice(page * pageSize, (page + 1) * pageSize);
|
|
101
|
+
|
|
102
|
+
const handleSort = (column: string) => {
|
|
103
|
+
if (sortColumn === column) {
|
|
104
|
+
setSortDirection((direction) => (direction === "asc" ? "desc" : "asc"));
|
|
105
|
+
} else {
|
|
106
|
+
setSortColumn(column);
|
|
107
|
+
setSortDirection("desc");
|
|
108
|
+
}
|
|
109
|
+
setPage(0);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const content = loading ? (
|
|
113
|
+
<DataTableLoadingSkeleton />
|
|
114
|
+
) : error ? (
|
|
115
|
+
<p className="py-4 text-sm text-destructive">{error}</p>
|
|
116
|
+
) : data.length === 0 ? (
|
|
117
|
+
<p className="py-4 text-center text-sm text-muted-foreground">
|
|
118
|
+
{labels.noData}
|
|
119
|
+
</p>
|
|
120
|
+
) : (
|
|
121
|
+
<div className={cn("overflow-x-auto", TABLE_MIN_HEIGHT_CLASS)}>
|
|
122
|
+
<Table>
|
|
123
|
+
<TableHeader>
|
|
124
|
+
<TableRow>
|
|
125
|
+
{columns.map((column) => (
|
|
126
|
+
<TableHead
|
|
127
|
+
key={column}
|
|
128
|
+
className="cursor-pointer select-none whitespace-nowrap hover:text-foreground"
|
|
129
|
+
onClick={() => handleSort(column)}
|
|
130
|
+
>
|
|
131
|
+
<span className="flex items-center gap-1">
|
|
132
|
+
{column}
|
|
133
|
+
<IconArrowsUpDown
|
|
134
|
+
className={cn(
|
|
135
|
+
"h-3 w-3",
|
|
136
|
+
sortColumn === column
|
|
137
|
+
? "text-foreground"
|
|
138
|
+
: "text-muted-foreground/50",
|
|
139
|
+
)}
|
|
140
|
+
/>
|
|
141
|
+
</span>
|
|
142
|
+
</TableHead>
|
|
143
|
+
))}
|
|
144
|
+
</TableRow>
|
|
145
|
+
</TableHeader>
|
|
146
|
+
<TableBody>
|
|
147
|
+
{pagedRows.map((row, index) => (
|
|
148
|
+
<TableRow key={index}>
|
|
149
|
+
{columns.map((column) => (
|
|
150
|
+
<TableCell
|
|
151
|
+
key={column}
|
|
152
|
+
className="max-w-[300px] truncate whitespace-nowrap"
|
|
153
|
+
>
|
|
154
|
+
{formatValue(row[column])}
|
|
155
|
+
</TableCell>
|
|
156
|
+
))}
|
|
157
|
+
</TableRow>
|
|
158
|
+
))}
|
|
159
|
+
</TableBody>
|
|
160
|
+
</Table>
|
|
161
|
+
{sortedRows.length > (pageSizeOptions[0] ?? 10) && (
|
|
162
|
+
<div className="flex items-center justify-between border-t border-border px-2 py-2 text-xs text-muted-foreground">
|
|
163
|
+
<div className="flex items-center gap-2">
|
|
164
|
+
<span>{labels.rowsPerPage}</span>
|
|
165
|
+
<Select
|
|
166
|
+
value={String(pageSize)}
|
|
167
|
+
onValueChange={(value) => {
|
|
168
|
+
setPageSize(Number(value));
|
|
169
|
+
setPage(0);
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
<SelectTrigger className="h-6 w-16 border-border px-2 py-0 text-xs">
|
|
173
|
+
<SelectValue />
|
|
174
|
+
</SelectTrigger>
|
|
175
|
+
<SelectContent>
|
|
176
|
+
{pageSizeOptions.map((size) => (
|
|
177
|
+
<SelectItem key={size} value={String(size)}>
|
|
178
|
+
{size}
|
|
179
|
+
</SelectItem>
|
|
180
|
+
))}
|
|
181
|
+
</SelectContent>
|
|
182
|
+
</Select>
|
|
183
|
+
</div>
|
|
184
|
+
<div className="flex items-center gap-2">
|
|
185
|
+
<span>
|
|
186
|
+
{page * pageSize + 1}–
|
|
187
|
+
{Math.min((page + 1) * pageSize, sortedRows.length)} {labels.of}{" "}
|
|
188
|
+
{sortedRows.length}
|
|
189
|
+
</span>
|
|
190
|
+
<Button
|
|
191
|
+
aria-label={labels.previousPage}
|
|
192
|
+
variant="ghost"
|
|
193
|
+
size="icon"
|
|
194
|
+
className="h-6 w-6"
|
|
195
|
+
onClick={() => setPage((current) => current - 1)}
|
|
196
|
+
disabled={page === 0}
|
|
197
|
+
>
|
|
198
|
+
<IconChevronLeft className="h-3.5 w-3.5" />
|
|
199
|
+
</Button>
|
|
200
|
+
<Button
|
|
201
|
+
aria-label={labels.nextPage}
|
|
202
|
+
variant="ghost"
|
|
203
|
+
size="icon"
|
|
204
|
+
className="h-6 w-6"
|
|
205
|
+
onClick={() => setPage((current) => current + 1)}
|
|
206
|
+
disabled={page >= pageCount - 1}
|
|
207
|
+
>
|
|
208
|
+
<IconChevronRight className="h-3.5 w-3.5" />
|
|
209
|
+
</Button>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
)}
|
|
213
|
+
</div>
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
if (!title) return content;
|
|
217
|
+
return (
|
|
218
|
+
<Card className="border-border/50 bg-card">
|
|
219
|
+
<CardHeader>
|
|
220
|
+
<CardTitle className="text-base">{title}</CardTitle>
|
|
221
|
+
</CardHeader>
|
|
222
|
+
<CardContent>{content}</CardContent>
|
|
223
|
+
</Card>
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function formatValue(value: unknown): string {
|
|
228
|
+
if (value == null) return "-";
|
|
229
|
+
if (typeof value === "number")
|
|
230
|
+
return Number.isInteger(value) ? value.toLocaleString() : value.toFixed(4);
|
|
231
|
+
return typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function DataTableLoadingSkeleton() {
|
|
235
|
+
const columnWidths = ["w-24", "w-32", "w-20", "w-28"];
|
|
236
|
+
return (
|
|
237
|
+
<div className={cn("space-y-1", TABLE_MIN_HEIGHT_CLASS)}>
|
|
238
|
+
<div className="overflow-x-auto">
|
|
239
|
+
<div className="min-w-[480px]">
|
|
240
|
+
<div className="grid h-8 grid-cols-4 items-center border-b border-border px-2">
|
|
241
|
+
{columnWidths.map((width, index) => (
|
|
242
|
+
<Skeleton key={index} className={cn("h-3", width)} />
|
|
243
|
+
))}
|
|
244
|
+
</div>
|
|
245
|
+
{Array.from({ length: 10 }).map((_, row) => (
|
|
246
|
+
<div
|
|
247
|
+
key={row}
|
|
248
|
+
className="grid h-8 grid-cols-4 items-center border-b border-border/50 px-2"
|
|
249
|
+
>
|
|
250
|
+
{columnWidths.map((width, column) => (
|
|
251
|
+
<Skeleton
|
|
252
|
+
key={column}
|
|
253
|
+
className={cn(
|
|
254
|
+
"h-3",
|
|
255
|
+
column === 0
|
|
256
|
+
? "w-36"
|
|
257
|
+
: column === 2
|
|
258
|
+
? "ml-auto w-16"
|
|
259
|
+
: width,
|
|
260
|
+
)}
|
|
261
|
+
/>
|
|
262
|
+
))}
|
|
263
|
+
</div>
|
|
264
|
+
))}
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
<div className="flex h-8 items-center justify-between border-t border-border px-2 text-xs">
|
|
268
|
+
<Skeleton className="h-3 w-28" />
|
|
269
|
+
<Skeleton className="h-3 w-24" />
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Button } from "../ui/button.js";
|
|
2
|
+
import { cn } from "../utils.js";
|
|
3
|
+
|
|
4
|
+
export type DateRange = "7d" | "30d" | "90d";
|
|
5
|
+
|
|
6
|
+
export interface DateRangeOption {
|
|
7
|
+
value: DateRange;
|
|
8
|
+
label: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface DateRangePickerProps {
|
|
12
|
+
value: DateRange;
|
|
13
|
+
onChange: (range: DateRange) => void;
|
|
14
|
+
options?: DateRangeOption[];
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const DEFAULT_DATE_RANGE_OPTIONS: DateRangeOption[] = [
|
|
19
|
+
{ value: "7d", label: "7 days" },
|
|
20
|
+
{ value: "30d", label: "30 days" },
|
|
21
|
+
{ value: "90d", label: "90 days" },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export function dateRangeToInterval(range: DateRange): number {
|
|
25
|
+
return Number.parseInt(range, 10);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function DateRangePicker({
|
|
29
|
+
value,
|
|
30
|
+
onChange,
|
|
31
|
+
options = DEFAULT_DATE_RANGE_OPTIONS,
|
|
32
|
+
className,
|
|
33
|
+
}: DateRangePickerProps) {
|
|
34
|
+
return (
|
|
35
|
+
<div
|
|
36
|
+
className={cn(
|
|
37
|
+
"flex items-center gap-1 rounded-lg border border-border p-1",
|
|
38
|
+
className,
|
|
39
|
+
)}
|
|
40
|
+
>
|
|
41
|
+
{options.map((option) => (
|
|
42
|
+
<Button
|
|
43
|
+
key={option.value}
|
|
44
|
+
variant="ghost"
|
|
45
|
+
size="sm"
|
|
46
|
+
className={cn(
|
|
47
|
+
"h-7 px-3 text-xs",
|
|
48
|
+
value === option.value && "bg-secondary text-secondary-foreground",
|
|
49
|
+
)}
|
|
50
|
+
onClick={() => onChange(option.value)}
|
|
51
|
+
>
|
|
52
|
+
{option.label}
|
|
53
|
+
</Button>
|
|
54
|
+
))}
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { AreaChart, BarChart, LineChart, PieChart } from "recharts";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
GenericChartRenderer,
|
|
6
|
+
formatGenericChartValue,
|
|
7
|
+
resolveGenericChartKeys,
|
|
8
|
+
} from "./GenericChartPanel.js";
|
|
9
|
+
|
|
10
|
+
const rows = [
|
|
11
|
+
{ day: "Mon", revenue: 12, costs: 4 },
|
|
12
|
+
{ day: "Tue", revenue: 18, costs: 7 },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
function render(
|
|
16
|
+
chartType: Parameters<typeof GenericChartRenderer>[0]["config"]["chartType"],
|
|
17
|
+
) {
|
|
18
|
+
return GenericChartRenderer({ rows, config: { chartType } });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe("GenericChartPanel renderers", () => {
|
|
22
|
+
it("derives portable x/y keys and formats provider values", () => {
|
|
23
|
+
expect(resolveGenericChartKeys(rows, {})).toEqual({
|
|
24
|
+
xKey: "day",
|
|
25
|
+
yKeys: ["revenue", "costs"],
|
|
26
|
+
});
|
|
27
|
+
expect(formatGenericChartValue("1200")).toBe("1,200");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("renders KPI metrics from an inferred numeric value", () => {
|
|
31
|
+
const element = render("metric");
|
|
32
|
+
expect(element.props.children[0].props.children).toBe("30");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it.each([
|
|
36
|
+
["line", LineChart],
|
|
37
|
+
["area", AreaChart],
|
|
38
|
+
["stacked-area", AreaChart],
|
|
39
|
+
["bar", BarChart],
|
|
40
|
+
["stacked-bar", BarChart],
|
|
41
|
+
["pie", PieChart],
|
|
42
|
+
["donut", PieChart],
|
|
43
|
+
] as const)("uses Recharts for %s", (chartType, Component) => {
|
|
44
|
+
const element = render(chartType);
|
|
45
|
+
expect(element.props.children.type).toBe(Component);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("uses the shared table renderer for tabular data", () => {
|
|
49
|
+
const element = render("table");
|
|
50
|
+
expect(element.type.name).toBe("DataTable");
|
|
51
|
+
expect(element.props.data).toBe(rows);
|
|
52
|
+
});
|
|
53
|
+
});
|