@assure-one/design-system 1.31.0 → 1.32.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.
- package/README.md +24 -0
- package/codemods/0.2.0-radix-migration.mjs +315 -0
- package/codemods/README.md +305 -0
- package/codemods/lib/css-selectors.mjs +33 -0
- package/codemods/lib/css-values.mjs +223 -0
- package/codemods/lib/ds-stylesheet.mjs +168 -0
- package/codemods/lib/environment.mjs +72 -0
- package/codemods/lib/files.mjs +100 -0
- package/codemods/lib/jsx.mjs +0 -0
- package/codemods/lib/ledger.mjs +84 -0
- package/codemods/lib/registry.mjs +30 -0
- package/codemods/lib/report.mjs +119 -0
- package/codemods/lib/runner.mjs +164 -0
- package/codemods/run.mjs +161 -0
- package/codemods/transforms/cm-15-dom-selectors.mjs +573 -0
- package/codemods/transforms/cm-16-globals-css.mjs +487 -0
- package/dist/css/base.css +60 -0
- package/dist/css/legacy-aliases.css +489 -0
- package/dist/css/shadcn.css +155 -0
- package/dist/css/tailwind.css +233 -0
- package/dist/css/tokens.css +439 -0
- package/dist/index.d.ts +279 -24
- package/dist/index.js +1513 -584
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/testing/index.cjs +458 -0
- package/dist/testing/index.d.cts +253 -0
- package/dist/testing/index.d.ts +253 -0
- package/dist/testing/index.js +452 -0
- package/dist/testing/setup.cjs +123 -0
- package/dist/testing/setup.js +121 -0
- package/dist/testing/style-stub.cjs +7 -0
- package/dist/testing/style-stub.js +5 -0
- package/dist/tokens/index.d.ts +96 -96
- package/dist/tokens/index.js +48 -48
- package/dist/tokens/index.js.map +1 -1
- package/package.json +73 -5
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import { act } from 'react';
|
|
2
|
+
|
|
3
|
+
// src/testing/jest-preset.ts
|
|
4
|
+
var PACKAGE = "@assure-one/design-system";
|
|
5
|
+
function escapeRegExp(value) {
|
|
6
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7
|
+
}
|
|
8
|
+
function trimSeparators(path) {
|
|
9
|
+
return path.replace(/[\\/]+$/, "");
|
|
10
|
+
}
|
|
11
|
+
function moduleDir() {
|
|
12
|
+
if (typeof __dirname === "string") return trimSeparators(__dirname);
|
|
13
|
+
const path = decodeURIComponent(new URL(".", import.meta.url).pathname);
|
|
14
|
+
const native = /^\/[A-Za-z]:\//.test(path) ? path.slice(1).replace(/\//g, "\\") : path;
|
|
15
|
+
return trimSeparators(native);
|
|
16
|
+
}
|
|
17
|
+
function publishedLocations() {
|
|
18
|
+
const dir = moduleDir();
|
|
19
|
+
const sep = dir.includes("\\") && !dir.includes("/") ? "\\" : "/";
|
|
20
|
+
return {
|
|
21
|
+
distDir: dir.replace(/[\\/][^\\/]+$/, ""),
|
|
22
|
+
setupFile: `${dir}${sep}setup.cjs`,
|
|
23
|
+
styleStub: `${dir}${sep}style-stub.cjs`
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function exemptFromIgnorePattern(pattern, distDir) {
|
|
27
|
+
const sep = "[\\\\/]";
|
|
28
|
+
const byName = `${sep}@assure-one${sep}design-system${sep}dist${sep}`;
|
|
29
|
+
const byPath = escapeRegExp(trimSeparators(distDir)) + sep;
|
|
30
|
+
return `^(?!.*(?:${byPath}|${byName}))[\\s\\S]*?(?:${pattern})`;
|
|
31
|
+
}
|
|
32
|
+
function createDesignSystemPreset(locations) {
|
|
33
|
+
return (config, options = {}) => async () => {
|
|
34
|
+
const resolved = (typeof config === "function" ? await config() : config) ?? {};
|
|
35
|
+
const { distDir, setupFile, styleStub } = locations();
|
|
36
|
+
const file = (name) => `${trimSeparators(distDir)}/${name}`;
|
|
37
|
+
const ignore = resolved.transformIgnorePatterns ?? ["/node_modules/"];
|
|
38
|
+
const shims = options.polyfills === false ? [] : [setupFile];
|
|
39
|
+
const own = {
|
|
40
|
+
[`^${escapeRegExp(PACKAGE)}$`]: file("index.js"),
|
|
41
|
+
[`^${escapeRegExp(PACKAGE)}/tokens$`]: file("tokens/index.js"),
|
|
42
|
+
[`^${escapeRegExp(PACKAGE)}/styles\\.css$`]: styleStub
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
...resolved,
|
|
46
|
+
// Jest uses the first matching key, so these come first. Spreading
|
|
47
|
+
// them again keeps their values when the app maps the same key to a stub.
|
|
48
|
+
moduleNameMapper: { ...own, ...resolved.moduleNameMapper, ...own },
|
|
49
|
+
transformIgnorePatterns: ignore.map((pattern) => exemptFromIgnorePattern(pattern, distDir)),
|
|
50
|
+
setupFiles: [...shims, ...resolved.setupFiles ?? []]
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
var withAssureDesignSystem = createDesignSystemPreset(publishedLocations);
|
|
55
|
+
|
|
56
|
+
// src/testing/polyfills.ts
|
|
57
|
+
var TOP_LAYER = /* @__PURE__ */ Symbol.for("@assure-one/design-system/testing:top-layer-guard");
|
|
58
|
+
var TOP_LAYER_SELECTORS = /* @__PURE__ */ new Set([":modal", ":fullscreen", ":popover-open"]);
|
|
59
|
+
function installDomPolyfills(options = {}) {
|
|
60
|
+
if (typeof window === "undefined" || typeof document === "undefined") return [];
|
|
61
|
+
const installed = [];
|
|
62
|
+
const g = globalThis;
|
|
63
|
+
const w = window;
|
|
64
|
+
if (typeof g.ResizeObserver === "undefined") {
|
|
65
|
+
g.ResizeObserver = class ResizeObserver {
|
|
66
|
+
observe() {
|
|
67
|
+
}
|
|
68
|
+
unobserve() {
|
|
69
|
+
}
|
|
70
|
+
disconnect() {
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
installed.push("ResizeObserver");
|
|
74
|
+
}
|
|
75
|
+
if (typeof w.matchMedia !== "function") {
|
|
76
|
+
const matches3 = options.matchMedia ?? (() => false);
|
|
77
|
+
w.matchMedia = (query) => ({
|
|
78
|
+
matches: matches3(query),
|
|
79
|
+
media: query,
|
|
80
|
+
onchange: null,
|
|
81
|
+
addListener() {
|
|
82
|
+
},
|
|
83
|
+
removeListener() {
|
|
84
|
+
},
|
|
85
|
+
addEventListener() {
|
|
86
|
+
},
|
|
87
|
+
removeEventListener() {
|
|
88
|
+
},
|
|
89
|
+
dispatchEvent: () => false
|
|
90
|
+
});
|
|
91
|
+
installed.push("matchMedia");
|
|
92
|
+
}
|
|
93
|
+
if (typeof g.PointerEvent === "undefined") {
|
|
94
|
+
g.PointerEvent = class PointerEvent extends MouseEvent {
|
|
95
|
+
pointerId;
|
|
96
|
+
pointerType;
|
|
97
|
+
width;
|
|
98
|
+
height;
|
|
99
|
+
pressure;
|
|
100
|
+
isPrimary;
|
|
101
|
+
constructor(type, init = {}) {
|
|
102
|
+
super(type, init);
|
|
103
|
+
this.pointerId = init.pointerId ?? 1;
|
|
104
|
+
this.pointerType = init.pointerType ?? "mouse";
|
|
105
|
+
this.width = init.width ?? 1;
|
|
106
|
+
this.height = init.height ?? 1;
|
|
107
|
+
this.pressure = init.pressure ?? 0;
|
|
108
|
+
this.isPrimary = init.isPrimary ?? true;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
installed.push("PointerEvent");
|
|
112
|
+
}
|
|
113
|
+
const proto = window.Element.prototype;
|
|
114
|
+
const methods = [
|
|
115
|
+
["hasPointerCapture", () => false],
|
|
116
|
+
["setPointerCapture", () => void 0],
|
|
117
|
+
["releasePointerCapture", () => void 0],
|
|
118
|
+
["scrollIntoView", () => void 0]
|
|
119
|
+
];
|
|
120
|
+
for (const [name, impl] of methods) {
|
|
121
|
+
if (typeof proto[name] !== "function") {
|
|
122
|
+
proto[name] = impl;
|
|
123
|
+
installed.push(name);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const matches2 = proto.matches;
|
|
127
|
+
if (/\bjsdom\b/.test(window.navigator.userAgent) && typeof matches2 === "function" && !matches2[TOP_LAYER]) {
|
|
128
|
+
const guarded = Object.assign(
|
|
129
|
+
function(selector) {
|
|
130
|
+
return TOP_LAYER_SELECTORS.has(String(selector).trim()) ? false : matches2.call(this, selector);
|
|
131
|
+
},
|
|
132
|
+
{ [TOP_LAYER]: true }
|
|
133
|
+
);
|
|
134
|
+
proto.matches = guarded;
|
|
135
|
+
installed.push("topLayerSelectors");
|
|
136
|
+
}
|
|
137
|
+
if (typeof g.DOMRect === "undefined") {
|
|
138
|
+
class DOMRectShim {
|
|
139
|
+
x;
|
|
140
|
+
y;
|
|
141
|
+
width;
|
|
142
|
+
height;
|
|
143
|
+
constructor(x = 0, y = 0, width = 0, height = 0) {
|
|
144
|
+
this.x = x;
|
|
145
|
+
this.y = y;
|
|
146
|
+
this.width = width;
|
|
147
|
+
this.height = height;
|
|
148
|
+
}
|
|
149
|
+
get left() {
|
|
150
|
+
return Math.min(this.x, this.x + this.width);
|
|
151
|
+
}
|
|
152
|
+
get right() {
|
|
153
|
+
return Math.max(this.x, this.x + this.width);
|
|
154
|
+
}
|
|
155
|
+
get top() {
|
|
156
|
+
return Math.min(this.y, this.y + this.height);
|
|
157
|
+
}
|
|
158
|
+
get bottom() {
|
|
159
|
+
return Math.max(this.y, this.y + this.height);
|
|
160
|
+
}
|
|
161
|
+
static fromRect(rect = {}) {
|
|
162
|
+
return new DOMRectShim(rect.x, rect.y, rect.width, rect.height);
|
|
163
|
+
}
|
|
164
|
+
toJSON() {
|
|
165
|
+
const { x, y, width, height, left, right, top, bottom } = this;
|
|
166
|
+
return { x, y, width, height, left, right, top, bottom };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
g.DOMRect = DOMRectShim;
|
|
170
|
+
installed.push("DOMRect");
|
|
171
|
+
}
|
|
172
|
+
return installed;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/testing/toast-recorder.ts
|
|
176
|
+
var REGION = '[role="region"][aria-live]';
|
|
177
|
+
var VARIANT_CLASSES = [
|
|
178
|
+
["bg-success-bg", "success"],
|
|
179
|
+
["bg-warning-bg", "warning"],
|
|
180
|
+
["bg-info/12", "info"]
|
|
181
|
+
];
|
|
182
|
+
function text(node) {
|
|
183
|
+
return (node?.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
184
|
+
}
|
|
185
|
+
function isElement(node) {
|
|
186
|
+
return !!node && node.nodeType === Node.ELEMENT_NODE;
|
|
187
|
+
}
|
|
188
|
+
function searchable(node) {
|
|
189
|
+
const t = node.nodeType;
|
|
190
|
+
return t === Node.ELEMENT_NODE || t === Node.DOCUMENT_NODE || t === Node.DOCUMENT_FRAGMENT_NODE ? node : null;
|
|
191
|
+
}
|
|
192
|
+
function isToast(node, parent) {
|
|
193
|
+
return isElement(node) && node.hasAttribute("data-state") && isElement(parent) && parent.matches(REGION);
|
|
194
|
+
}
|
|
195
|
+
function read(el) {
|
|
196
|
+
const role = el.getAttribute("role") ?? "status";
|
|
197
|
+
if (role === "group") {
|
|
198
|
+
return { title: "", description: "", variant: "custom", role, action: null, text: text(el) };
|
|
199
|
+
}
|
|
200
|
+
const chip = el.querySelector('span[aria-hidden="true"]');
|
|
201
|
+
const body = chip?.nextElementSibling ?? null;
|
|
202
|
+
const paragraphs = Array.from(body?.querySelectorAll(":scope > p") ?? []);
|
|
203
|
+
const title = paragraphs.find((p) => p.classList.contains("font-semibold"));
|
|
204
|
+
const description = paragraphs.find((p) => p !== title);
|
|
205
|
+
const action = body?.querySelector(":scope > button") ?? null;
|
|
206
|
+
let variant = "default";
|
|
207
|
+
if (role === "alert") variant = "destructive";
|
|
208
|
+
else if (chip?.querySelector(".animate-spin")) variant = "loading";
|
|
209
|
+
else {
|
|
210
|
+
for (const [cls, v] of VARIANT_CLASSES) if (chip?.classList.contains(cls)) variant = v;
|
|
211
|
+
}
|
|
212
|
+
const parts = [text(title), text(description), text(action)];
|
|
213
|
+
return {
|
|
214
|
+
title: parts[0],
|
|
215
|
+
description: parts[1],
|
|
216
|
+
variant,
|
|
217
|
+
role,
|
|
218
|
+
action: action ? parts[2] : null,
|
|
219
|
+
text: parts.filter(Boolean).join(" ")
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function createToastRecorder(options = {}) {
|
|
223
|
+
const root = options.root ?? document.body;
|
|
224
|
+
const entries = [];
|
|
225
|
+
const known = /* @__PURE__ */ new Set();
|
|
226
|
+
const add = (el) => {
|
|
227
|
+
if (known.has(el)) return;
|
|
228
|
+
known.add(el);
|
|
229
|
+
entries.push({ el, snapshot: read(el) });
|
|
230
|
+
};
|
|
231
|
+
const within = (node) => {
|
|
232
|
+
searchable(node)?.querySelectorAll(`${REGION} > [data-state]`).forEach(add);
|
|
233
|
+
};
|
|
234
|
+
const handle = (records) => {
|
|
235
|
+
for (const record of records) {
|
|
236
|
+
record.addedNodes.forEach((node) => {
|
|
237
|
+
if (isToast(node, record.target)) add(node);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
for (const record of records) record.addedNodes.forEach(within);
|
|
241
|
+
for (const entry of entries) if (entry.el.isConnected) entry.snapshot = read(entry.el);
|
|
242
|
+
};
|
|
243
|
+
const observer = new MutationObserver(handle);
|
|
244
|
+
observer.observe(root, { childList: true, subtree: true, characterData: true, attributes: true });
|
|
245
|
+
let active = true;
|
|
246
|
+
within(root);
|
|
247
|
+
const sync = () => {
|
|
248
|
+
if (active) handle(observer.takeRecords());
|
|
249
|
+
};
|
|
250
|
+
const view = () => entries.map(({ el, snapshot }) => ({
|
|
251
|
+
...snapshot,
|
|
252
|
+
open: el.isConnected && el.dataset.state === "open"
|
|
253
|
+
}));
|
|
254
|
+
return {
|
|
255
|
+
get toasts() {
|
|
256
|
+
sync();
|
|
257
|
+
return view();
|
|
258
|
+
},
|
|
259
|
+
get visible() {
|
|
260
|
+
const open = searchable(root)?.querySelectorAll(
|
|
261
|
+
`${REGION} > [data-state="open"]`
|
|
262
|
+
);
|
|
263
|
+
return Array.from(open ?? []).map((el) => ({ ...read(el), open: true }));
|
|
264
|
+
},
|
|
265
|
+
last() {
|
|
266
|
+
sync();
|
|
267
|
+
return view().at(-1);
|
|
268
|
+
},
|
|
269
|
+
find(match) {
|
|
270
|
+
sync();
|
|
271
|
+
const test = (value) => typeof match === "string" ? value.includes(match) : match.test(value);
|
|
272
|
+
return view().find((t) => test(t.title) || test(t.description) || test(t.text));
|
|
273
|
+
},
|
|
274
|
+
clear() {
|
|
275
|
+
sync();
|
|
276
|
+
entries.length = 0;
|
|
277
|
+
},
|
|
278
|
+
stop() {
|
|
279
|
+
sync();
|
|
280
|
+
active = false;
|
|
281
|
+
observer.disconnect();
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function normalise(text2) {
|
|
286
|
+
return (text2 ?? "").replace(/\s+/g, " ").trim();
|
|
287
|
+
}
|
|
288
|
+
function matches(text2, match) {
|
|
289
|
+
return typeof match === "string" ? text2 === normalise(match) : match.test(text2);
|
|
290
|
+
}
|
|
291
|
+
function describe(match) {
|
|
292
|
+
return typeof match === "string" ? JSON.stringify(match) : String(match);
|
|
293
|
+
}
|
|
294
|
+
function accessibleText(element) {
|
|
295
|
+
const labelledBy = element.getAttribute("aria-labelledby");
|
|
296
|
+
if (labelledBy) {
|
|
297
|
+
const doc = element.ownerDocument;
|
|
298
|
+
const text2 = labelledBy.split(/\s+/).map((id) => doc.getElementById(id)?.textContent ?? "").join(" ");
|
|
299
|
+
if (normalise(text2)) return normalise(text2);
|
|
300
|
+
}
|
|
301
|
+
return normalise(element.getAttribute("aria-label") ?? element.textContent);
|
|
302
|
+
}
|
|
303
|
+
function isDisabled(element) {
|
|
304
|
+
return element.hasAttribute("disabled") || element.getAttribute("aria-disabled") === "true" || element.hasAttribute("data-disabled");
|
|
305
|
+
}
|
|
306
|
+
function press(target, key) {
|
|
307
|
+
const view = target.ownerDocument.defaultView ?? window;
|
|
308
|
+
const init = { key, code: key === " " ? "Space" : key, bubbles: true, cancelable: true };
|
|
309
|
+
target.dispatchEvent(new view.KeyboardEvent("keydown", init));
|
|
310
|
+
target.dispatchEvent(new view.KeyboardEvent("keyup", init));
|
|
311
|
+
}
|
|
312
|
+
async function flush() {
|
|
313
|
+
await act(async () => {
|
|
314
|
+
await Promise.resolve();
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
async function waitUntil(find, maxFlushes) {
|
|
318
|
+
for (let i = 0; i <= maxFlushes; i++) {
|
|
319
|
+
const found = find();
|
|
320
|
+
if (found) return found;
|
|
321
|
+
await flush();
|
|
322
|
+
}
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
function resolveTrigger(target, name, role) {
|
|
326
|
+
if (typeof target.getByRole === "function") {
|
|
327
|
+
if (name === void 0)
|
|
328
|
+
throw new Error(`A ${role} name is required when passing a query object.`);
|
|
329
|
+
return target.getByRole(role, { name });
|
|
330
|
+
}
|
|
331
|
+
return target;
|
|
332
|
+
}
|
|
333
|
+
function popupOf(trigger, role) {
|
|
334
|
+
const doc = trigger.ownerDocument;
|
|
335
|
+
const id = trigger.getAttribute("aria-controls");
|
|
336
|
+
const controlled = id ? doc.getElementById(id) : null;
|
|
337
|
+
if (controlled?.getAttribute("role") === role) return controlled;
|
|
338
|
+
if (controlled) {
|
|
339
|
+
const inner = controlled.querySelector(`[role="${role}"]`);
|
|
340
|
+
if (inner) return inner;
|
|
341
|
+
}
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
async function selectOption(target, ...rest) {
|
|
345
|
+
const byQuery = typeof target.getByRole === "function";
|
|
346
|
+
const label = byQuery ? rest[0] : void 0;
|
|
347
|
+
const option = byQuery ? rest[1] : rest[0];
|
|
348
|
+
const settings = (byQuery ? rest[2] : rest[1]) ?? {};
|
|
349
|
+
const maxFlushes = settings.maxFlushes ?? 20;
|
|
350
|
+
const trigger = resolveTrigger(target, label, "combobox");
|
|
351
|
+
if (isDisabled(trigger)) {
|
|
352
|
+
throw new Error(
|
|
353
|
+
`selectOption: the select ${describe(label ?? accessibleText(trigger))} is disabled.`
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
if (trigger.getAttribute("aria-expanded") !== "true") {
|
|
357
|
+
await act(async () => {
|
|
358
|
+
trigger.focus();
|
|
359
|
+
press(trigger, "Enter");
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
const listbox = await waitUntil(() => popupOf(trigger, "listbox"), maxFlushes);
|
|
363
|
+
if (!listbox) {
|
|
364
|
+
throw new Error(
|
|
365
|
+
"selectOption: the list did not open. The helper supports the design-system Select (a combobox trigger that controls a listbox)."
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
const all = Array.from(listbox.querySelectorAll('[role="option"]'));
|
|
369
|
+
const wanted = all.find((el) => matches(accessibleText(el), option));
|
|
370
|
+
if (!wanted) {
|
|
371
|
+
const available = all.map((el) => JSON.stringify(accessibleText(el))).join(", ");
|
|
372
|
+
await act(async () => press(listbox, "Escape"));
|
|
373
|
+
throw new Error(
|
|
374
|
+
`selectOption: no option matches ${describe(option)}. Options: ${available || "(none)"}.`
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
if (isDisabled(wanted)) {
|
|
378
|
+
await act(async () => press(listbox, "Escape"));
|
|
379
|
+
throw new Error(`selectOption: the option ${describe(option)} is disabled.`);
|
|
380
|
+
}
|
|
381
|
+
await act(async () => {
|
|
382
|
+
wanted.focus();
|
|
383
|
+
press(wanted, "Enter");
|
|
384
|
+
});
|
|
385
|
+
const closed = await waitUntil(() => popupOf(trigger, "listbox") ? null : true, maxFlushes);
|
|
386
|
+
if (!closed)
|
|
387
|
+
throw new Error(`selectOption: the list stayed open after choosing ${describe(option)}.`);
|
|
388
|
+
}
|
|
389
|
+
async function openMenu(target, ...rest) {
|
|
390
|
+
const byQuery = typeof target.getByRole === "function";
|
|
391
|
+
const name = byQuery ? rest[0] : void 0;
|
|
392
|
+
const settings = (byQuery ? rest[1] : rest[0]) ?? {};
|
|
393
|
+
const maxFlushes = settings.maxFlushes ?? 20;
|
|
394
|
+
const trigger = resolveTrigger(target, name, "button");
|
|
395
|
+
if (isDisabled(trigger)) {
|
|
396
|
+
throw new Error(
|
|
397
|
+
`openMenu: the trigger ${describe(name ?? accessibleText(trigger))} is disabled.`
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
if (trigger.getAttribute("aria-expanded") !== "true") {
|
|
401
|
+
await act(async () => {
|
|
402
|
+
trigger.focus();
|
|
403
|
+
press(trigger, "ArrowDown");
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
const menu = await waitUntil(() => popupOf(trigger, "menu"), maxFlushes);
|
|
407
|
+
if (!menu) {
|
|
408
|
+
throw new Error(
|
|
409
|
+
"openMenu: the menu did not open. The helper supports the design-system DropdownMenu (a trigger with aria-haspopup that controls a menu)."
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
const items = () => Array.from(
|
|
413
|
+
menu.querySelectorAll(
|
|
414
|
+
'[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"]'
|
|
415
|
+
)
|
|
416
|
+
);
|
|
417
|
+
const getItem = (itemName) => {
|
|
418
|
+
const found = items().find((el) => matches(accessibleText(el), itemName));
|
|
419
|
+
if (!found) {
|
|
420
|
+
const available = items().map((el) => JSON.stringify(accessibleText(el))).join(", ");
|
|
421
|
+
throw new Error(
|
|
422
|
+
`openMenu: no item matches ${describe(itemName)}. Items: ${available || "(none)"}.`
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
return found;
|
|
426
|
+
};
|
|
427
|
+
const waitClosed = async (what) => {
|
|
428
|
+
const closed = await waitUntil(() => menu.isConnected ? null : true, maxFlushes);
|
|
429
|
+
if (!closed) throw new Error(`openMenu: the menu stayed open after ${what}.`);
|
|
430
|
+
};
|
|
431
|
+
return {
|
|
432
|
+
element: menu,
|
|
433
|
+
items,
|
|
434
|
+
getItem,
|
|
435
|
+
async select(itemName) {
|
|
436
|
+
const item = getItem(itemName);
|
|
437
|
+
if (isDisabled(item))
|
|
438
|
+
throw new Error(`openMenu: the item ${describe(itemName)} is disabled.`);
|
|
439
|
+
await act(async () => {
|
|
440
|
+
item.focus();
|
|
441
|
+
press(item, "Enter");
|
|
442
|
+
});
|
|
443
|
+
await waitUntil(() => menu.isConnected ? null : true, maxFlushes);
|
|
444
|
+
},
|
|
445
|
+
async close() {
|
|
446
|
+
await act(async () => press(menu, "Escape"));
|
|
447
|
+
await waitClosed("Escape");
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export { createToastRecorder, installDomPolyfills, openMenu, selectOption, withAssureDesignSystem };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/testing/polyfills.ts
|
|
4
|
+
var TOP_LAYER = /* @__PURE__ */ Symbol.for("@assure-one/design-system/testing:top-layer-guard");
|
|
5
|
+
var TOP_LAYER_SELECTORS = /* @__PURE__ */ new Set([":modal", ":fullscreen", ":popover-open"]);
|
|
6
|
+
function installDomPolyfills(options = {}) {
|
|
7
|
+
if (typeof window === "undefined" || typeof document === "undefined") return [];
|
|
8
|
+
const installed = [];
|
|
9
|
+
const g = globalThis;
|
|
10
|
+
const w = window;
|
|
11
|
+
if (typeof g.ResizeObserver === "undefined") {
|
|
12
|
+
g.ResizeObserver = class ResizeObserver {
|
|
13
|
+
observe() {
|
|
14
|
+
}
|
|
15
|
+
unobserve() {
|
|
16
|
+
}
|
|
17
|
+
disconnect() {
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
installed.push("ResizeObserver");
|
|
21
|
+
}
|
|
22
|
+
if (typeof w.matchMedia !== "function") {
|
|
23
|
+
const matches2 = options.matchMedia ?? (() => false);
|
|
24
|
+
w.matchMedia = (query) => ({
|
|
25
|
+
matches: matches2(query),
|
|
26
|
+
media: query,
|
|
27
|
+
onchange: null,
|
|
28
|
+
addListener() {
|
|
29
|
+
},
|
|
30
|
+
removeListener() {
|
|
31
|
+
},
|
|
32
|
+
addEventListener() {
|
|
33
|
+
},
|
|
34
|
+
removeEventListener() {
|
|
35
|
+
},
|
|
36
|
+
dispatchEvent: () => false
|
|
37
|
+
});
|
|
38
|
+
installed.push("matchMedia");
|
|
39
|
+
}
|
|
40
|
+
if (typeof g.PointerEvent === "undefined") {
|
|
41
|
+
g.PointerEvent = class PointerEvent extends MouseEvent {
|
|
42
|
+
pointerId;
|
|
43
|
+
pointerType;
|
|
44
|
+
width;
|
|
45
|
+
height;
|
|
46
|
+
pressure;
|
|
47
|
+
isPrimary;
|
|
48
|
+
constructor(type, init = {}) {
|
|
49
|
+
super(type, init);
|
|
50
|
+
this.pointerId = init.pointerId ?? 1;
|
|
51
|
+
this.pointerType = init.pointerType ?? "mouse";
|
|
52
|
+
this.width = init.width ?? 1;
|
|
53
|
+
this.height = init.height ?? 1;
|
|
54
|
+
this.pressure = init.pressure ?? 0;
|
|
55
|
+
this.isPrimary = init.isPrimary ?? true;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
installed.push("PointerEvent");
|
|
59
|
+
}
|
|
60
|
+
const proto = window.Element.prototype;
|
|
61
|
+
const methods = [
|
|
62
|
+
["hasPointerCapture", () => false],
|
|
63
|
+
["setPointerCapture", () => void 0],
|
|
64
|
+
["releasePointerCapture", () => void 0],
|
|
65
|
+
["scrollIntoView", () => void 0]
|
|
66
|
+
];
|
|
67
|
+
for (const [name, impl] of methods) {
|
|
68
|
+
if (typeof proto[name] !== "function") {
|
|
69
|
+
proto[name] = impl;
|
|
70
|
+
installed.push(name);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const matches = proto.matches;
|
|
74
|
+
if (/\bjsdom\b/.test(window.navigator.userAgent) && typeof matches === "function" && !matches[TOP_LAYER]) {
|
|
75
|
+
const guarded = Object.assign(
|
|
76
|
+
function(selector) {
|
|
77
|
+
return TOP_LAYER_SELECTORS.has(String(selector).trim()) ? false : matches.call(this, selector);
|
|
78
|
+
},
|
|
79
|
+
{ [TOP_LAYER]: true }
|
|
80
|
+
);
|
|
81
|
+
proto.matches = guarded;
|
|
82
|
+
installed.push("topLayerSelectors");
|
|
83
|
+
}
|
|
84
|
+
if (typeof g.DOMRect === "undefined") {
|
|
85
|
+
class DOMRectShim {
|
|
86
|
+
x;
|
|
87
|
+
y;
|
|
88
|
+
width;
|
|
89
|
+
height;
|
|
90
|
+
constructor(x = 0, y = 0, width = 0, height = 0) {
|
|
91
|
+
this.x = x;
|
|
92
|
+
this.y = y;
|
|
93
|
+
this.width = width;
|
|
94
|
+
this.height = height;
|
|
95
|
+
}
|
|
96
|
+
get left() {
|
|
97
|
+
return Math.min(this.x, this.x + this.width);
|
|
98
|
+
}
|
|
99
|
+
get right() {
|
|
100
|
+
return Math.max(this.x, this.x + this.width);
|
|
101
|
+
}
|
|
102
|
+
get top() {
|
|
103
|
+
return Math.min(this.y, this.y + this.height);
|
|
104
|
+
}
|
|
105
|
+
get bottom() {
|
|
106
|
+
return Math.max(this.y, this.y + this.height);
|
|
107
|
+
}
|
|
108
|
+
static fromRect(rect = {}) {
|
|
109
|
+
return new DOMRectShim(rect.x, rect.y, rect.width, rect.height);
|
|
110
|
+
}
|
|
111
|
+
toJSON() {
|
|
112
|
+
const { x, y, width, height, left, right, top, bottom } = this;
|
|
113
|
+
return { x, y, width, height, left, right, top, bottom };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
g.DOMRect = DOMRectShim;
|
|
117
|
+
installed.push("DOMRect");
|
|
118
|
+
}
|
|
119
|
+
return installed;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/testing/setup.ts
|
|
123
|
+
installDomPolyfills();
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// src/testing/polyfills.ts
|
|
2
|
+
var TOP_LAYER = /* @__PURE__ */ Symbol.for("@assure-one/design-system/testing:top-layer-guard");
|
|
3
|
+
var TOP_LAYER_SELECTORS = /* @__PURE__ */ new Set([":modal", ":fullscreen", ":popover-open"]);
|
|
4
|
+
function installDomPolyfills(options = {}) {
|
|
5
|
+
if (typeof window === "undefined" || typeof document === "undefined") return [];
|
|
6
|
+
const installed = [];
|
|
7
|
+
const g = globalThis;
|
|
8
|
+
const w = window;
|
|
9
|
+
if (typeof g.ResizeObserver === "undefined") {
|
|
10
|
+
g.ResizeObserver = class ResizeObserver {
|
|
11
|
+
observe() {
|
|
12
|
+
}
|
|
13
|
+
unobserve() {
|
|
14
|
+
}
|
|
15
|
+
disconnect() {
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
installed.push("ResizeObserver");
|
|
19
|
+
}
|
|
20
|
+
if (typeof w.matchMedia !== "function") {
|
|
21
|
+
const matches2 = options.matchMedia ?? (() => false);
|
|
22
|
+
w.matchMedia = (query) => ({
|
|
23
|
+
matches: matches2(query),
|
|
24
|
+
media: query,
|
|
25
|
+
onchange: null,
|
|
26
|
+
addListener() {
|
|
27
|
+
},
|
|
28
|
+
removeListener() {
|
|
29
|
+
},
|
|
30
|
+
addEventListener() {
|
|
31
|
+
},
|
|
32
|
+
removeEventListener() {
|
|
33
|
+
},
|
|
34
|
+
dispatchEvent: () => false
|
|
35
|
+
});
|
|
36
|
+
installed.push("matchMedia");
|
|
37
|
+
}
|
|
38
|
+
if (typeof g.PointerEvent === "undefined") {
|
|
39
|
+
g.PointerEvent = class PointerEvent extends MouseEvent {
|
|
40
|
+
pointerId;
|
|
41
|
+
pointerType;
|
|
42
|
+
width;
|
|
43
|
+
height;
|
|
44
|
+
pressure;
|
|
45
|
+
isPrimary;
|
|
46
|
+
constructor(type, init = {}) {
|
|
47
|
+
super(type, init);
|
|
48
|
+
this.pointerId = init.pointerId ?? 1;
|
|
49
|
+
this.pointerType = init.pointerType ?? "mouse";
|
|
50
|
+
this.width = init.width ?? 1;
|
|
51
|
+
this.height = init.height ?? 1;
|
|
52
|
+
this.pressure = init.pressure ?? 0;
|
|
53
|
+
this.isPrimary = init.isPrimary ?? true;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
installed.push("PointerEvent");
|
|
57
|
+
}
|
|
58
|
+
const proto = window.Element.prototype;
|
|
59
|
+
const methods = [
|
|
60
|
+
["hasPointerCapture", () => false],
|
|
61
|
+
["setPointerCapture", () => void 0],
|
|
62
|
+
["releasePointerCapture", () => void 0],
|
|
63
|
+
["scrollIntoView", () => void 0]
|
|
64
|
+
];
|
|
65
|
+
for (const [name, impl] of methods) {
|
|
66
|
+
if (typeof proto[name] !== "function") {
|
|
67
|
+
proto[name] = impl;
|
|
68
|
+
installed.push(name);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const matches = proto.matches;
|
|
72
|
+
if (/\bjsdom\b/.test(window.navigator.userAgent) && typeof matches === "function" && !matches[TOP_LAYER]) {
|
|
73
|
+
const guarded = Object.assign(
|
|
74
|
+
function(selector) {
|
|
75
|
+
return TOP_LAYER_SELECTORS.has(String(selector).trim()) ? false : matches.call(this, selector);
|
|
76
|
+
},
|
|
77
|
+
{ [TOP_LAYER]: true }
|
|
78
|
+
);
|
|
79
|
+
proto.matches = guarded;
|
|
80
|
+
installed.push("topLayerSelectors");
|
|
81
|
+
}
|
|
82
|
+
if (typeof g.DOMRect === "undefined") {
|
|
83
|
+
class DOMRectShim {
|
|
84
|
+
x;
|
|
85
|
+
y;
|
|
86
|
+
width;
|
|
87
|
+
height;
|
|
88
|
+
constructor(x = 0, y = 0, width = 0, height = 0) {
|
|
89
|
+
this.x = x;
|
|
90
|
+
this.y = y;
|
|
91
|
+
this.width = width;
|
|
92
|
+
this.height = height;
|
|
93
|
+
}
|
|
94
|
+
get left() {
|
|
95
|
+
return Math.min(this.x, this.x + this.width);
|
|
96
|
+
}
|
|
97
|
+
get right() {
|
|
98
|
+
return Math.max(this.x, this.x + this.width);
|
|
99
|
+
}
|
|
100
|
+
get top() {
|
|
101
|
+
return Math.min(this.y, this.y + this.height);
|
|
102
|
+
}
|
|
103
|
+
get bottom() {
|
|
104
|
+
return Math.max(this.y, this.y + this.height);
|
|
105
|
+
}
|
|
106
|
+
static fromRect(rect = {}) {
|
|
107
|
+
return new DOMRectShim(rect.x, rect.y, rect.width, rect.height);
|
|
108
|
+
}
|
|
109
|
+
toJSON() {
|
|
110
|
+
const { x, y, width, height, left, right, top, bottom } = this;
|
|
111
|
+
return { x, y, width, height, left, right, top, bottom };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
g.DOMRect = DOMRectShim;
|
|
115
|
+
installed.push("DOMRect");
|
|
116
|
+
}
|
|
117
|
+
return installed;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/testing/setup.ts
|
|
121
|
+
installDomPolyfills();
|