gbs-add-block 2.1.0 → 2.3.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 (129) hide show
  1. package/.gbs/skills/gbs-components/SKILL.md +190 -134
  2. package/.gbs/skills/gbs-components/references/install.md +25 -3
  3. package/.gbs/skills/gbs-components/references/styling.md +246 -207
  4. package/CHANGELOG.md +74 -0
  5. package/README.md +154 -23
  6. package/index.cjs +212 -3
  7. package/package.json +41 -10
  8. package/schema/passport-v1.schema.json +204 -0
  9. package/source/beta-components/accordion/passport.json +259 -0
  10. package/source/beta-components/accordion/styles.css +207 -208
  11. package/source/beta-components/alert/passport.json +250 -0
  12. package/source/beta-components/alert/styles.css +154 -155
  13. package/source/beta-components/avatar/passport.json +294 -0
  14. package/source/beta-components/avatar/styles.css +225 -226
  15. package/source/beta-components/badge/passport.json +332 -0
  16. package/source/beta-components/badge/styles.css +203 -204
  17. package/source/beta-components/breadcrumb/passport.json +243 -0
  18. package/source/beta-components/breadcrumb/styles.css +138 -140
  19. package/source/beta-components/button/passport.json +402 -0
  20. package/source/beta-components/button/passport.manual.json +31 -0
  21. package/source/beta-components/button/styles.css +232 -234
  22. package/source/beta-components/card/passport.json +337 -0
  23. package/source/beta-components/card/styles.css +230 -232
  24. package/source/beta-components/checkbox/passport.json +456 -0
  25. package/source/beta-components/checkbox/styles.css +211 -213
  26. package/source/beta-components/combobox/passport.json +456 -0
  27. package/source/beta-components/combobox/styles.css +419 -417
  28. package/source/beta-components/data-grid/agent/coerce.ts +368 -0
  29. package/source/beta-components/data-grid/agent/contract.ts +410 -0
  30. package/source/beta-components/data-grid/agent/dataset.ts +92 -0
  31. package/source/beta-components/data-grid/agent/engine.ts +470 -0
  32. package/source/beta-components/data-grid/agent/executors.ts +155 -0
  33. package/source/beta-components/data-grid/agent/index.ts +79 -0
  34. package/source/beta-components/data-grid/agent/intent.ts +324 -0
  35. package/source/beta-components/data-grid/agent/operations.ts +335 -0
  36. package/source/beta-components/data-grid/agent/validate.ts +630 -0
  37. package/source/beta-components/data-grid/agent/webmcp.ts +107 -0
  38. package/source/beta-components/data-grid/index.ts +14 -7
  39. package/source/beta-components/data-grid/passport.json +1051 -0
  40. package/source/beta-components/data-grid/passport.manual.json +255 -0
  41. package/source/beta-components/data-grid/react/AskGrid.tsx +164 -0
  42. package/source/beta-components/data-grid/react/DataGrid.tsx +39 -0
  43. package/source/beta-components/data-grid/styles.css +874 -716
  44. package/source/beta-components/date-picker/passport.json +407 -0
  45. package/source/beta-components/date-picker/styles.css +445 -446
  46. package/source/beta-components/dialog/passport.json +344 -0
  47. package/source/beta-components/dialog/styles.css +280 -279
  48. package/source/beta-components/file-uploader/passport.json +518 -0
  49. package/source/beta-components/file-uploader/styles.css +394 -396
  50. package/source/beta-components/input/passport.json +536 -0
  51. package/source/beta-components/input/styles.css +295 -297
  52. package/source/beta-components/menu/passport.json +322 -0
  53. package/source/beta-components/menu/styles.css +224 -223
  54. package/source/beta-components/modal/passport.json +289 -0
  55. package/source/beta-components/modal/styles.css +241 -240
  56. package/source/beta-components/number-input/passport.json +541 -0
  57. package/source/beta-components/number-input/styles.css +230 -231
  58. package/source/beta-components/popover/passport.json +238 -0
  59. package/source/beta-components/popover/styles.css +148 -147
  60. package/source/beta-components/progress/passport.json +270 -0
  61. package/source/beta-components/progress/styles.css +200 -201
  62. package/source/beta-components/radio-group/passport.json +477 -0
  63. package/source/beta-components/radio-group/styles.css +269 -270
  64. package/source/beta-components/shared/core/agent/adapter.ts +65 -0
  65. package/source/beta-components/shared/core/agent/history.ts +120 -0
  66. package/source/beta-components/shared/core/agent/index.ts +46 -0
  67. package/source/beta-components/shared/core/agent/numbers.ts +217 -0
  68. package/source/beta-components/shared/core/agent/schema.ts +180 -0
  69. package/source/beta-components/shared/core/agent/types.ts +169 -0
  70. package/source/beta-components/shared/core/agent/webmcp.ts +328 -0
  71. package/source/beta-components/shared/index.ts +9 -0
  72. package/source/beta-components/shared/react/GramproAIProvider.tsx +50 -0
  73. package/source/beta-components/shared/react/useAskAgent.ts +217 -0
  74. package/source/beta-components/shared/styles.css +79 -0
  75. package/source/beta-components/shared/version.json +4 -4
  76. package/source/beta-components/shared/version.ts +6 -6
  77. package/source/beta-components/skeleton/passport.json +251 -0
  78. package/source/beta-components/skeleton/styles.css +185 -187
  79. package/source/beta-components/spinner/passport.json +245 -0
  80. package/source/beta-components/spinner/styles.css +173 -174
  81. package/source/beta-components/switch/passport.json +421 -0
  82. package/source/beta-components/switch/styles.css +227 -229
  83. package/source/beta-components/tabs/passport.json +315 -0
  84. package/source/beta-components/tabs/styles.css +263 -264
  85. package/source/beta-components/textarea/passport.json +382 -0
  86. package/source/beta-components/textarea/styles.css +158 -160
  87. package/source/beta-components/toaster/passport.json +221 -0
  88. package/source/beta-components/toaster/styles.css +282 -282
  89. package/source/beta-components/tooltip/passport.json +170 -0
  90. package/source/beta-components/tooltip/styles.css +71 -73
  91. package/tools/env.cjs +61 -0
  92. package/tools/passport/cli.cjs +79 -0
  93. package/tools/passport/extract.cjs +493 -0
  94. package/tools/passport/index.cjs +185 -0
  95. package/tools/passport/merge.cjs +131 -0
  96. package/tools/passport/policy.cjs +65 -0
  97. package/tools/passport/validate.cjs +277 -0
  98. package/tools/ts-require.cjs +79 -0
  99. package/source/beta-components/accordion/__tests__/core.test.ts +0 -58
  100. package/source/beta-components/alert/__tests__/core.test.ts +0 -17
  101. package/source/beta-components/avatar/__tests__/core.test.ts +0 -88
  102. package/source/beta-components/badge/__tests__/core.test.ts +0 -46
  103. package/source/beta-components/breadcrumb/__tests__/core.test.ts +0 -58
  104. package/source/beta-components/button/__tests__/core.test.ts +0 -31
  105. package/source/beta-components/card/__tests__/core.test.ts +0 -57
  106. package/source/beta-components/checkbox/__tests__/core.test.ts +0 -40
  107. package/source/beta-components/combobox/__tests__/core.test.ts +0 -134
  108. package/source/beta-components/data-grid/__tests__/core.test.ts +0 -356
  109. package/source/beta-components/data-grid/__tests__/export.test.ts +0 -70
  110. package/source/beta-components/data-grid/__tests__/pdf.test.ts +0 -209
  111. package/source/beta-components/date-picker/__tests__/core.test.ts +0 -273
  112. package/source/beta-components/dialog/__tests__/core.test.ts +0 -86
  113. package/source/beta-components/file-uploader/__tests__/core.test.ts +0 -395
  114. package/source/beta-components/input/__tests__/core.test.ts +0 -75
  115. package/source/beta-components/menu/__tests__/core.test.ts +0 -120
  116. package/source/beta-components/modal/__tests__/core.test.ts +0 -55
  117. package/source/beta-components/number-input/__tests__/core.test.ts +0 -151
  118. package/source/beta-components/progress/__tests__/core.test.ts +0 -56
  119. package/source/beta-components/radio-group/__tests__/core.test.ts +0 -64
  120. package/source/beta-components/shared/__tests__/boundaries.test.ts +0 -95
  121. package/source/beta-components/shared/__tests__/core.test.ts +0 -55
  122. package/source/beta-components/shared/__tests__/position.test.ts +0 -143
  123. package/source/beta-components/skeleton/__tests__/core.test.ts +0 -41
  124. package/source/beta-components/spinner/__tests__/core.test.ts +0 -48
  125. package/source/beta-components/switch/__tests__/core.test.ts +0 -64
  126. package/source/beta-components/tabs/__tests__/core.test.ts +0 -51
  127. package/source/beta-components/textarea/__tests__/core.test.ts +0 -38
  128. package/source/beta-components/toaster/__tests__/core.test.ts +0 -256
  129. package/source/beta-components/tooltip/__tests__/core.test.ts +0 -42
@@ -1,64 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { adopt, idleToggle, request, settle } from "../core/toggle";
3
-
4
- describe("switch toggling", () => {
5
- it("shows the new setting before it has been saved", () => {
6
- const state = request(idleToggle(false), true);
7
- expect(state).toEqual({ checked: true, pending: true, fallback: false });
8
- });
9
-
10
- it("keeps the new setting once the save succeeds", () => {
11
- const state = settle(request(idleToggle(false), true), true, true);
12
- expect(state).toEqual({ checked: true, pending: null, fallback: true });
13
- });
14
-
15
- it("goes back where it was when the save fails", () => {
16
- const state = settle(request(idleToggle(false), true), true, false);
17
- expect(state).toEqual({ checked: false, pending: null, fallback: false });
18
- });
19
-
20
- it("returns to where the run of changes started, not to the opposite", () => {
21
- // On, then off again while the first save is still running: failing the
22
- // second must land on the original off, which is also where it looks like
23
- // it is — flipping the visible value would turn it back on.
24
- const first = request(idleToggle(false), true);
25
- const second = request(first, false);
26
- expect(second.fallback).toBe(false);
27
- expect(settle(second, false, false).checked).toBe(false);
28
- });
29
-
30
- it("ignores the outcome of a change that was overtaken", () => {
31
- const first = request(idleToggle(false), true);
32
- const second = request(first, false);
33
- // The first request finally answers, long after the second replaced it.
34
- expect(settle(second, true, true)).toBe(second);
35
- expect(settle(second, true, false)).toBe(second);
36
- });
37
-
38
- it("ignores an outcome when nothing is in flight", () => {
39
- const idle = idleToggle(true);
40
- expect(settle(idle, true, false)).toBe(idle);
41
- });
42
-
43
- it("does nothing when asked for the value it already shows", () => {
44
- const idle = idleToggle(true);
45
- expect(request(idle, true)).toBe(idle);
46
- });
47
-
48
- it("re-requests a value that is showing but not yet saved", () => {
49
- // Pressed on, pressed off, pressed on again: the last press still needs an
50
- // outcome of its own, so it cannot be dropped as a no-op.
51
- const pending = request(request(idleToggle(false), true), false);
52
- const again = request(pending, true);
53
- expect(again.pending).toBe(true);
54
- expect(again.fallback).toBe(false);
55
- });
56
-
57
- it("lets a controlled value win over anything in flight", () => {
58
- const pending = request(idleToggle(false), true);
59
- expect(adopt(pending, false)).toEqual({ checked: false, pending: null, fallback: false });
60
- // An unchanged, settled value is left as it is, so React skips the render.
61
- const idle = idleToggle(true);
62
- expect(adopt(idle, true)).toBe(idle);
63
- });
64
- });
@@ -1,51 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { nextTab, tabDomId } from "../core/navigation";
3
-
4
- const tabs = [{}, { disabled: true }, {}, {}];
5
-
6
- describe("keyboard movement", () => {
7
- it("moves with the arrows, skipping disabled tabs", () => {
8
- expect(nextTab(tabs, 0, "ArrowRight")).toBe(2);
9
- expect(nextTab(tabs, 2, "ArrowLeft")).toBe(0);
10
- });
11
-
12
- it("wraps around by default, or stops at the ends", () => {
13
- expect(nextTab(tabs, 3, "ArrowRight")).toBe(0);
14
- expect(nextTab(tabs, 0, "ArrowLeft")).toBe(3);
15
- expect(nextTab(tabs, 3, "ArrowRight", { loop: false })).toBeNull();
16
- expect(nextTab(tabs, 0, "ArrowLeft", { loop: false })).toBeNull();
17
- });
18
-
19
- it("jumps with Home and End", () => {
20
- expect(nextTab(tabs, 2, "Home")).toBe(0);
21
- expect(nextTab(tabs, 0, "End")).toBe(3);
22
- expect(nextTab([{ disabled: true }, {}, { disabled: true }], 1, "End")).toBe(1);
23
- });
24
-
25
- it("uses up and down when vertical, and ignores the other axis", () => {
26
- expect(nextTab(tabs, 0, "ArrowDown", { orientation: "vertical" })).toBe(2);
27
- expect(nextTab(tabs, 2, "ArrowUp", { orientation: "vertical" })).toBe(0);
28
- expect(nextTab(tabs, 0, "ArrowRight", { orientation: "vertical" })).toBeNull();
29
- expect(nextTab(tabs, 0, "ArrowDown")).toBeNull();
30
- });
31
-
32
- it("swaps left and right in right-to-left layouts", () => {
33
- expect(nextTab(tabs, 0, "ArrowLeft", { rtl: true })).toBe(2);
34
- expect(nextTab(tabs, 2, "ArrowRight", { rtl: true })).toBe(0);
35
- });
36
-
37
- it("returns null when nothing can take focus", () => {
38
- expect(nextTab([], 0, "ArrowRight")).toBeNull();
39
- expect(nextTab([{ disabled: true }], 0, "ArrowRight")).toBeNull();
40
- expect(nextTab(tabs, 0, "Enter")).toBeNull();
41
- });
42
- });
43
-
44
- describe("ids", () => {
45
- it("makes safe, distinct ids from any value", () => {
46
- expect(tabDomId("t1", "tab", "overview")).toBe("t1-tab-overview");
47
- expect(tabDomId("t1", "panel", "billing & plans")).toBe("t1-panel-billing_20_26_20plans");
48
- expect(tabDomId("t1", "tab", "a b")).not.toBe(tabDomId("t1", "tab", "a_b"));
49
- expect(tabDomId("t1", "tab", "v1.2")).toMatch(/^[\w-]+$/);
50
- });
51
- });
@@ -1,38 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { countCharacters } from "../core/count";
3
- import { fitHeight, heightForRows } from "../core/size";
4
-
5
- const metrics = { lineHeight: 20, paddingBlock: 16, borderBlock: 2 };
6
-
7
- describe("character count", () => {
8
- it("counts what a person sees", () => {
9
- expect(countCharacters("")).toBe(0);
10
- expect(countCharacters("line one\nline two")).toBe(17);
11
- expect(countCharacters("👍🏽👍🏽")).toBe(2);
12
- });
13
- });
14
-
15
- describe("auto resize", () => {
16
- it("measures rows as border-box height", () => {
17
- expect(heightForRows(1, metrics)).toBe(38);
18
- expect(heightForRows(3, metrics)).toBe(78);
19
- });
20
-
21
- it("never shrinks below minRows", () => {
22
- // One line of content: scrollHeight = 20 + 16 padding.
23
- expect(fitHeight(36, metrics, 3)).toEqual({ height: 78, overflow: false });
24
- });
25
-
26
- it("grows with the content", () => {
27
- expect(fitHeight(136, metrics, 3)).toEqual({ height: 138, overflow: false });
28
- });
29
-
30
- it("stops at maxRows and reports that it should scroll", () => {
31
- expect(fitHeight(416, metrics, 3, 5)).toEqual({ height: 118, overflow: true });
32
- expect(fitHeight(96, metrics, 3, 5)).toEqual({ height: 98, overflow: false });
33
- });
34
-
35
- it("keeps maxRows from undercutting minRows", () => {
36
- expect(fitHeight(36, metrics, 4, 2).height).toBe(98);
37
- });
38
- });
@@ -1,256 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
- import { createToastApi } from "../core/api";
3
- import { createToastStore, visibleToasts, type ToastStore } from "../core/store";
4
- import { toast, toastStore } from "../core/toast";
5
-
6
- beforeEach(() => {
7
- vi.useFakeTimers();
8
- });
9
-
10
- afterEach(() => {
11
- vi.useRealTimers();
12
- });
13
-
14
- const states = (store: ToastStore) =>
15
- store.getSnapshot().toasts.map((item) => `${item.id}:${item.state}`);
16
-
17
- const find = (store: ToastStore, id: string) =>
18
- store.getSnapshot().toasts.find((item) => item.id === id);
19
-
20
- describe("showing", () => {
21
- it("adds toasts newest first, with defaults", () => {
22
- const store = createToastStore();
23
- const first = store.show("Saved");
24
- const second = store.show("Deleted", { type: "error", description: "3 rows" });
25
-
26
- expect([first, second]).toEqual(["toast-1", "toast-2"]);
27
- const [newest, oldest] = store.getSnapshot().toasts;
28
- expect(newest).toMatchObject({ id: "toast-2", type: "error", description: "3 rows" });
29
- expect(oldest).toMatchObject({
30
- title: "Saved",
31
- type: "default",
32
- duration: 5000,
33
- dismissible: true,
34
- state: "open",
35
- version: 1,
36
- });
37
- });
38
-
39
- it("updates a toast shown again with the same id, keeping its place", () => {
40
- const store = createToastStore();
41
- store.show("Syncing", { id: "sync" });
42
- store.show("Other");
43
- store.show("Synced", { id: "sync", type: "success" });
44
-
45
- expect(states(store)).toEqual(["toast-1:open", "sync:open"]);
46
- expect(find(store, "sync")).toMatchObject({ title: "Synced", type: "success", version: 2 });
47
- });
48
-
49
- it("restarts the countdown when the same id is shown again", () => {
50
- const store = createToastStore();
51
- store.show("Draft saved", { id: "draft" });
52
- vi.advanceTimersByTime(4000);
53
- store.show("Draft saved again", { id: "draft" });
54
- vi.advanceTimersByTime(4000);
55
- expect(find(store, "draft")?.state).toBe("open");
56
- vi.advanceTimersByTime(1000);
57
- expect(find(store, "draft")?.state).toBe("closing");
58
- });
59
-
60
- it("brings back a toast that is fading out instead of stacking a copy", () => {
61
- const store = createToastStore();
62
- store.show("Offline", { id: "net" });
63
- store.dismiss("net");
64
- store.show("Still offline", { id: "net" });
65
- vi.advanceTimersByTime(300);
66
- expect(states(store)).toEqual(["net:open"]);
67
- });
68
- });
69
-
70
- describe("timing", () => {
71
- it("closes after its duration, then leaves after the exit animation", () => {
72
- const store = createToastStore();
73
- const onAutoClose = vi.fn();
74
- const onDismiss = vi.fn();
75
- store.show("Saved", { duration: 2000, onAutoClose, onDismiss });
76
-
77
- vi.advanceTimersByTime(1999);
78
- expect(states(store)).toEqual(["toast-1:open"]);
79
- vi.advanceTimersByTime(1);
80
- expect(states(store)).toEqual(["toast-1:closing"]);
81
- expect(onAutoClose).toHaveBeenCalledOnce();
82
- expect(onDismiss).not.toHaveBeenCalled();
83
- vi.advanceTimersByTime(200);
84
- expect(states(store)).toEqual([]);
85
- });
86
-
87
- it("keeps sticky and loading toasts until dismissed", () => {
88
- const store = createToastStore();
89
- store.show("Zero", { duration: 0 });
90
- store.show("Infinite", { duration: Infinity });
91
- store.show("Working", { type: "loading" });
92
-
93
- vi.advanceTimersByTime(60_000);
94
- expect(states(store)).toEqual(["toast-3:open", "toast-2:open", "toast-1:open"]);
95
- expect(find(store, "toast-1")?.duration).toBe(Infinity);
96
- });
97
-
98
- it("dismisses one toast or all of them", () => {
99
- const store = createToastStore();
100
- const onDismiss = vi.fn();
101
- store.show("A", { onDismiss });
102
- store.show("B");
103
- store.show("C");
104
-
105
- store.dismiss("toast-1");
106
- expect(onDismiss).toHaveBeenCalledWith(expect.objectContaining({ id: "toast-1" }));
107
- expect(states(store)).toEqual(["toast-3:open", "toast-2:open", "toast-1:closing"]);
108
-
109
- store.dismiss();
110
- vi.advanceTimersByTime(200);
111
- expect(states(store)).toEqual([]);
112
- });
113
-
114
- it("holds the remaining time while paused", () => {
115
- const store = createToastStore();
116
- store.show("Saved");
117
-
118
- vi.advanceTimersByTime(3000);
119
- store.pause();
120
- vi.advanceTimersByTime(10_000);
121
- expect(states(store)).toEqual(["toast-1:open"]);
122
-
123
- store.resume();
124
- vi.advanceTimersByTime(1999);
125
- expect(states(store)).toEqual(["toast-1:open"]);
126
- vi.advanceTimersByTime(1);
127
- expect(states(store)).toEqual(["toast-1:closing"]);
128
- });
129
-
130
- it("queues toasts beyond the limit with their full time", () => {
131
- const store = createToastStore({ limit: 3 });
132
- for (const title of ["A", "B", "C", "D"]) store.show(title);
133
-
134
- expect(visibleToasts(store.getSnapshot()).map((item) => item.title)).toEqual(["D", "C", "B"]);
135
-
136
- // B, C and D run out; A only starts counting once it gets a slot.
137
- vi.advanceTimersByTime(5000);
138
- expect(find(store, "toast-1")?.state).toBe("open");
139
- expect(visibleToasts(store.getSnapshot()).map((item) => item.title)).toEqual([
140
- "D",
141
- "C",
142
- "B",
143
- "A",
144
- ]);
145
- vi.advanceTimersByTime(4999);
146
- expect(find(store, "toast-1")?.state).toBe("open");
147
- vi.advanceTimersByTime(1);
148
- expect(find(store, "toast-1")?.state).toBe("closing");
149
- });
150
- });
151
-
152
- describe("updates", () => {
153
- it("gives a loading toast a normal duration once its type changes", () => {
154
- const store = createToastStore();
155
- const id = store.show("Uploading", { type: "loading" });
156
-
157
- store.update(id, { description: "40%" });
158
- expect(find(store, id)).toMatchObject({ duration: Infinity, description: "40%", version: 2 });
159
-
160
- store.update(id, { type: "success", title: "Uploaded" });
161
- expect(find(store, id)).toMatchObject({ type: "success", title: "Uploaded", duration: 5000 });
162
- vi.advanceTimersByTime(5000);
163
- expect(find(store, id)?.state).toBe("closing");
164
- });
165
-
166
- it("ignores ids that are not open", () => {
167
- const store = createToastStore();
168
- store.update("missing", { title: "Nope" });
169
- expect(states(store)).toEqual([]);
170
- });
171
-
172
- it("applies a new limit and notifies subscribers", () => {
173
- const store = createToastStore();
174
- const listener = vi.fn();
175
- store.subscribe(listener);
176
-
177
- store.configure({ limit: 1, duration: undefined });
178
- expect(store.getSnapshot().limit).toBe(1);
179
- expect(listener).toHaveBeenCalledOnce();
180
-
181
- store.pause();
182
- store.pause();
183
- expect(listener).toHaveBeenCalledTimes(2);
184
- });
185
-
186
- it("keeps the same snapshot object until something changes", () => {
187
- const store = createToastStore();
188
- const before = store.getSnapshot();
189
- expect(store.getSnapshot()).toBe(before);
190
- store.show("Hi");
191
- expect(store.getSnapshot()).not.toBe(before);
192
- });
193
- });
194
-
195
- describe("toast api", () => {
196
- it("sets the type from the shortcut used", () => {
197
- const store = createToastStore();
198
- const api = createToastApi(store);
199
- api.success("Saved");
200
- api.warning("Low disk");
201
- api.custom(({ id }) => id);
202
-
203
- const [custom, warning, success] = store.getSnapshot().toasts;
204
- expect(success.type).toBe("success");
205
- expect(warning.type).toBe("warning");
206
- expect(typeof custom.render).toBe("function");
207
- });
208
-
209
- it("turns a promise into a success toast", async () => {
210
- const store = createToastStore();
211
- const api = createToastApi(store);
212
- const result = api.promise(Promise.resolve(42), {
213
- loading: "Saving",
214
- success: (value) => `Saved ${value}`,
215
- error: "Failed",
216
- });
217
-
218
- expect(store.getSnapshot().toasts[0]).toMatchObject({ type: "loading", title: "Saving" });
219
- await expect(result).resolves.toBe(42);
220
- expect(store.getSnapshot().toasts[0]).toMatchObject({ type: "success", title: "Saved 42" });
221
- });
222
-
223
- it("turns a rejected promise into an error toast and still rejects", async () => {
224
- const store = createToastStore();
225
- const api = createToastApi(store);
226
- const result = api.promise(() => Promise.reject(new Error("Network down")), {
227
- loading: "Saving",
228
- error: (error) => (error as Error).message,
229
- });
230
-
231
- await expect(result).rejects.toThrow("Network down");
232
- expect(store.getSnapshot().toasts[0]).toMatchObject({ type: "error", title: "Network down" });
233
- });
234
-
235
- it("closes quietly when a promise message is left out", async () => {
236
- const store = createToastStore();
237
- const api = createToastApi(store);
238
- await api.promise(Promise.resolve("ok"), { loading: "Checking" });
239
- expect(states(store)).toEqual(["toast-1:closing"]);
240
- });
241
-
242
- it("does nothing where a toast cannot be shown", async () => {
243
- const store = createToastStore();
244
- const api = createToastApi(store, () => false);
245
- expect(api("Hidden")).toBe("");
246
- expect(api.success("Hidden", { id: "kept" })).toBe("kept");
247
- await expect(api.promise(Promise.resolve(1), { loading: "…" })).resolves.toBe(1);
248
- expect(states(store)).toEqual([]);
249
- });
250
-
251
- it("ignores the default toast() on a server, where there is no document", () => {
252
- expect(typeof document).toBe("undefined");
253
- expect(toast("Rendered on the server")).toBe("");
254
- expect(toastStore.getSnapshot().toasts).toHaveLength(0);
255
- });
256
- });
@@ -1,42 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { isHoverPointer, nextVisibility } from "../core/visibility";
3
-
4
- const delays = { open: 400, close: 120 };
5
-
6
- describe("nextVisibility", () => {
7
- it("waits before showing on hover, so a swept toolbar stays quiet", () => {
8
- expect(nextVisibility({ hovered: true, focused: false }, delays)).toEqual({
9
- open: true,
10
- delay: 400,
11
- });
12
- });
13
-
14
- it("shows immediately on keyboard focus", () => {
15
- expect(nextVisibility({ hovered: false, focused: true }, delays)).toEqual({
16
- open: true,
17
- delay: 0,
18
- });
19
- });
20
-
21
- it("stays open while focused, even after the pointer leaves", () => {
22
- expect(nextVisibility({ hovered: false, focused: true }, delays).open).toBe(true);
23
- });
24
-
25
- it("waits a moment before hiding, so crossing a gap does not flicker", () => {
26
- expect(nextVisibility({ hovered: false, focused: false }, delays)).toEqual({
27
- open: false,
28
- delay: 120,
29
- });
30
- });
31
- });
32
-
33
- describe("isHoverPointer", () => {
34
- it("counts a mouse", () => {
35
- expect(isHoverPointer("mouse")).toBe(true);
36
- });
37
-
38
- it("ignores touch and pen, where a tooltip would cover what was tapped", () => {
39
- expect(isHoverPointer("touch")).toBe(false);
40
- expect(isHoverPointer("pen")).toBe(false);
41
- });
42
- });