@carlonicora/nextjs-jsonapi 3.28.0 → 3.28.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/{BlockNoteEditor-ATZVHS6X.js → BlockNoteEditor-36VL2TNM.js} +9 -9
- package/dist/{BlockNoteEditor-ATZVHS6X.js.map → BlockNoteEditor-36VL2TNM.js.map} +1 -1
- package/dist/{BlockNoteEditor-CWW2D3SG.mjs → BlockNoteEditor-4IDXT6ZU.mjs} +2 -2
- package/dist/billing/index.js +335 -335
- package/dist/billing/index.mjs +1 -1
- package/dist/{chunk-2IMQ5IKA.mjs → chunk-HPUV6FBW.mjs} +4 -3
- package/dist/{chunk-2IMQ5IKA.mjs.map → chunk-HPUV6FBW.mjs.map} +1 -1
- package/dist/{chunk-YDPDJIJF.js → chunk-KOTKKBJT.js} +4 -3
- package/dist/chunk-KOTKKBJT.js.map +1 -0
- package/dist/client/index.js +2 -2
- package/dist/client/index.mjs +1 -1
- package/dist/components/index.js +2 -2
- package/dist/components/index.mjs +1 -1
- package/dist/contexts/index.js +2 -2
- package/dist/contexts/index.mjs +1 -1
- package/dist/features/analytics/index.js +88 -88
- package/dist/features/analytics/index.mjs +1 -1
- package/dist/features/help/index.js +31 -31
- package/dist/features/help/index.mjs +1 -1
- package/dist/features/tokenusage/index.js +75 -75
- package/dist/features/tokenusage/index.mjs +1 -1
- package/dist/testing/index.d.mts +6 -13
- package/dist/testing/index.d.ts +6 -13
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/index.mjs.map +1 -1
- package/package.json +44 -42
- package/src/features/company/hooks/useSubscriptionStatus.ts +6 -1
- package/src/testing/matchers/jsonApiMatchers.ts +8 -13
- package/dist/chunk-YDPDJIJF.js.map +0 -1
- /package/dist/{BlockNoteEditor-CWW2D3SG.mjs.map → BlockNoteEditor-4IDXT6ZU.mjs.map} +0 -0
|
@@ -37,7 +37,7 @@ var _chunkGKDHYB2Rjs = require('../../chunk-GKDHYB2R.js');
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
var
|
|
40
|
+
var _chunkKOTKKBJTjs = require('../../chunk-KOTKKBJT.js');
|
|
41
41
|
require('../../chunk-4Z45NYPR.js');
|
|
42
42
|
|
|
43
43
|
|
|
@@ -81,7 +81,7 @@ function TokenUsageAdminFilterBar({ granularity, companyId, metric, companies, o
|
|
|
81
81
|
};
|
|
82
82
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
83
83
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "max-md:w-full max-md:[&_#date]:w-full", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
84
|
-
|
|
84
|
+
_chunkKOTKKBJTjs.DateRangeSelector,
|
|
85
85
|
{
|
|
86
86
|
onDateChange: (range) => {
|
|
87
87
|
if (!_optionalChain([range, 'optionalAccess', _ => _.from]) || !_optionalChain([range, 'optionalAccess', _2 => _2.to])) return;
|
|
@@ -116,16 +116,16 @@ function TokenUsageAdminFilterBar({ granularity, companyId, metric, companies, o
|
|
|
116
116
|
}
|
|
117
117
|
),
|
|
118
118
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
119
|
-
|
|
119
|
+
_chunkKOTKKBJTjs.Select,
|
|
120
120
|
{
|
|
121
121
|
items: companyItems,
|
|
122
122
|
value: _nullishCoalesce(companyId, () => ( ALL_COMPANIES)),
|
|
123
123
|
onValueChange: (value) => onChange({ companyId: !value || value === ALL_COMPANIES ? void 0 : value }),
|
|
124
124
|
children: [
|
|
125
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
126
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
127
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
128
|
-
companies.map((company) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
125
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.SelectTrigger, { className: "w-56", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.SelectValue, { placeholder: t("token_usage.admin.all_companies") }) }),
|
|
126
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkKOTKKBJTjs.SelectContent, { children: [
|
|
127
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.SelectItem, { value: ALL_COMPANIES, children: t("token_usage.admin.all_companies") }),
|
|
128
|
+
companies.map((company) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.SelectItem, { value: company.id, children: company.label }, company.id))
|
|
129
129
|
] })
|
|
130
130
|
]
|
|
131
131
|
}
|
|
@@ -148,7 +148,7 @@ function Segmented({
|
|
|
148
148
|
children: options.map((option) => {
|
|
149
149
|
const selected = option.value === value;
|
|
150
150
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
151
|
-
|
|
151
|
+
_chunkKOTKKBJTjs.Button,
|
|
152
152
|
{
|
|
153
153
|
type: "button",
|
|
154
154
|
size: "sm",
|
|
@@ -230,7 +230,7 @@ var TokenUsageAdminProvider = /* @__PURE__ */ _chunk7QVYU63Ejs.__name.call(void
|
|
|
230
230
|
pageUrl = TOKEN_USAGE_ADMIN_PAGE_URL
|
|
231
231
|
}) => {
|
|
232
232
|
const t = _nextintl.useTranslations.call(void 0, );
|
|
233
|
-
const generateUrl =
|
|
233
|
+
const generateUrl = _chunkKOTKKBJTjs.usePageUrlGenerator.call(void 0, );
|
|
234
234
|
const [filters, setFilterState] = _react.useState.call(void 0, () => {
|
|
235
235
|
const range = defaultRange();
|
|
236
236
|
return {
|
|
@@ -348,7 +348,7 @@ var TokenUsageAdminProvider = /* @__PURE__ */ _chunk7QVYU63Ejs.__name.call(void
|
|
|
348
348
|
error
|
|
349
349
|
]
|
|
350
350
|
);
|
|
351
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
351
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.SharedProvider, { value: { breadcrumbs: breadcrumb(), title: title() }, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TokenUsageAdminContext.Provider, { value: contextValue, children }) });
|
|
352
352
|
}, "TokenUsageAdminProvider");
|
|
353
353
|
var useTokenUsageAdmin = /* @__PURE__ */ _chunk7QVYU63Ejs.__name.call(void 0, () => {
|
|
354
354
|
const ctx = _react.useContext.call(void 0, TokenUsageAdminContext);
|
|
@@ -445,7 +445,7 @@ function LeadTile({
|
|
|
445
445
|
previousLabel,
|
|
446
446
|
decimal
|
|
447
447
|
}) {
|
|
448
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
448
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.Card, { "data-testid": testId, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkKOTKKBJTjs.CardContent, { className: "grid gap-1", children: [
|
|
449
449
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-muted-foreground text-xs", children: label }),
|
|
450
450
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-primary text-xl font-semibold tabular-nums", children: value }),
|
|
451
451
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "flex items-center gap-1", children: [
|
|
@@ -462,7 +462,7 @@ function SupportingTile({
|
|
|
462
462
|
delta,
|
|
463
463
|
decimal
|
|
464
464
|
}) {
|
|
465
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
465
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.Card, { "data-testid": testId, size: "sm", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkKOTKKBJTjs.CardContent, { className: "grid gap-1", children: [
|
|
466
466
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-muted-foreground text-xs", children: label }),
|
|
467
467
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "flex items-center gap-2", children: [
|
|
468
468
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-sm font-medium tabular-nums", children: value }),
|
|
@@ -559,8 +559,8 @@ function TokenUsageBreakdownTable({ rows, metric }) {
|
|
|
559
559
|
// The package's <Table> wraps itself in an `overflow-x-clip` container, which
|
|
560
560
|
// would swallow the overflow before this scroller ever saw it — hence the
|
|
561
561
|
// child override. Wide metric tables must scroll, never widen the page.
|
|
562
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "overflow-x-auto [&_[data-slot=table-container]]:overflow-x-visible", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
563
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
562
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "overflow-x-auto [&_[data-slot=table-container]]:overflow-x-visible", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkKOTKKBJTjs.Table, { children: [
|
|
563
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.TableHeader, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.TableRow, { children: columns.map((column) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.TableHead, { className: _chunkCAKOFJZDjs.cn.call(void 0, column.numeric && "text-end"), children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
564
564
|
"button",
|
|
565
565
|
{
|
|
566
566
|
type: "button",
|
|
@@ -575,20 +575,20 @@ function TokenUsageBreakdownTable({ rows, metric }) {
|
|
|
575
575
|
]
|
|
576
576
|
}
|
|
577
577
|
) }, column.key)) }) }),
|
|
578
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
578
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.TableBody, { children: sortedRows.map((row) => {
|
|
579
579
|
const value = _chunkGKDHYB2Rjs.metricValue.call(void 0, row, metric);
|
|
580
580
|
const share = total > 0 ? value / total * 100 : 0;
|
|
581
|
-
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
582
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
583
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
584
|
-
showActiveUsers && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
585
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
586
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
587
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
588
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
589
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
590
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
591
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
581
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkKOTKKBJTjs.TableRow, { "data-testid": `breakdown-row-${row.id}`, children: [
|
|
582
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.TableCell, { className: "text-xs", children: row.id === "other" ? t("token_usage.admin.other") : row.label }),
|
|
583
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.TableCell, { className: "text-muted-foreground text-xs", children: _nullishCoalesce(row.sublabel, () => ( "")) }),
|
|
584
|
+
showActiveUsers && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.TableCell, { className: "text-end text-xs tabular-nums", children: row.activeUsers === void 0 ? "" : decimal(row.activeUsers, 0) }),
|
|
585
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.TableCell, { className: "text-end text-xs tabular-nums", children: decimal(row.calls, 0) }),
|
|
586
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.TableCell, { className: "text-end text-xs tabular-nums", children: decimal(row.tokensIn, 0) }),
|
|
587
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.TableCell, { className: "text-end text-xs tabular-nums", children: decimal(row.tokensOut, 0) }),
|
|
588
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.TableCell, { className: "text-end text-xs tabular-nums", children: percent(_chunkGKDHYB2Rjs.cacheHitPercentage.call(void 0, row.cached, row.tokensIn)) }),
|
|
589
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.TableCell, { className: "text-end text-xs tabular-nums", children: formatValue(row.cost, "cost") }),
|
|
590
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.TableCell, { className: "text-end text-xs tabular-nums", children: formatValue(row.credits, "credits") }),
|
|
591
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.TableCell, { className: "text-end text-xs tabular-nums", children: percent(share) })
|
|
592
592
|
] }, row.id);
|
|
593
593
|
}) })
|
|
594
594
|
] }) })
|
|
@@ -756,7 +756,7 @@ function TokenUsageTimelineChart({ rows, metric, stackBy, className }) {
|
|
|
756
756
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className, dir: "ltr", children: [
|
|
757
757
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "sr-only", "data-testid": "timeline-data", children: JSON.stringify(chartData) }),
|
|
758
758
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "sr-only", "data-testid": "timeline-series", children: JSON.stringify(seriesKeys) }),
|
|
759
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
759
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.ChartContainer, { config: chartConfig, className: "aspect-auto h-72 w-full", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _recharts.BarChart, { accessibilityLayer: true, data: chartData, margin: { top: 8, right: 8, bottom: 0, left: 0 }, children: [
|
|
760
760
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _recharts.CartesianGrid, { vertical: false }),
|
|
761
761
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
762
762
|
_recharts.XAxis,
|
|
@@ -780,10 +780,10 @@ function TokenUsageTimelineChart({ rows, metric, stackBy, className }) {
|
|
|
780
780
|
}
|
|
781
781
|
),
|
|
782
782
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
783
|
-
|
|
783
|
+
_chunkKOTKKBJTjs.ChartTooltip,
|
|
784
784
|
{
|
|
785
785
|
content: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
786
|
-
|
|
786
|
+
_chunkKOTKKBJTjs.ChartTooltipContent,
|
|
787
787
|
{
|
|
788
788
|
labelFormatter: (value) => bucketDate(String(value), granularity),
|
|
789
789
|
valueFormatter: (value) => formatValue(Number(value), metric),
|
|
@@ -792,7 +792,7 @@ function TokenUsageTimelineChart({ rows, metric, stackBy, className }) {
|
|
|
792
792
|
)
|
|
793
793
|
}
|
|
794
794
|
),
|
|
795
|
-
seriesKeys.length >= 2 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
795
|
+
seriesKeys.length >= 2 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.ChartLegend, { content: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.ChartLegendContent, {}) }) : null,
|
|
796
796
|
seriesKeys.map((series, index) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
797
797
|
_recharts.Bar,
|
|
798
798
|
{
|
|
@@ -844,19 +844,19 @@ function TokenUsageAdminContainer() {
|
|
|
844
844
|
[t]
|
|
845
845
|
);
|
|
846
846
|
if (error) {
|
|
847
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
847
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.RoundPageContainer, { fullWidth: true, forceHeader: true, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "p-4", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.Card, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "text-destructive text-xs/relaxed", children: error }) }) }) }) });
|
|
848
848
|
}
|
|
849
|
-
if (isLoading) return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
849
|
+
if (isLoading) return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.RoundPageContainer, { fullWidth: true, forceHeader: true });
|
|
850
850
|
const emptyLabel = t("token_usage.admin.no_data");
|
|
851
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
851
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.RoundPageContainer, { fullWidth: true, forceHeader: true, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex w-full flex-col gap-4 p-4", children: [
|
|
852
852
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, TokenUsageAdminTiles, { summary, metric: filters.metric, singleCustomerMode: hidePlatform }),
|
|
853
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
854
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
855
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
856
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
853
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkKOTKKBJTjs.Card, { children: [
|
|
854
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkKOTKKBJTjs.CardHeader, { children: [
|
|
855
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardTitle, { children: t("token_usage.admin.usage_over_time") }),
|
|
856
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardAction, { children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex items-center gap-2", children: [
|
|
857
857
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-muted-foreground text-xs", children: t("token_usage.admin.stack_by") }),
|
|
858
858
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
859
|
-
|
|
859
|
+
_chunkKOTKKBJTjs.Select,
|
|
860
860
|
{
|
|
861
861
|
items: stackByItems,
|
|
862
862
|
value: filters.stackBy,
|
|
@@ -864,29 +864,29 @@ function TokenUsageAdminContainer() {
|
|
|
864
864
|
if (value) setFilters({ stackBy: value });
|
|
865
865
|
},
|
|
866
866
|
children: [
|
|
867
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
868
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
867
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.SelectTrigger, { size: "sm", className: "w-36", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.SelectValue, {}) }),
|
|
868
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.SelectContent, { children: STACK_BY_VALUES.map((value) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.SelectItem, { value, children: stackByItems[value] }, value)) })
|
|
869
869
|
]
|
|
870
870
|
}
|
|
871
871
|
)
|
|
872
872
|
] }) })
|
|
873
873
|
] }),
|
|
874
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
874
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TokenUsageTimelineChart, { rows: timeline, metric: filters.metric, stackBy: filters.stackBy }) })
|
|
875
875
|
] }),
|
|
876
876
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "grid gap-4 md:grid-cols-2", children: [
|
|
877
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
878
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
879
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
877
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkKOTKKBJTjs.Card, { children: [
|
|
878
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardHeader, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardTitle, { children: t("token_usage.admin.by_company") }) }),
|
|
879
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TokenUsageRankedBar, { rows: byCompany, metric: filters.metric, emptyLabel }) })
|
|
880
880
|
] }),
|
|
881
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
882
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
883
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
881
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkKOTKKBJTjs.Card, { children: [
|
|
882
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardHeader, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardTitle, { children: t("token_usage.admin.by_user") }) }),
|
|
883
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TokenUsageRankedBar, { rows: byUser, metric: filters.metric, emptyLabel }) })
|
|
884
884
|
] })
|
|
885
885
|
] }),
|
|
886
886
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkCAKOFJZDjs.cn.call(void 0, "grid gap-4", !hidePlatform && "md:grid-cols-2"), children: [
|
|
887
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
888
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
889
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
887
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkKOTKKBJTjs.Card, { children: [
|
|
888
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardHeader, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardTitle, { children: t("token_usage.admin.customer_by_operation") }) }),
|
|
889
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
890
890
|
TokenUsageRankedBar,
|
|
891
891
|
{
|
|
892
892
|
rows: byCustomerOperation,
|
|
@@ -896,9 +896,9 @@ function TokenUsageAdminContainer() {
|
|
|
896
896
|
}
|
|
897
897
|
) })
|
|
898
898
|
] }),
|
|
899
|
-
!hidePlatform && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
900
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
901
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
899
|
+
!hidePlatform && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkKOTKKBJTjs.Card, { children: [
|
|
900
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardHeader, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardTitle, { children: t("token_usage.admin.platform_by_operation") }) }),
|
|
901
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
902
902
|
TokenUsageRankedBar,
|
|
903
903
|
{
|
|
904
904
|
rows: byOperation,
|
|
@@ -909,9 +909,9 @@ function TokenUsageAdminContainer() {
|
|
|
909
909
|
) })
|
|
910
910
|
] })
|
|
911
911
|
] }),
|
|
912
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
913
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
914
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
912
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkKOTKKBJTjs.Card, { children: [
|
|
913
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardHeader, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardTitle, { children: t("token_usage.admin.detail") }) }),
|
|
914
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TokenUsageBreakdownTable, { rows: byCompany, metric: filters.metric }) })
|
|
915
915
|
] })
|
|
916
916
|
] }) });
|
|
917
917
|
}
|
|
@@ -1032,7 +1032,7 @@ var TOKEN_USAGE_REPORT_I18N_KEYS = [
|
|
|
1032
1032
|
|
|
1033
1033
|
function TokenUsageReportFilterBar({ onChange }) {
|
|
1034
1034
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { "data-help": "tokenusage.filters", className: "flex flex-wrap items-center gap-2", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1035
|
-
|
|
1035
|
+
_chunkKOTKKBJTjs.DateRangeSelector,
|
|
1036
1036
|
{
|
|
1037
1037
|
onDateChange: (range) => {
|
|
1038
1038
|
if (!_optionalChain([range, 'optionalAccess', _5 => _5.from]) || !_optionalChain([range, 'optionalAccess', _6 => _6.to])) return;
|
|
@@ -1065,7 +1065,7 @@ var TokenUsageReportProvider = /* @__PURE__ */ _chunk7QVYU63Ejs.__name.call(void
|
|
|
1065
1065
|
pageUrl = TOKEN_USAGE_REPORT_PAGE_URL
|
|
1066
1066
|
}) => {
|
|
1067
1067
|
const t = _nextintl.useTranslations.call(void 0, );
|
|
1068
|
-
const generateUrl =
|
|
1068
|
+
const generateUrl = _chunkKOTKKBJTjs.usePageUrlGenerator.call(void 0, );
|
|
1069
1069
|
const [filters, setFilterState] = _react.useState.call(void 0, () => {
|
|
1070
1070
|
const range = defaultRange2();
|
|
1071
1071
|
return { from: _nullishCoalesce(initialFrom, () => ( range.from)), to: _nullishCoalesce(initialTo, () => ( range.to)) };
|
|
@@ -1129,7 +1129,7 @@ var TokenUsageReportProvider = /* @__PURE__ */ _chunk7QVYU63Ejs.__name.call(void
|
|
|
1129
1129
|
() => ({ summary, timeline, byOperation, byTarget, targetPanelTitleKey, filters, setFilters, isLoading, error }),
|
|
1130
1130
|
[summary, timeline, byOperation, byTarget, targetPanelTitleKey, filters, setFilters, isLoading, error]
|
|
1131
1131
|
);
|
|
1132
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1132
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.SharedProvider, { value: { breadcrumbs: breadcrumb(), title: title() }, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TokenUsageReportContext.Provider, { value: contextValue, children }) });
|
|
1133
1133
|
}, "TokenUsageReportProvider");
|
|
1134
1134
|
var useTokenUsageReport = /* @__PURE__ */ _chunk7QVYU63Ejs.__name.call(void 0, () => {
|
|
1135
1135
|
const ctx = _react.useContext.call(void 0, TokenUsageReportContext);
|
|
@@ -1157,7 +1157,7 @@ function TokenUsageReportTiles({ summary, metric, balances }) {
|
|
|
1157
1157
|
const monthlyColor = monthlyPercentage > 75 ? "text-success" : monthlyPercentage >= 5 ? "text-warning" : "text-destructive";
|
|
1158
1158
|
const whole = /* @__PURE__ */ _chunk7QVYU63Ejs.__name.call(void 0, (value) => decimal(Math.max(0, Math.floor(value)), 0), "whole");
|
|
1159
1159
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "grid gap-3", children: [
|
|
1160
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1160
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.Card, { "data-testid": "tile-used", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkKOTKKBJTjs.CardContent, { className: "grid gap-1", children: [
|
|
1161
1161
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-muted-foreground text-xs", children: t("token_usage.report.used_in_period") }),
|
|
1162
1162
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-primary text-xl font-semibold tabular-nums", children: formatValue(_chunkGKDHYB2Rjs.metricValue.call(void 0, current, metric), metric) }),
|
|
1163
1163
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "flex items-center gap-1", children: [
|
|
@@ -1173,7 +1173,7 @@ function TokenUsageReportTiles({ summary, metric, balances }) {
|
|
|
1173
1173
|
] })
|
|
1174
1174
|
] }) }),
|
|
1175
1175
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "grid gap-3 sm:grid-cols-3", children: [
|
|
1176
|
-
balances && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1176
|
+
balances && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.Card, { "data-testid": "tile-monthly", size: "sm", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkKOTKKBJTjs.CardContent, { className: "grid gap-1", children: [
|
|
1177
1177
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-muted-foreground text-xs", children: t("token_usage.report.monthly_left") }),
|
|
1178
1178
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "span", { className: "flex items-baseline gap-1", children: [
|
|
1179
1179
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { "data-testid": "tile-monthly-value", className: _chunkCAKOFJZDjs.cn.call(void 0, "text-sm font-medium tabular-nums", monthlyColor), children: whole(balances.availableMonthlyCredits) }),
|
|
@@ -1183,11 +1183,11 @@ function TokenUsageReportTiles({ summary, metric, balances }) {
|
|
|
1183
1183
|
] })
|
|
1184
1184
|
] })
|
|
1185
1185
|
] }) }),
|
|
1186
|
-
balances && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1186
|
+
balances && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.Card, { "data-testid": "tile-extra", size: "sm", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkKOTKKBJTjs.CardContent, { className: "grid gap-1", children: [
|
|
1187
1187
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-muted-foreground text-xs", children: t("token_usage.report.extra_credits") }),
|
|
1188
1188
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-sm font-medium tabular-nums", children: whole(balances.availableExtraCredits) })
|
|
1189
1189
|
] }) }),
|
|
1190
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1190
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.Card, { "data-testid": "tile-calls", size: "sm", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkKOTKKBJTjs.CardContent, { className: "grid gap-1", children: [
|
|
1191
1191
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-muted-foreground text-xs", children: t("token_usage.report.calls") }),
|
|
1192
1192
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-sm font-medium tabular-nums", children: decimal(current.calls, 0) })
|
|
1193
1193
|
] }) })
|
|
@@ -1229,21 +1229,21 @@ function TokenUsageReportContainer({ balances = null }) {
|
|
|
1229
1229
|
const t = _nextintl.useTranslations.call(void 0, );
|
|
1230
1230
|
const { summary, timeline, byOperation, byTarget, isLoading, error, targetPanelTitleKey } = useTokenUsageReport();
|
|
1231
1231
|
if (error) {
|
|
1232
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1232
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.RoundPageContainer, { fullWidth: true, forceHeader: true, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "p-4", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.Card, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "text-destructive text-xs/relaxed", children: error }) }) }) }) });
|
|
1233
1233
|
}
|
|
1234
|
-
if (isLoading) return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1234
|
+
if (isLoading) return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.RoundPageContainer, { fullWidth: true, forceHeader: true });
|
|
1235
1235
|
const emptyLabel = t("token_usage.report.no_data");
|
|
1236
1236
|
const targetTitle = targetPanelTitleKey && byTarget.length > 0 ? t(targetPanelTitleKey) : void 0;
|
|
1237
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1237
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.RoundPageContainer, { fullWidth: true, forceHeader: true, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex w-full flex-col gap-4 p-4", children: [
|
|
1238
1238
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, TokenUsageReportTiles, { summary, metric: "credits", balances }),
|
|
1239
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
1240
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1241
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1239
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkKOTKKBJTjs.Card, { children: [
|
|
1240
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardHeader, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardTitle, { children: t("token_usage.report.usage_over_time") }) }),
|
|
1241
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TokenUsageTimelineChart, { rows: timeline, metric: "credits", stackBy: "type" }) })
|
|
1242
1242
|
] }),
|
|
1243
1243
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkCAKOFJZDjs.cn.call(void 0, "grid gap-4", targetTitle && "md:grid-cols-2"), children: [
|
|
1244
|
-
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
1245
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1246
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1244
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkKOTKKBJTjs.Card, { children: [
|
|
1245
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardHeader, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardTitle, { children: t("token_usage.report.by_operation") }) }),
|
|
1246
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1247
1247
|
TokenUsageRankedBar,
|
|
1248
1248
|
{
|
|
1249
1249
|
rows: byOperation,
|
|
@@ -1253,9 +1253,9 @@ function TokenUsageReportContainer({ balances = null }) {
|
|
|
1253
1253
|
}
|
|
1254
1254
|
) })
|
|
1255
1255
|
] }),
|
|
1256
|
-
targetTitle && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
1257
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1258
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
1256
|
+
targetTitle && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkKOTKKBJTjs.Card, { children: [
|
|
1257
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardHeader, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardTitle, { children: targetTitle }) }),
|
|
1258
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKOTKKBJTjs.CardContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TokenUsageRankedBar, { rows: byTarget, metric: "credits", emptyLabel }) })
|
|
1259
1259
|
] })
|
|
1260
1260
|
] })
|
|
1261
1261
|
] }) });
|
package/dist/testing/index.d.mts
CHANGED
|
@@ -263,19 +263,12 @@ declare const jsonApiMatchers: {
|
|
|
263
263
|
};
|
|
264
264
|
};
|
|
265
265
|
declare module "vitest" {
|
|
266
|
-
interface
|
|
267
|
-
toBeValidJsonApi():
|
|
268
|
-
toHaveJsonApiType(expectedType: string):
|
|
269
|
-
toHaveJsonApiAttribute(attributeName: string, expectedValue?: any):
|
|
270
|
-
toHaveJsonApiRelationship(relationshipName: string):
|
|
271
|
-
toHaveJsonApiLength(expectedLength: number):
|
|
272
|
-
}
|
|
273
|
-
interface AsymmetricMatchersContaining {
|
|
274
|
-
toBeValidJsonApi(): any;
|
|
275
|
-
toHaveJsonApiType(expectedType: string): any;
|
|
276
|
-
toHaveJsonApiAttribute(attributeName: string, expectedValue?: any): any;
|
|
277
|
-
toHaveJsonApiRelationship(relationshipName: string): any;
|
|
278
|
-
toHaveJsonApiLength(expectedLength: number): any;
|
|
266
|
+
interface Matchers<R extends void | Promise<void> = void | Promise<void>, T = unknown> {
|
|
267
|
+
toBeValidJsonApi(): R;
|
|
268
|
+
toHaveJsonApiType(expectedType: string): R;
|
|
269
|
+
toHaveJsonApiAttribute(attributeName: string, expectedValue?: any): R;
|
|
270
|
+
toHaveJsonApiRelationship(relationshipName: string): R;
|
|
271
|
+
toHaveJsonApiLength(expectedLength: number): R;
|
|
279
272
|
}
|
|
280
273
|
}
|
|
281
274
|
/**
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -263,19 +263,12 @@ declare const jsonApiMatchers: {
|
|
|
263
263
|
};
|
|
264
264
|
};
|
|
265
265
|
declare module "vitest" {
|
|
266
|
-
interface
|
|
267
|
-
toBeValidJsonApi():
|
|
268
|
-
toHaveJsonApiType(expectedType: string):
|
|
269
|
-
toHaveJsonApiAttribute(attributeName: string, expectedValue?: any):
|
|
270
|
-
toHaveJsonApiRelationship(relationshipName: string):
|
|
271
|
-
toHaveJsonApiLength(expectedLength: number):
|
|
272
|
-
}
|
|
273
|
-
interface AsymmetricMatchersContaining {
|
|
274
|
-
toBeValidJsonApi(): any;
|
|
275
|
-
toHaveJsonApiType(expectedType: string): any;
|
|
276
|
-
toHaveJsonApiAttribute(attributeName: string, expectedValue?: any): any;
|
|
277
|
-
toHaveJsonApiRelationship(relationshipName: string): any;
|
|
278
|
-
toHaveJsonApiLength(expectedLength: number): any;
|
|
266
|
+
interface Matchers<R extends void | Promise<void> = void | Promise<void>, T = unknown> {
|
|
267
|
+
toBeValidJsonApi(): R;
|
|
268
|
+
toHaveJsonApiType(expectedType: string): R;
|
|
269
|
+
toHaveJsonApiAttribute(attributeName: string, expectedValue?: any): R;
|
|
270
|
+
toHaveJsonApiRelationship(relationshipName: string): R;
|
|
271
|
+
toHaveJsonApiLength(expectedLength: number): R;
|
|
279
272
|
}
|
|
280
273
|
}
|
|
281
274
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/testing/index.js","../../src/testing/providers/MockJsonApiProvider.tsx","../../src/testing/factories/createMockModule.ts","../../src/testing/factories/createMockResponse.ts","../../src/testing/factories/createMockService.ts","../../src/testing/factories/createMockApiData.ts","../../src/testing/matchers/jsonApiMatchers.ts","../../src/testing/utils/renderWithProviders.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/testing/index.js","sourcesContent":[null,"\"use client\";\n\nimport React from \"react\";\nimport { JsonApiConfig, JsonApiContext } from \"../../client/context/JsonApiContext\";\n\nexport interface MockJsonApiProviderProps {\n children: React.ReactNode;\n config?: Partial<JsonApiConfig>;\n}\n\nconst defaultMockConfig: JsonApiConfig = {\n apiUrl: \"https://api.test.com\",\n tokenGetter: async () => \"mock-token-for-testing\",\n languageGetter: async () => \"en\",\n defaultHeaders: {},\n onError: () => {},\n cacheConfig: {\n defaultProfile: \"default\",\n },\n};\n\n/**\n * A test-friendly provider that wraps components with mock JSON:API context.\n *\n * @example\n * ```tsx\n * import { MockJsonApiProvider } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * render(\n * <MockJsonApiProvider>\n * <MyComponent />\n * </MockJsonApiProvider>\n * );\n * ```\n *\n * @example With custom config\n * ```tsx\n * render(\n * <MockJsonApiProvider config={{ apiUrl: 'https://custom.api.com' }}>\n * <MyComponent />\n * </MockJsonApiProvider>\n * );\n * ```\n */\nexport function MockJsonApiProvider({ children, config }: MockJsonApiProviderProps) {\n const mergedConfig: JsonApiConfig = {\n ...defaultMockConfig,\n ...config,\n };\n\n return <JsonApiContext.Provider value={mergedConfig}>{children}</JsonApiContext.Provider>;\n}\n\nexport { defaultMockConfig };\n","import { ApiRequestDataTypeInterface } from \"../../core/interfaces/ApiRequestDataTypeInterface\";\n\nexport interface CreateMockModuleOptions {\n name: string;\n cache?: string;\n inclusions?: ApiRequestDataTypeInterface[\"inclusions\"];\n}\n\n/**\n * Creates a mock module definition for testing.\n *\n * @example\n * ```ts\n * import { createMockModule } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockArticleModule = createMockModule({ name: 'articles' });\n * ```\n */\nexport function createMockModule(options: CreateMockModuleOptions): ApiRequestDataTypeInterface {\n // Create a mock model class\n class MockModel {\n id = \"mock-id\";\n type = options.name;\n }\n\n return {\n name: options.name,\n cache: options.cache,\n inclusions: options.inclusions,\n model: MockModel,\n };\n}\n","import { ApiResponseInterface } from \"../../core/interfaces/ApiResponseInterface\";\nimport { ApiDataInterface } from \"../../core/interfaces/ApiDataInterface\";\n\nexport interface CreateMockResponseOptions {\n data?: ApiDataInterface | ApiDataInterface[] | null;\n ok?: boolean;\n response?: number;\n error?: string;\n meta?: Record<string, any>;\n self?: string;\n next?: string;\n prev?: string;\n}\n\n/**\n * Creates a mock API response for testing.\n *\n * @example\n * ```ts\n * import { createMockResponse, createMockApiData } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockData = createMockApiData({ type: 'articles', id: '1' });\n * const response = createMockResponse({ data: mockData, ok: true });\n * ```\n *\n * @example With pagination\n * ```ts\n * const response = createMockResponse({\n * data: [mockData],\n * ok: true,\n * next: '/articles?page=2',\n * prev: '/articles?page=0',\n * });\n * ```\n */\nexport function createMockResponse(options: CreateMockResponseOptions = {}): ApiResponseInterface {\n const {\n data = null,\n ok = true,\n response = ok ? 200 : 500,\n error = ok ? \"\" : \"Error\",\n meta,\n self,\n next,\n prev,\n } = options;\n\n const mockResponse: ApiResponseInterface = {\n ok,\n response,\n data: data as ApiDataInterface | ApiDataInterface[],\n error,\n meta,\n self,\n next,\n prev,\n };\n\n // Add pagination methods if next/prev provided\n if (next) {\n mockResponse.nextPage = async () => createMockResponse({ ...options, next: undefined, prev: self });\n }\n\n if (prev) {\n mockResponse.prevPage = async () => createMockResponse({ ...options, prev: undefined, next: self });\n }\n\n return mockResponse;\n}\n\n/**\n * Creates a mock error response for testing error scenarios.\n *\n * @example\n * ```ts\n * import { createMockErrorResponse } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const errorResponse = createMockErrorResponse(404, 'Not Found');\n * ```\n */\nexport function createMockErrorResponse(statusCode: number, errorMessage: string): ApiResponseInterface {\n return createMockResponse({\n ok: false,\n response: statusCode,\n error: errorMessage,\n data: null,\n });\n}\n","import { vi, type Mock } from \"vitest\";\nimport { ApiResponseInterface } from \"../../core/interfaces/ApiResponseInterface\";\nimport { createMockResponse } from \"./createMockResponse\";\n\nexport type MockApiMethod = Mock<(...args: any[]) => Promise<ApiResponseInterface>>;\n\nexport interface MockService {\n get: MockApiMethod;\n post: MockApiMethod;\n put: MockApiMethod;\n patch: MockApiMethod;\n delete: MockApiMethod;\n}\n\nexport interface CreateMockServiceOptions {\n defaultResponse?: ApiResponseInterface;\n}\n\n/**\n * Creates a mock service with Vitest mock functions for all HTTP methods.\n *\n * @example\n * ```ts\n * import { createMockService, createMockResponse } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockService = createMockService();\n * mockService.get.mockResolvedValue(createMockResponse({ data: mockData }));\n *\n * // Use in test\n * expect(mockService.get).toHaveBeenCalled();\n * ```\n *\n * @example With default response\n * ```ts\n * const mockService = createMockService({\n * defaultResponse: createMockResponse({ ok: true, data: [] }),\n * });\n * ```\n */\nexport function createMockService(options: CreateMockServiceOptions = {}): MockService {\n const defaultResponse = options.defaultResponse ?? createMockResponse({ ok: true });\n\n return {\n get: vi.fn().mockResolvedValue(defaultResponse),\n post: vi.fn().mockResolvedValue(defaultResponse),\n put: vi.fn().mockResolvedValue(defaultResponse),\n patch: vi.fn().mockResolvedValue(defaultResponse),\n delete: vi.fn().mockResolvedValue(defaultResponse),\n };\n}\n\n/**\n * Creates a mock service that returns errors for all methods.\n *\n * @example\n * ```ts\n * import { createMockErrorService } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const errorService = createMockErrorService(500, 'Internal Server Error');\n * ```\n */\nexport function createMockErrorService(statusCode: number = 500, errorMessage: string = \"Error\"): MockService {\n const errorResponse = createMockResponse({\n ok: false,\n response: statusCode,\n error: errorMessage,\n });\n\n return {\n get: vi.fn().mockResolvedValue(errorResponse),\n post: vi.fn().mockResolvedValue(errorResponse),\n put: vi.fn().mockResolvedValue(errorResponse),\n patch: vi.fn().mockResolvedValue(errorResponse),\n delete: vi.fn().mockResolvedValue(errorResponse),\n };\n}\n","import { ApiDataInterface } from \"../../core/interfaces/ApiDataInterface\";\n\nexport interface CreateMockApiDataOptions {\n type: string;\n id?: string;\n attributes?: Record<string, any>;\n relationships?: Record<string, any>;\n included?: any[];\n createdAt?: Date;\n updatedAt?: Date;\n self?: string;\n}\n\n/**\n * Creates a mock ApiDataInterface object for testing.\n *\n * @example\n * ```ts\n * import { createMockApiData } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockArticle = createMockApiData({\n * type: 'articles',\n * id: '1',\n * attributes: { title: 'Test Article', body: 'Content here' },\n * });\n * ```\n *\n * @example With relationships\n * ```ts\n * const mockArticle = createMockApiData({\n * type: 'articles',\n * id: '1',\n * attributes: { title: 'Test' },\n * relationships: {\n * author: { data: { type: 'users', id: '42' } },\n * },\n * });\n * ```\n */\nexport function createMockApiData(options: CreateMockApiDataOptions): ApiDataInterface {\n const {\n type,\n id = `mock-${type}-${Math.random().toString(36).substring(7)}`,\n attributes = {},\n relationships = {},\n included = [],\n createdAt = new Date(),\n updatedAt = new Date(),\n self,\n } = options;\n\n const jsonApiData = {\n type,\n id,\n attributes: {\n ...attributes,\n createdAt: createdAt.toISOString(),\n updatedAt: updatedAt.toISOString(),\n },\n relationships,\n };\n\n const mockData: ApiDataInterface = {\n get included() {\n return included;\n },\n get type() {\n return type;\n },\n get id() {\n return id;\n },\n get createdAt() {\n return createdAt;\n },\n get updatedAt() {\n return updatedAt;\n },\n get self() {\n return self;\n },\n get jsonApi() {\n return jsonApiData;\n },\n generateApiUrl: (params?: any) => {\n const baseUrl = `/${type}/${id}`;\n if (params) {\n const searchParams = new URLSearchParams(params);\n return `${baseUrl}?${searchParams.toString()}`;\n }\n return baseUrl;\n },\n dehydrate: () => ({\n jsonApi: jsonApiData,\n included,\n allData: [jsonApiData],\n }),\n rehydrate: function (_data: any) {\n return this;\n },\n createJsonApi: (data: any) => ({\n type,\n id,\n attributes: data,\n }),\n get identifier() {\n const identifierFields = [\"name\"];\n return identifierFields\n .map((field) => attributes[field])\n .filter((v) => v != null && v !== \"\")\n .join(\" \");\n },\n };\n\n // Add attribute accessors to the mock object\n Object.keys(attributes).forEach((key) => {\n Object.defineProperty(mockData, key, {\n get: () => attributes[key],\n enumerable: true,\n });\n });\n\n return mockData;\n}\n\n/**\n * Creates an array of mock ApiDataInterface objects.\n *\n * @example\n * ```ts\n * import { createMockApiDataList } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockArticles = createMockApiDataList('articles', 5, (index) => ({\n * title: `Article ${index + 1}`,\n * }));\n * ```\n */\nexport function createMockApiDataList(\n type: string,\n count: number,\n attributesFactory?: (index: number) => Record<string, any>,\n): ApiDataInterface[] {\n return Array.from({ length: count }, (_, index) =>\n createMockApiData({\n type,\n id: `${index + 1}`,\n attributes: attributesFactory?.(index) ?? {},\n }),\n );\n}\n","import { expect } from \"vitest\";\n\ninterface JsonApiResponse {\n data?:\n | {\n type?: string;\n id?: string;\n attributes?: Record<string, any>;\n relationships?: Record<string, any>;\n }\n | Array<{\n type?: string;\n id?: string;\n attributes?: Record<string, any>;\n relationships?: Record<string, any>;\n }>;\n}\n\n/**\n * Custom Vitest matchers for JSON:API assertions.\n *\n * @example\n * ```ts\n * import { jsonApiMatchers } from '@carlonicora/nextjs-jsonapi/testing';\n * import { expect } from 'vitest';\n *\n * expect.extend(jsonApiMatchers);\n *\n * // Then use in tests:\n * expect(response).toBeValidJsonApi();\n * expect(response).toHaveJsonApiType('articles');\n * expect(response).toHaveJsonApiAttribute('title', 'My Article');\n * expect(response).toHaveJsonApiRelationship('author');\n * ```\n */\nexport const jsonApiMatchers = {\n /**\n * Asserts that the response has a valid JSON:API structure with type and id.\n */\n toBeValidJsonApi(received: JsonApiResponse) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const hasType = typeof data?.type === \"string\" && data.type.length > 0;\n const hasId = typeof data?.id === \"string\" && data.id.length > 0;\n const isValid = hasType && hasId;\n\n return {\n pass: isValid,\n message: () =>\n isValid\n ? `Expected response not to be valid JSON:API, but it has type \"${data?.type}\" and id \"${data?.id}\"`\n : `Expected response to be valid JSON:API with type and id, but got type: ${JSON.stringify(data?.type)}, id: ${JSON.stringify(data?.id)}`,\n };\n },\n\n /**\n * Asserts that the response data has the expected JSON:API type.\n */\n toHaveJsonApiType(received: JsonApiResponse, expectedType: string) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const actualType = data?.type;\n const pass = actualType === expectedType;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected response not to have JSON:API type \"${expectedType}\"`\n : `Expected response to have JSON:API type \"${expectedType}\", but got \"${actualType}\"`,\n };\n },\n\n /**\n * Asserts that the response data has an attribute with the expected value.\n */\n toHaveJsonApiAttribute(received: JsonApiResponse, attributeName: string, expectedValue?: any) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const attributes = data?.attributes ?? {};\n const hasAttribute = attributeName in attributes;\n const actualValue = attributes[attributeName];\n\n if (expectedValue === undefined) {\n // Just check existence\n return {\n pass: hasAttribute,\n message: () =>\n hasAttribute\n ? `Expected response not to have JSON:API attribute \"${attributeName}\"`\n : `Expected response to have JSON:API attribute \"${attributeName}\", but it was not found. Available attributes: ${Object.keys(attributes).join(\", \") || \"none\"}`,\n };\n }\n\n const valuesMatch = actualValue === expectedValue;\n return {\n pass: hasAttribute && valuesMatch,\n message: () =>\n hasAttribute && valuesMatch\n ? `Expected response not to have JSON:API attribute \"${attributeName}\" with value \"${expectedValue}\"`\n : !hasAttribute\n ? `Expected response to have JSON:API attribute \"${attributeName}\", but it was not found`\n : `Expected JSON:API attribute \"${attributeName}\" to be \"${expectedValue}\", but got \"${actualValue}\"`,\n };\n },\n\n /**\n * Asserts that the response data has the specified relationship.\n */\n toHaveJsonApiRelationship(received: JsonApiResponse, relationshipName: string) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const relationships = data?.relationships ?? {};\n const hasRelationship = relationshipName in relationships;\n\n return {\n pass: hasRelationship,\n message: () =>\n hasRelationship\n ? `Expected response not to have JSON:API relationship \"${relationshipName}\"`\n : `Expected response to have JSON:API relationship \"${relationshipName}\", but it was not found. Available relationships: ${Object.keys(relationships).join(\", \") || \"none\"}`,\n };\n },\n\n /**\n * Asserts that the response data array has the expected length.\n */\n toHaveJsonApiLength(received: JsonApiResponse, expectedLength: number) {\n const data = received?.data;\n const isArray = Array.isArray(data);\n const actualLength = isArray ? data.length : data ? 1 : 0;\n const pass = actualLength === expectedLength;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected response not to have ${expectedLength} items`\n : `Expected response to have ${expectedLength} items, but got ${actualLength}`,\n };\n },\n};\n\n// Type declarations for the custom matchers\ndeclare module \"vitest\" {\n interface Assertion<T = any> {\n toBeValidJsonApi(): T;\n toHaveJsonApiType(expectedType: string): T;\n toHaveJsonApiAttribute(attributeName: string, expectedValue?: any): T;\n toHaveJsonApiRelationship(relationshipName: string): T;\n toHaveJsonApiLength(expectedLength: number): T;\n }\n interface AsymmetricMatchersContaining {\n toBeValidJsonApi(): any;\n toHaveJsonApiType(expectedType: string): any;\n toHaveJsonApiAttribute(attributeName: string, expectedValue?: any): any;\n toHaveJsonApiRelationship(relationshipName: string): any;\n toHaveJsonApiLength(expectedLength: number): any;\n }\n}\n\n/**\n * Extends Vitest's expect with JSON:API matchers.\n * Call this in your test setup file.\n *\n * @example\n * ```ts\n * // vitest.setup.ts\n * import { extendExpectWithJsonApiMatchers } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * extendExpectWithJsonApiMatchers();\n * ```\n */\nexport function extendExpectWithJsonApiMatchers(): void {\n expect.extend(jsonApiMatchers);\n}\n","\"use client\";\n\nimport React, { ReactElement } from \"react\";\nimport { render, RenderOptions, RenderResult } from \"@testing-library/react\";\nimport {\n MockJsonApiProvider,\n MockJsonApiProviderProps as _MockJsonApiProviderProps,\n} from \"../providers/MockJsonApiProvider\";\nimport { JsonApiConfig } from \"../../client/context/JsonApiContext\";\n\nexport interface RenderWithProvidersOptions extends Omit<RenderOptions, \"wrapper\"> {\n /**\n * Custom JSON:API configuration to pass to the mock provider.\n */\n jsonApiConfig?: Partial<JsonApiConfig>;\n\n /**\n * Additional wrapper component to wrap around the providers.\n */\n wrapper?: React.ComponentType<{ children: React.ReactNode }>;\n}\n\n/**\n * Renders a component wrapped with all necessary providers for testing.\n *\n * @example\n * ```tsx\n * import { renderWithProviders } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const { getByText } = renderWithProviders(<MyComponent />);\n * expect(getByText('Hello')).toBeInTheDocument();\n * ```\n *\n * @example With custom config\n * ```tsx\n * const { getByText } = renderWithProviders(<MyComponent />, {\n * jsonApiConfig: { apiUrl: 'https://custom.api.com' },\n * });\n * ```\n *\n * @example With additional wrapper\n * ```tsx\n * const CustomWrapper = ({ children }) => (\n * <ThemeProvider>{children}</ThemeProvider>\n * );\n *\n * const { getByText } = renderWithProviders(<MyComponent />, {\n * wrapper: CustomWrapper,\n * });\n * ```\n */\nexport function renderWithProviders(ui: ReactElement, options: RenderWithProvidersOptions = {}): RenderResult {\n const { jsonApiConfig, wrapper: AdditionalWrapper, ...renderOptions } = options;\n\n function AllProviders({ children }: { children: React.ReactNode }) {\n const content = <MockJsonApiProvider config={jsonApiConfig}>{children}</MockJsonApiProvider>;\n\n if (AdditionalWrapper) {\n return <AdditionalWrapper>{content}</AdditionalWrapper>;\n }\n\n return content;\n }\n\n return render(ui, { wrapper: AllProviders, ...renderOptions });\n}\n\n/**\n * Re-export render utilities from Testing Library for convenience.\n */\nexport { render, screen, waitFor, fireEvent, within } from \"@testing-library/react\";\nexport { userEvent } from \"@testing-library/user-event\";\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/testing/index.js","../../src/testing/providers/MockJsonApiProvider.tsx","../../src/testing/factories/createMockModule.ts","../../src/testing/factories/createMockResponse.ts","../../src/testing/factories/createMockService.ts","../../src/testing/factories/createMockApiData.ts","../../src/testing/matchers/jsonApiMatchers.ts","../../src/testing/utils/renderWithProviders.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/testing/index.js","sourcesContent":[null,"\"use client\";\n\nimport React from \"react\";\nimport { JsonApiConfig, JsonApiContext } from \"../../client/context/JsonApiContext\";\n\nexport interface MockJsonApiProviderProps {\n children: React.ReactNode;\n config?: Partial<JsonApiConfig>;\n}\n\nconst defaultMockConfig: JsonApiConfig = {\n apiUrl: \"https://api.test.com\",\n tokenGetter: async () => \"mock-token-for-testing\",\n languageGetter: async () => \"en\",\n defaultHeaders: {},\n onError: () => {},\n cacheConfig: {\n defaultProfile: \"default\",\n },\n};\n\n/**\n * A test-friendly provider that wraps components with mock JSON:API context.\n *\n * @example\n * ```tsx\n * import { MockJsonApiProvider } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * render(\n * <MockJsonApiProvider>\n * <MyComponent />\n * </MockJsonApiProvider>\n * );\n * ```\n *\n * @example With custom config\n * ```tsx\n * render(\n * <MockJsonApiProvider config={{ apiUrl: 'https://custom.api.com' }}>\n * <MyComponent />\n * </MockJsonApiProvider>\n * );\n * ```\n */\nexport function MockJsonApiProvider({ children, config }: MockJsonApiProviderProps) {\n const mergedConfig: JsonApiConfig = {\n ...defaultMockConfig,\n ...config,\n };\n\n return <JsonApiContext.Provider value={mergedConfig}>{children}</JsonApiContext.Provider>;\n}\n\nexport { defaultMockConfig };\n","import { ApiRequestDataTypeInterface } from \"../../core/interfaces/ApiRequestDataTypeInterface\";\n\nexport interface CreateMockModuleOptions {\n name: string;\n cache?: string;\n inclusions?: ApiRequestDataTypeInterface[\"inclusions\"];\n}\n\n/**\n * Creates a mock module definition for testing.\n *\n * @example\n * ```ts\n * import { createMockModule } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockArticleModule = createMockModule({ name: 'articles' });\n * ```\n */\nexport function createMockModule(options: CreateMockModuleOptions): ApiRequestDataTypeInterface {\n // Create a mock model class\n class MockModel {\n id = \"mock-id\";\n type = options.name;\n }\n\n return {\n name: options.name,\n cache: options.cache,\n inclusions: options.inclusions,\n model: MockModel,\n };\n}\n","import { ApiResponseInterface } from \"../../core/interfaces/ApiResponseInterface\";\nimport { ApiDataInterface } from \"../../core/interfaces/ApiDataInterface\";\n\nexport interface CreateMockResponseOptions {\n data?: ApiDataInterface | ApiDataInterface[] | null;\n ok?: boolean;\n response?: number;\n error?: string;\n meta?: Record<string, any>;\n self?: string;\n next?: string;\n prev?: string;\n}\n\n/**\n * Creates a mock API response for testing.\n *\n * @example\n * ```ts\n * import { createMockResponse, createMockApiData } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockData = createMockApiData({ type: 'articles', id: '1' });\n * const response = createMockResponse({ data: mockData, ok: true });\n * ```\n *\n * @example With pagination\n * ```ts\n * const response = createMockResponse({\n * data: [mockData],\n * ok: true,\n * next: '/articles?page=2',\n * prev: '/articles?page=0',\n * });\n * ```\n */\nexport function createMockResponse(options: CreateMockResponseOptions = {}): ApiResponseInterface {\n const {\n data = null,\n ok = true,\n response = ok ? 200 : 500,\n error = ok ? \"\" : \"Error\",\n meta,\n self,\n next,\n prev,\n } = options;\n\n const mockResponse: ApiResponseInterface = {\n ok,\n response,\n data: data as ApiDataInterface | ApiDataInterface[],\n error,\n meta,\n self,\n next,\n prev,\n };\n\n // Add pagination methods if next/prev provided\n if (next) {\n mockResponse.nextPage = async () => createMockResponse({ ...options, next: undefined, prev: self });\n }\n\n if (prev) {\n mockResponse.prevPage = async () => createMockResponse({ ...options, prev: undefined, next: self });\n }\n\n return mockResponse;\n}\n\n/**\n * Creates a mock error response for testing error scenarios.\n *\n * @example\n * ```ts\n * import { createMockErrorResponse } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const errorResponse = createMockErrorResponse(404, 'Not Found');\n * ```\n */\nexport function createMockErrorResponse(statusCode: number, errorMessage: string): ApiResponseInterface {\n return createMockResponse({\n ok: false,\n response: statusCode,\n error: errorMessage,\n data: null,\n });\n}\n","import { vi, type Mock } from \"vitest\";\nimport { ApiResponseInterface } from \"../../core/interfaces/ApiResponseInterface\";\nimport { createMockResponse } from \"./createMockResponse\";\n\nexport type MockApiMethod = Mock<(...args: any[]) => Promise<ApiResponseInterface>>;\n\nexport interface MockService {\n get: MockApiMethod;\n post: MockApiMethod;\n put: MockApiMethod;\n patch: MockApiMethod;\n delete: MockApiMethod;\n}\n\nexport interface CreateMockServiceOptions {\n defaultResponse?: ApiResponseInterface;\n}\n\n/**\n * Creates a mock service with Vitest mock functions for all HTTP methods.\n *\n * @example\n * ```ts\n * import { createMockService, createMockResponse } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockService = createMockService();\n * mockService.get.mockResolvedValue(createMockResponse({ data: mockData }));\n *\n * // Use in test\n * expect(mockService.get).toHaveBeenCalled();\n * ```\n *\n * @example With default response\n * ```ts\n * const mockService = createMockService({\n * defaultResponse: createMockResponse({ ok: true, data: [] }),\n * });\n * ```\n */\nexport function createMockService(options: CreateMockServiceOptions = {}): MockService {\n const defaultResponse = options.defaultResponse ?? createMockResponse({ ok: true });\n\n return {\n get: vi.fn().mockResolvedValue(defaultResponse),\n post: vi.fn().mockResolvedValue(defaultResponse),\n put: vi.fn().mockResolvedValue(defaultResponse),\n patch: vi.fn().mockResolvedValue(defaultResponse),\n delete: vi.fn().mockResolvedValue(defaultResponse),\n };\n}\n\n/**\n * Creates a mock service that returns errors for all methods.\n *\n * @example\n * ```ts\n * import { createMockErrorService } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const errorService = createMockErrorService(500, 'Internal Server Error');\n * ```\n */\nexport function createMockErrorService(statusCode: number = 500, errorMessage: string = \"Error\"): MockService {\n const errorResponse = createMockResponse({\n ok: false,\n response: statusCode,\n error: errorMessage,\n });\n\n return {\n get: vi.fn().mockResolvedValue(errorResponse),\n post: vi.fn().mockResolvedValue(errorResponse),\n put: vi.fn().mockResolvedValue(errorResponse),\n patch: vi.fn().mockResolvedValue(errorResponse),\n delete: vi.fn().mockResolvedValue(errorResponse),\n };\n}\n","import { ApiDataInterface } from \"../../core/interfaces/ApiDataInterface\";\n\nexport interface CreateMockApiDataOptions {\n type: string;\n id?: string;\n attributes?: Record<string, any>;\n relationships?: Record<string, any>;\n included?: any[];\n createdAt?: Date;\n updatedAt?: Date;\n self?: string;\n}\n\n/**\n * Creates a mock ApiDataInterface object for testing.\n *\n * @example\n * ```ts\n * import { createMockApiData } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockArticle = createMockApiData({\n * type: 'articles',\n * id: '1',\n * attributes: { title: 'Test Article', body: 'Content here' },\n * });\n * ```\n *\n * @example With relationships\n * ```ts\n * const mockArticle = createMockApiData({\n * type: 'articles',\n * id: '1',\n * attributes: { title: 'Test' },\n * relationships: {\n * author: { data: { type: 'users', id: '42' } },\n * },\n * });\n * ```\n */\nexport function createMockApiData(options: CreateMockApiDataOptions): ApiDataInterface {\n const {\n type,\n id = `mock-${type}-${Math.random().toString(36).substring(7)}`,\n attributes = {},\n relationships = {},\n included = [],\n createdAt = new Date(),\n updatedAt = new Date(),\n self,\n } = options;\n\n const jsonApiData = {\n type,\n id,\n attributes: {\n ...attributes,\n createdAt: createdAt.toISOString(),\n updatedAt: updatedAt.toISOString(),\n },\n relationships,\n };\n\n const mockData: ApiDataInterface = {\n get included() {\n return included;\n },\n get type() {\n return type;\n },\n get id() {\n return id;\n },\n get createdAt() {\n return createdAt;\n },\n get updatedAt() {\n return updatedAt;\n },\n get self() {\n return self;\n },\n get jsonApi() {\n return jsonApiData;\n },\n generateApiUrl: (params?: any) => {\n const baseUrl = `/${type}/${id}`;\n if (params) {\n const searchParams = new URLSearchParams(params);\n return `${baseUrl}?${searchParams.toString()}`;\n }\n return baseUrl;\n },\n dehydrate: () => ({\n jsonApi: jsonApiData,\n included,\n allData: [jsonApiData],\n }),\n rehydrate: function (_data: any) {\n return this;\n },\n createJsonApi: (data: any) => ({\n type,\n id,\n attributes: data,\n }),\n get identifier() {\n const identifierFields = [\"name\"];\n return identifierFields\n .map((field) => attributes[field])\n .filter((v) => v != null && v !== \"\")\n .join(\" \");\n },\n };\n\n // Add attribute accessors to the mock object\n Object.keys(attributes).forEach((key) => {\n Object.defineProperty(mockData, key, {\n get: () => attributes[key],\n enumerable: true,\n });\n });\n\n return mockData;\n}\n\n/**\n * Creates an array of mock ApiDataInterface objects.\n *\n * @example\n * ```ts\n * import { createMockApiDataList } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const mockArticles = createMockApiDataList('articles', 5, (index) => ({\n * title: `Article ${index + 1}`,\n * }));\n * ```\n */\nexport function createMockApiDataList(\n type: string,\n count: number,\n attributesFactory?: (index: number) => Record<string, any>,\n): ApiDataInterface[] {\n return Array.from({ length: count }, (_, index) =>\n createMockApiData({\n type,\n id: `${index + 1}`,\n attributes: attributesFactory?.(index) ?? {},\n }),\n );\n}\n","import { expect } from \"vitest\";\n\ninterface JsonApiResponse {\n data?:\n | {\n type?: string;\n id?: string;\n attributes?: Record<string, any>;\n relationships?: Record<string, any>;\n }\n | Array<{\n type?: string;\n id?: string;\n attributes?: Record<string, any>;\n relationships?: Record<string, any>;\n }>;\n}\n\n/**\n * Custom Vitest matchers for JSON:API assertions.\n *\n * @example\n * ```ts\n * import { jsonApiMatchers } from '@carlonicora/nextjs-jsonapi/testing';\n * import { expect } from 'vitest';\n *\n * expect.extend(jsonApiMatchers);\n *\n * // Then use in tests:\n * expect(response).toBeValidJsonApi();\n * expect(response).toHaveJsonApiType('articles');\n * expect(response).toHaveJsonApiAttribute('title', 'My Article');\n * expect(response).toHaveJsonApiRelationship('author');\n * ```\n */\nexport const jsonApiMatchers = {\n /**\n * Asserts that the response has a valid JSON:API structure with type and id.\n */\n toBeValidJsonApi(received: JsonApiResponse) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const hasType = typeof data?.type === \"string\" && data.type.length > 0;\n const hasId = typeof data?.id === \"string\" && data.id.length > 0;\n const isValid = hasType && hasId;\n\n return {\n pass: isValid,\n message: () =>\n isValid\n ? `Expected response not to be valid JSON:API, but it has type \"${data?.type}\" and id \"${data?.id}\"`\n : `Expected response to be valid JSON:API with type and id, but got type: ${JSON.stringify(data?.type)}, id: ${JSON.stringify(data?.id)}`,\n };\n },\n\n /**\n * Asserts that the response data has the expected JSON:API type.\n */\n toHaveJsonApiType(received: JsonApiResponse, expectedType: string) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const actualType = data?.type;\n const pass = actualType === expectedType;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected response not to have JSON:API type \"${expectedType}\"`\n : `Expected response to have JSON:API type \"${expectedType}\", but got \"${actualType}\"`,\n };\n },\n\n /**\n * Asserts that the response data has an attribute with the expected value.\n */\n toHaveJsonApiAttribute(received: JsonApiResponse, attributeName: string, expectedValue?: any) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const attributes = data?.attributes ?? {};\n const hasAttribute = attributeName in attributes;\n const actualValue = attributes[attributeName];\n\n if (expectedValue === undefined) {\n // Just check existence\n return {\n pass: hasAttribute,\n message: () =>\n hasAttribute\n ? `Expected response not to have JSON:API attribute \"${attributeName}\"`\n : `Expected response to have JSON:API attribute \"${attributeName}\", but it was not found. Available attributes: ${Object.keys(attributes).join(\", \") || \"none\"}`,\n };\n }\n\n const valuesMatch = actualValue === expectedValue;\n return {\n pass: hasAttribute && valuesMatch,\n message: () =>\n hasAttribute && valuesMatch\n ? `Expected response not to have JSON:API attribute \"${attributeName}\" with value \"${expectedValue}\"`\n : !hasAttribute\n ? `Expected response to have JSON:API attribute \"${attributeName}\", but it was not found`\n : `Expected JSON:API attribute \"${attributeName}\" to be \"${expectedValue}\", but got \"${actualValue}\"`,\n };\n },\n\n /**\n * Asserts that the response data has the specified relationship.\n */\n toHaveJsonApiRelationship(received: JsonApiResponse, relationshipName: string) {\n const data = Array.isArray(received?.data) ? received.data[0] : received?.data;\n const relationships = data?.relationships ?? {};\n const hasRelationship = relationshipName in relationships;\n\n return {\n pass: hasRelationship,\n message: () =>\n hasRelationship\n ? `Expected response not to have JSON:API relationship \"${relationshipName}\"`\n : `Expected response to have JSON:API relationship \"${relationshipName}\", but it was not found. Available relationships: ${Object.keys(relationships).join(\", \") || \"none\"}`,\n };\n },\n\n /**\n * Asserts that the response data array has the expected length.\n */\n toHaveJsonApiLength(received: JsonApiResponse, expectedLength: number) {\n const data = received?.data;\n const isArray = Array.isArray(data);\n const actualLength = isArray ? data.length : data ? 1 : 0;\n const pass = actualLength === expectedLength;\n\n return {\n pass,\n message: () =>\n pass\n ? `Expected response not to have ${expectedLength} items`\n : `Expected response to have ${expectedLength} items, but got ${actualLength}`,\n };\n },\n};\n\n// Type declarations for the custom matchers\n// Vitest 5: Assertion<R, T> and AsymmetricMatchersContaining both extend\n// Matchers<R, T>, so the matchers are declared once here and return R.\ndeclare module \"vitest\" {\n interface Matchers<R extends void | Promise<void> = void | Promise<void>, T = unknown> {\n toBeValidJsonApi(): R;\n toHaveJsonApiType(expectedType: string): R;\n toHaveJsonApiAttribute(attributeName: string, expectedValue?: any): R;\n toHaveJsonApiRelationship(relationshipName: string): R;\n toHaveJsonApiLength(expectedLength: number): R;\n }\n}\n\n/**\n * Extends Vitest's expect with JSON:API matchers.\n * Call this in your test setup file.\n *\n * @example\n * ```ts\n * // vitest.setup.ts\n * import { extendExpectWithJsonApiMatchers } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * extendExpectWithJsonApiMatchers();\n * ```\n */\nexport function extendExpectWithJsonApiMatchers(): void {\n expect.extend(jsonApiMatchers);\n}\n","\"use client\";\n\nimport React, { ReactElement } from \"react\";\nimport { render, RenderOptions, RenderResult } from \"@testing-library/react\";\nimport {\n MockJsonApiProvider,\n MockJsonApiProviderProps as _MockJsonApiProviderProps,\n} from \"../providers/MockJsonApiProvider\";\nimport { JsonApiConfig } from \"../../client/context/JsonApiContext\";\n\nexport interface RenderWithProvidersOptions extends Omit<RenderOptions, \"wrapper\"> {\n /**\n * Custom JSON:API configuration to pass to the mock provider.\n */\n jsonApiConfig?: Partial<JsonApiConfig>;\n\n /**\n * Additional wrapper component to wrap around the providers.\n */\n wrapper?: React.ComponentType<{ children: React.ReactNode }>;\n}\n\n/**\n * Renders a component wrapped with all necessary providers for testing.\n *\n * @example\n * ```tsx\n * import { renderWithProviders } from '@carlonicora/nextjs-jsonapi/testing';\n *\n * const { getByText } = renderWithProviders(<MyComponent />);\n * expect(getByText('Hello')).toBeInTheDocument();\n * ```\n *\n * @example With custom config\n * ```tsx\n * const { getByText } = renderWithProviders(<MyComponent />, {\n * jsonApiConfig: { apiUrl: 'https://custom.api.com' },\n * });\n * ```\n *\n * @example With additional wrapper\n * ```tsx\n * const CustomWrapper = ({ children }) => (\n * <ThemeProvider>{children}</ThemeProvider>\n * );\n *\n * const { getByText } = renderWithProviders(<MyComponent />, {\n * wrapper: CustomWrapper,\n * });\n * ```\n */\nexport function renderWithProviders(ui: ReactElement, options: RenderWithProvidersOptions = {}): RenderResult {\n const { jsonApiConfig, wrapper: AdditionalWrapper, ...renderOptions } = options;\n\n function AllProviders({ children }: { children: React.ReactNode }) {\n const content = <MockJsonApiProvider config={jsonApiConfig}>{children}</MockJsonApiProvider>;\n\n if (AdditionalWrapper) {\n return <AdditionalWrapper>{content}</AdditionalWrapper>;\n }\n\n return content;\n }\n\n return render(ui, { wrapper: AllProviders, ...renderOptions });\n}\n\n/**\n * Re-export render utilities from Testing Library for convenience.\n */\nexport { render, screen, waitFor, fireEvent, within } from \"@testing-library/react\";\nexport { userEvent } from \"@testing-library/user-event\";\n"]}
|