@carlonicora/nextjs-jsonapi 3.16.1 → 3.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/{BlockNoteEditor-BQ46E6YI.js → BlockNoteEditor-K23Z4LHK.js} +9 -9
  2. package/dist/{BlockNoteEditor-BQ46E6YI.js.map → BlockNoteEditor-K23Z4LHK.js.map} +1 -1
  3. package/dist/{BlockNoteEditor-6SQR7WNA.mjs → BlockNoteEditor-OJTGGL67.mjs} +2 -2
  4. package/dist/billing/index.js +335 -335
  5. package/dist/billing/index.mjs +1 -1
  6. package/dist/{chunk-2HZCW4ML.js → chunk-2UQBILF5.js} +20 -23
  7. package/dist/chunk-2UQBILF5.js.map +1 -0
  8. package/dist/{chunk-YRWZGH66.mjs → chunk-4WUD3DDO.mjs} +20 -23
  9. package/dist/chunk-4WUD3DDO.mjs.map +1 -0
  10. package/dist/client/index.js +2 -2
  11. package/dist/client/index.mjs +1 -1
  12. package/dist/components/index.d.mts +17 -1
  13. package/dist/components/index.d.ts +17 -1
  14. package/dist/components/index.js +2 -2
  15. package/dist/components/index.mjs +1 -1
  16. package/dist/contexts/index.js +2 -2
  17. package/dist/contexts/index.mjs +1 -1
  18. package/dist/features/help/index.js +31 -31
  19. package/dist/features/help/index.mjs +1 -1
  20. package/dist/features/tokenusage/index.js +75 -75
  21. package/dist/features/tokenusage/index.mjs +1 -1
  22. package/package.json +1 -1
  23. package/src/components/containers/RoundPageContainer.tsx +25 -0
  24. package/src/features/notification/components/modals/NotificationModal.tsx +8 -16
  25. package/src/features/notification/components/modals/__tests__/NotificationModal.spec.tsx +156 -0
  26. package/dist/chunk-2HZCW4ML.js.map +0 -1
  27. package/dist/chunk-YRWZGH66.mjs.map +0 -1
  28. /package/dist/{BlockNoteEditor-6SQR7WNA.mjs.map → BlockNoteEditor-OJTGGL67.mjs.map} +0 -0
@@ -0,0 +1,156 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { render } from "@testing-library/react";
3
+ import React from "react";
4
+
5
+ // The modal competes with `useNotificationSync` for the same socket queue; the
6
+ // parent hook drains it in the same commit. These specs pin the property that
7
+ // makes the toast possible at all: the modal takes the queue synchronously
8
+ // when it renders with socket notifications, never on a deferred timer.
9
+
10
+ // Vitest hoists every `vi.mock` factory above the module body, so anything a
11
+ // factory touches has to be created by `vi.hoisted` — including the mutable
12
+ // socket queue, which lives in a holder object the tests reassign the contents
13
+ // of (`mocks.queue.current = [...]`) rather than rebinding.
14
+ //
15
+ // `contexts`, `hooks` and `shadcnui` are large barrels: Vitest throws
16
+ // `No "X" export is defined on the … mock` for any key a factory omits, so
17
+ // `barrel` wraps the returned object in a Proxy that claims to have every key
18
+ // and answers the unstubbed ones with `undefined` (`then` included, so the
19
+ // mocked module is never mistaken for a thenable).
20
+ const mocks = vi.hoisted(() => ({
21
+ showToast: vi.fn(),
22
+ clearSocketNotifications: vi.fn(),
23
+ addNotification: vi.fn(),
24
+ generateToastNotification: vi.fn(
25
+ (): { title: string; description: string; action?: { label: string; onClick: () => void } } => ({
26
+ title: "Transcript ready",
27
+ description: "Session 12",
28
+ }),
29
+ ),
30
+ queue: { current: [] as unknown[] },
31
+ barrel: function <T extends object>(exports: T): T {
32
+ return new Proxy({ then: undefined, ...exports }, { has: () => true }) as T;
33
+ },
34
+ }));
35
+
36
+ vi.mock("../../../../../utils/toast", () => ({ showToast: (...args: unknown[]) => mocks.showToast(...args) }));
37
+
38
+ vi.mock("../../../../../contexts", () =>
39
+ mocks.barrel({
40
+ useSocketContext: () => ({
41
+ socketNotifications: mocks.queue.current,
42
+ removeSocketNotification: vi.fn(),
43
+ clearSocketNotifications: mocks.clearSocketNotifications,
44
+ }),
45
+ }),
46
+ );
47
+
48
+ vi.mock("../../../../../hooks", () => mocks.barrel({ usePageUrlGenerator: () => (p: unknown) => String(p) }));
49
+ vi.mock("next-intl", () => ({ useTranslations: () => (key: string) => key }));
50
+
51
+ vi.mock("../../../contexts/NotificationContext", () => ({
52
+ useNotificationContext: () => ({
53
+ notifications: [],
54
+ addNotification: mocks.addNotification,
55
+ generateNotification: () => null,
56
+ generateToastNotification: mocks.generateToastNotification,
57
+ markNotificationsAsRead: vi.fn(),
58
+ isLoading: false,
59
+ error: null,
60
+ loadNotifications: vi.fn(),
61
+ shouldRefresh: false,
62
+ }),
63
+ }));
64
+
65
+ // The popover and card primitives are irrelevant here; render their children.
66
+ vi.mock("../../../../../shadcnui", () => {
67
+ const passthrough = ({ children }: { children?: React.ReactNode }) => <div>{children}</div>;
68
+ return mocks.barrel({
69
+ Card: passthrough,
70
+ CardHeader: passthrough,
71
+ CardTitle: passthrough,
72
+ Popover: passthrough,
73
+ PopoverContent: passthrough,
74
+ PopoverTrigger: passthrough,
75
+ ScrollArea: passthrough,
76
+ Separator: () => null,
77
+ SidebarMenuButton: passthrough,
78
+ });
79
+ });
80
+
81
+ import { NotificationModal } from "../NotificationModal";
82
+
83
+ const notification = { id: "n-1", notificationType: "transcript_ready", isRead: false } as never;
84
+
85
+ describe("NotificationModal socket queue", () => {
86
+ beforeEach(() => {
87
+ vi.useFakeTimers();
88
+ mocks.showToast.mockClear();
89
+ mocks.clearSocketNotifications.mockClear();
90
+ mocks.addNotification.mockClear();
91
+ });
92
+
93
+ afterEach(() => {
94
+ vi.useRealTimers();
95
+ });
96
+
97
+ it("takes the socket queue synchronously on render, before any timer runs", () => {
98
+ mocks.queue.current = [notification];
99
+
100
+ render(<NotificationModal isOpen={false} setIsOpen={vi.fn()} />);
101
+
102
+ // Same commit: the queue is already claimed, so a sibling drain finds it empty.
103
+ expect(mocks.clearSocketNotifications).toHaveBeenCalledTimes(1);
104
+ });
105
+
106
+ it("adds the notification and shows its toast once the batch timer fires", () => {
107
+ mocks.queue.current = [notification];
108
+
109
+ render(<NotificationModal isOpen={false} setIsOpen={vi.fn()} />);
110
+ vi.runAllTimers();
111
+
112
+ expect(mocks.addNotification).toHaveBeenCalledWith(notification);
113
+ expect(mocks.showToast).toHaveBeenCalledTimes(1);
114
+ expect(mocks.showToast).toHaveBeenCalledWith(
115
+ "Transcript ready",
116
+ expect.objectContaining({ description: "Session 12" }),
117
+ );
118
+ });
119
+
120
+ it("does nothing when the queue is empty", () => {
121
+ mocks.queue.current = [];
122
+
123
+ render(<NotificationModal isOpen={false} setIsOpen={vi.fn()} />);
124
+ vi.runAllTimers();
125
+
126
+ expect(mocks.clearSocketNotifications).not.toHaveBeenCalled();
127
+ expect(mocks.showToast).not.toHaveBeenCalled();
128
+ });
129
+
130
+ it("keeps a toast that has something to press on screen until it is dismissed", () => {
131
+ mocks.queue.current = [notification];
132
+ const action = { label: "Open", onClick: vi.fn() };
133
+ mocks.generateToastNotification.mockReturnValueOnce({
134
+ title: "Transcript ready",
135
+ description: "Session 12",
136
+ action,
137
+ });
138
+
139
+ render(<NotificationModal isOpen={false} setIsOpen={vi.fn()} />);
140
+ vi.runAllTimers();
141
+
142
+ expect(mocks.showToast).toHaveBeenCalledWith(
143
+ "Transcript ready",
144
+ expect.objectContaining({ action, duration: Infinity }),
145
+ );
146
+ });
147
+
148
+ it("leaves the usual timeout on a toast with nothing to press", () => {
149
+ mocks.queue.current = [notification];
150
+
151
+ render(<NotificationModal isOpen={false} setIsOpen={vi.fn()} />);
152
+ vi.runAllTimers();
153
+
154
+ expect(mocks.showToast).toHaveBeenCalledWith("Transcript ready", expect.objectContaining({ duration: undefined }));
155
+ });
156
+ });