@aurodesignsystem/auro-library 5.14.0 → 5.14.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/CHANGELOG.md +14 -0
- package/bin/generateDocs.mjs +4 -210
- package/bin/generateDocs_index.mjs +4 -210
- package/dist/_chunks/chunk-2MOEVVSZ.mjs +308 -0
- package/dist/_chunks/chunk-H7YO7ERJ.mjs +69 -0
- package/dist/_chunks/chunk-JLCAYVRX.mjs +84 -0
- package/dist/_chunks/chunk-RKBXVLA5.mjs +5983 -0
- package/dist/_chunks/chunk-TDFKN2EP.mjs +38 -0
- package/dist/_chunks/chunk-UY4SIQT6.mjs +201 -0
- package/dist/_chunks/chunk-V7YBXHAI.mjs +32379 -0
- package/dist/bin/generateDocs.mjs +152 -0
- package/dist/bin/generateDocs_index.mjs +152 -0
- package/dist/build/generateDocs.mjs +18 -0
- package/dist/build/generateReadme.mjs +49 -0
- package/dist/build/generateWcaComponent.mjs +6657 -0
- package/dist/build/processors/defaultDocsProcessor.mjs +16 -0
- package/dist/build/processors/defaultDotGithubSync.mjs +12 -0
- package/dist/build/syncGithubFiles.mjs +18 -0
- package/dist/utils/auroTemplateFiller.mjs +8 -0
- package/dist/utils/sharedFileProcessorUtils.mjs +31 -0
- package/package.json +10 -5
- package/scripts/build/generateDocs.mjs +4 -24
- package/scripts/build/generateReadme.mjs +4 -60
- package/scripts/build/generateWcaComponent.mjs +4 -43
- package/scripts/build/postinstall.mjs +10 -10
- package/scripts/build/pre-commit.mjs +13 -7
- package/scripts/build/processors/defaultDocsProcessor.mjs +4 -83
- package/scripts/build/processors/defaultDotGithubSync.mjs +4 -83
- package/scripts/build/syncGithubFiles.mjs +4 -25
- package/scripts/runtime/floatingUI.mjs +2 -1
- package/scripts/runtime/generateUUID/generateUUID.mjs +41 -0
- package/scripts/runtime/generateUUID/index.mjs +1 -0
- package/scripts/utils/ansiColors.mjs +119 -0
- package/scripts/utils/auroLibraryUtils.mjs +87 -51
- package/scripts/utils/auroTemplateFiller.mjs +4 -178
- package/scripts/utils/logger.mjs +27 -16
- package/scripts/utils/sharedFileProcessorUtils.mjs +4 -270
- package/scripts/build/deprecatedProseToFieldPlugin.spec.js +0 -188
- package/scripts/runtime/ClickTracker/test/ClickTracker.test.js +0 -424
- package/scripts/runtime/FocusTrap/test/FocusTrap.test.js +0 -168
- package/scripts/runtime/Focusables/test/Focusables.test.js +0 -185
- package/scripts/runtime/dateUtilities/dateFormatter.test.js +0 -284
- package/scripts/runtime/dateUtilities/dateUtilities.test.js +0 -80
- package/scripts/runtime/floatingUI/test/floatingUI.test.js +0 -189
- package/scripts/runtime/floatingUI.test.js +0 -478
|
@@ -1,424 +0,0 @@
|
|
|
1
|
-
import { expect } from "@open-wc/testing";
|
|
2
|
-
import { html, render } from "lit";
|
|
3
|
-
import sinon from "sinon";
|
|
4
|
-
import { ClickTracker } from "../ClickTracker.mjs";
|
|
5
|
-
|
|
6
|
-
async function fixture(template) {
|
|
7
|
-
const wrapper = document.createElement("div");
|
|
8
|
-
render(template, wrapper);
|
|
9
|
-
document.body.appendChild(wrapper);
|
|
10
|
-
await new Promise((resolve) => setTimeout(resolve, 0)); // Give browser time to render
|
|
11
|
-
return wrapper.firstElementChild;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
describe("ClickTracker", () => {
|
|
15
|
-
let container;
|
|
16
|
-
let clickTracker;
|
|
17
|
-
let onClickSpy;
|
|
18
|
-
let onInnerClickSpy;
|
|
19
|
-
let onOuterClickSpy;
|
|
20
|
-
|
|
21
|
-
beforeEach(async () => {
|
|
22
|
-
// Create a container element for testing
|
|
23
|
-
container = await fixture(html`
|
|
24
|
-
<div id="test-container" style="width: 100px; height: 100px; background: red;">
|
|
25
|
-
<button id="inner-button">Inner Button</button>
|
|
26
|
-
</div>
|
|
27
|
-
`);
|
|
28
|
-
|
|
29
|
-
// Create spies for callback functions
|
|
30
|
-
onClickSpy = sinon.spy();
|
|
31
|
-
onInnerClickSpy = sinon.spy();
|
|
32
|
-
onOuterClickSpy = sinon.spy();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
afterEach(() => {
|
|
36
|
-
if (clickTracker) {
|
|
37
|
-
clickTracker.disconnect();
|
|
38
|
-
clickTracker = null;
|
|
39
|
-
}
|
|
40
|
-
if (container && container.parentNode) {
|
|
41
|
-
container.parentNode.remove();
|
|
42
|
-
}
|
|
43
|
-
sinon.restore();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe("constructor", () => {
|
|
47
|
-
it("should create a new instance with a valid target element", () => {
|
|
48
|
-
expect(() => new ClickTracker({ target: container })).to.not.throw();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("should throw an error if target element is not provided", () => {
|
|
52
|
-
expect(() => new ClickTracker({})).to.throw(
|
|
53
|
-
"ClickHandler: Target element is required",
|
|
54
|
-
);
|
|
55
|
-
expect(() => new ClickTracker({ target: null })).to.throw(
|
|
56
|
-
"ClickHandler: Target element is required",
|
|
57
|
-
);
|
|
58
|
-
expect(() => new ClickTracker({ target: undefined })).to.throw(
|
|
59
|
-
"ClickHandler: Target element is required",
|
|
60
|
-
);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("should accept all callback options", () => {
|
|
64
|
-
clickTracker = new ClickTracker({
|
|
65
|
-
target: container,
|
|
66
|
-
onClick: onClickSpy,
|
|
67
|
-
onInnerClick: onInnerClickSpy,
|
|
68
|
-
onOuterClick: onOuterClickSpy,
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
expect(clickTracker).to.be.instanceOf(ClickTracker);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("should work with only target element provided", () => {
|
|
75
|
-
clickTracker = new ClickTracker({ target: container });
|
|
76
|
-
expect(clickTracker).to.be.instanceOf(ClickTracker);
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
describe("click tracking functionality", () => {
|
|
81
|
-
beforeEach(() => {
|
|
82
|
-
clickTracker = new ClickTracker({
|
|
83
|
-
target: container,
|
|
84
|
-
onClick: onClickSpy,
|
|
85
|
-
onInnerClick: onInnerClickSpy,
|
|
86
|
-
onOuterClick: onOuterClickSpy,
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("should detect clicks inside the target element", async () => {
|
|
91
|
-
const button = container.querySelector("#inner-button");
|
|
92
|
-
|
|
93
|
-
// Simulate a click on the inner button
|
|
94
|
-
const clickEvent = new MouseEvent("click", {
|
|
95
|
-
bubbles: true,
|
|
96
|
-
cancelable: true,
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
button.dispatchEvent(clickEvent);
|
|
100
|
-
|
|
101
|
-
// Wait for event processing
|
|
102
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
103
|
-
|
|
104
|
-
expect(onClickSpy.calledOnce).to.be.true;
|
|
105
|
-
expect(onClickSpy.calledWith({ clickLocation: "inside" })).to.be.true;
|
|
106
|
-
expect(onInnerClickSpy.calledOnce).to.be.true;
|
|
107
|
-
expect(onOuterClickSpy.called).to.be.false;
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("should detect clicks on the target element itself", async () => {
|
|
111
|
-
// Simulate a click on the container itself
|
|
112
|
-
const clickEvent = new MouseEvent("click", {
|
|
113
|
-
bubbles: true,
|
|
114
|
-
cancelable: true,
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
container.dispatchEvent(clickEvent);
|
|
118
|
-
|
|
119
|
-
// Wait for event processing
|
|
120
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
121
|
-
|
|
122
|
-
expect(onClickSpy.calledOnce).to.be.true;
|
|
123
|
-
expect(onClickSpy.calledWith({ clickLocation: "inside" })).to.be.true;
|
|
124
|
-
expect(onInnerClickSpy.calledOnce).to.be.true;
|
|
125
|
-
expect(onOuterClickSpy.called).to.be.false;
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("should detect clicks outside the target element", async () => {
|
|
129
|
-
// Create an element outside the container
|
|
130
|
-
const outsideElement = document.createElement("div");
|
|
131
|
-
document.body.appendChild(outsideElement);
|
|
132
|
-
|
|
133
|
-
// Simulate a click on the outside element
|
|
134
|
-
const clickEvent = new MouseEvent("click", {
|
|
135
|
-
bubbles: true,
|
|
136
|
-
cancelable: true,
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
outsideElement.dispatchEvent(clickEvent);
|
|
140
|
-
|
|
141
|
-
// Wait for event processing
|
|
142
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
143
|
-
|
|
144
|
-
expect(onClickSpy.calledOnce).to.be.true;
|
|
145
|
-
expect(onClickSpy.calledWith({ clickLocation: "outside" })).to.be.true;
|
|
146
|
-
expect(onInnerClickSpy.called).to.be.false;
|
|
147
|
-
expect(onOuterClickSpy.calledOnce).to.be.true;
|
|
148
|
-
|
|
149
|
-
// Clean up
|
|
150
|
-
outsideElement.remove();
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it("should handle clicks when only onClick callback is provided", async () => {
|
|
154
|
-
clickTracker.disconnect();
|
|
155
|
-
clickTracker = new ClickTracker({
|
|
156
|
-
target: container,
|
|
157
|
-
onClick: onClickSpy,
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
const button = container.querySelector("#inner-button");
|
|
161
|
-
const clickEvent = new MouseEvent("click", {
|
|
162
|
-
bubbles: true,
|
|
163
|
-
cancelable: true,
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
button.dispatchEvent(clickEvent);
|
|
167
|
-
|
|
168
|
-
// Wait for event processing
|
|
169
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
170
|
-
|
|
171
|
-
expect(onClickSpy.calledOnce).to.be.true;
|
|
172
|
-
expect(onClickSpy.calledWith({ clickLocation: "inside" })).to.be.true;
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it("should handle clicks when only onInnerClick callback is provided", async () => {
|
|
176
|
-
clickTracker.disconnect();
|
|
177
|
-
clickTracker = new ClickTracker({
|
|
178
|
-
target: container,
|
|
179
|
-
onInnerClick: onInnerClickSpy,
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
const button = container.querySelector("#inner-button");
|
|
183
|
-
const clickEvent = new MouseEvent("click", {
|
|
184
|
-
bubbles: true,
|
|
185
|
-
cancelable: true,
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
button.dispatchEvent(clickEvent);
|
|
189
|
-
|
|
190
|
-
// Wait for event processing
|
|
191
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
192
|
-
|
|
193
|
-
expect(onInnerClickSpy.calledOnce).to.be.true;
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it("should handle clicks when only onOuterClick callback is provided", async () => {
|
|
197
|
-
clickTracker.disconnect();
|
|
198
|
-
clickTracker = new ClickTracker({
|
|
199
|
-
target: container,
|
|
200
|
-
onOuterClick: onOuterClickSpy,
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// Create an element outside the container
|
|
204
|
-
const outsideElement = document.createElement("div");
|
|
205
|
-
document.body.appendChild(outsideElement);
|
|
206
|
-
|
|
207
|
-
const clickEvent = new MouseEvent("click", {
|
|
208
|
-
bubbles: true,
|
|
209
|
-
cancelable: true,
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
outsideElement.dispatchEvent(clickEvent);
|
|
213
|
-
|
|
214
|
-
// Wait for event processing
|
|
215
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
216
|
-
|
|
217
|
-
expect(onOuterClickSpy.calledOnce).to.be.true;
|
|
218
|
-
|
|
219
|
-
// Clean up
|
|
220
|
-
outsideElement.remove();
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
describe("multiple instances", () => {
|
|
225
|
-
let container2;
|
|
226
|
-
let clickTracker2;
|
|
227
|
-
let onClickSpy2;
|
|
228
|
-
let onInnerClickSpy2;
|
|
229
|
-
let onOuterClickSpy2;
|
|
230
|
-
|
|
231
|
-
beforeEach(async () => {
|
|
232
|
-
// Create second container
|
|
233
|
-
container2 = await fixture(html`
|
|
234
|
-
<div id="test-container-2" style="width: 100px; height: 100px; background: blue;">
|
|
235
|
-
<span id="inner-span">Inner Span</span>
|
|
236
|
-
</div>
|
|
237
|
-
`);
|
|
238
|
-
|
|
239
|
-
// Create spies for second instance
|
|
240
|
-
onClickSpy2 = sinon.spy();
|
|
241
|
-
onInnerClickSpy2 = sinon.spy();
|
|
242
|
-
onOuterClickSpy2 = sinon.spy();
|
|
243
|
-
|
|
244
|
-
// Create first instance
|
|
245
|
-
clickTracker = new ClickTracker({
|
|
246
|
-
target: container,
|
|
247
|
-
onClick: onClickSpy,
|
|
248
|
-
onInnerClick: onInnerClickSpy,
|
|
249
|
-
onOuterClick: onOuterClickSpy,
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
// Create second instance
|
|
253
|
-
clickTracker2 = new ClickTracker({
|
|
254
|
-
target: container2,
|
|
255
|
-
onClick: onClickSpy2,
|
|
256
|
-
onInnerClick: onInnerClickSpy2,
|
|
257
|
-
onOuterClick: onOuterClickSpy2,
|
|
258
|
-
});
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
afterEach(() => {
|
|
262
|
-
if (clickTracker2) {
|
|
263
|
-
clickTracker2.disconnect();
|
|
264
|
-
clickTracker2 = null;
|
|
265
|
-
}
|
|
266
|
-
if (container2 && container2.parentNode) {
|
|
267
|
-
container2.parentNode.remove();
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it("should handle multiple instances correctly", async () => {
|
|
272
|
-
const button = container.querySelector("#inner-button");
|
|
273
|
-
|
|
274
|
-
// Click on first container
|
|
275
|
-
const clickEvent = new MouseEvent("click", {
|
|
276
|
-
bubbles: true,
|
|
277
|
-
cancelable: true,
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
button.dispatchEvent(clickEvent);
|
|
281
|
-
|
|
282
|
-
// Wait for event processing
|
|
283
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
284
|
-
|
|
285
|
-
// First instance should detect inside click
|
|
286
|
-
expect(onClickSpy.calledOnce).to.be.true;
|
|
287
|
-
expect(onClickSpy.calledWith({ clickLocation: "inside" })).to.be.true;
|
|
288
|
-
expect(onInnerClickSpy.calledOnce).to.be.true;
|
|
289
|
-
expect(onOuterClickSpy.called).to.be.false;
|
|
290
|
-
|
|
291
|
-
// Second instance should detect outside click
|
|
292
|
-
expect(onClickSpy2.calledOnce).to.be.true;
|
|
293
|
-
expect(onClickSpy2.calledWith({ clickLocation: "outside" })).to.be.true;
|
|
294
|
-
expect(onInnerClickSpy2.called).to.be.false;
|
|
295
|
-
expect(onOuterClickSpy2.calledOnce).to.be.true;
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
it("should properly clean up when one instance is disconnected", async () => {
|
|
299
|
-
// Disconnect first instance
|
|
300
|
-
clickTracker.disconnect();
|
|
301
|
-
clickTracker = null;
|
|
302
|
-
|
|
303
|
-
// Reset spies
|
|
304
|
-
onClickSpy.resetHistory();
|
|
305
|
-
onInnerClickSpy.resetHistory();
|
|
306
|
-
onOuterClickSpy.resetHistory();
|
|
307
|
-
onClickSpy2.resetHistory();
|
|
308
|
-
onInnerClickSpy2.resetHistory();
|
|
309
|
-
onOuterClickSpy2.resetHistory();
|
|
310
|
-
|
|
311
|
-
const span = container2.querySelector("#inner-span");
|
|
312
|
-
|
|
313
|
-
// Click on second container
|
|
314
|
-
const clickEvent = new MouseEvent("click", {
|
|
315
|
-
bubbles: true,
|
|
316
|
-
cancelable: true,
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
span.dispatchEvent(clickEvent);
|
|
320
|
-
|
|
321
|
-
// Wait for event processing
|
|
322
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
323
|
-
|
|
324
|
-
// First instance should not be called (disconnected)
|
|
325
|
-
expect(onClickSpy.called).to.be.false;
|
|
326
|
-
expect(onInnerClickSpy.called).to.be.false;
|
|
327
|
-
expect(onOuterClickSpy.called).to.be.false;
|
|
328
|
-
|
|
329
|
-
// Second instance should work normally
|
|
330
|
-
expect(onClickSpy2.calledOnce).to.be.true;
|
|
331
|
-
expect(onClickSpy2.calledWith({ clickLocation: "inside" })).to.be.true;
|
|
332
|
-
expect(onInnerClickSpy2.calledOnce).to.be.true;
|
|
333
|
-
expect(onOuterClickSpy2.called).to.be.false;
|
|
334
|
-
});
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
describe("disconnect method", () => {
|
|
338
|
-
it("should properly disconnect and clean up event listeners", () => {
|
|
339
|
-
clickTracker = new ClickTracker({
|
|
340
|
-
target: container,
|
|
341
|
-
onClick: onClickSpy,
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
// Verify instance is created
|
|
345
|
-
expect(clickTracker).to.be.instanceOf(ClickTracker);
|
|
346
|
-
|
|
347
|
-
// Disconnect should not throw
|
|
348
|
-
expect(() => clickTracker.disconnect()).to.not.throw();
|
|
349
|
-
|
|
350
|
-
// Create an outside element and click it
|
|
351
|
-
const outsideElement = document.createElement("div");
|
|
352
|
-
document.body.appendChild(outsideElement);
|
|
353
|
-
|
|
354
|
-
const clickEvent = new MouseEvent("click", {
|
|
355
|
-
bubbles: true,
|
|
356
|
-
cancelable: true,
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
outsideElement.dispatchEvent(clickEvent);
|
|
360
|
-
|
|
361
|
-
// Callback should not be called after disconnect
|
|
362
|
-
expect(onClickSpy.called).to.be.false;
|
|
363
|
-
|
|
364
|
-
// Clean up
|
|
365
|
-
outsideElement.remove();
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
it("should handle multiple disconnect calls gracefully", () => {
|
|
369
|
-
clickTracker = new ClickTracker({
|
|
370
|
-
target: container,
|
|
371
|
-
onClick: onClickSpy,
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
// Multiple disconnects should not throw
|
|
375
|
-
expect(() => {
|
|
376
|
-
clickTracker.disconnect();
|
|
377
|
-
clickTracker.disconnect();
|
|
378
|
-
clickTracker.disconnect();
|
|
379
|
-
}).to.not.throw();
|
|
380
|
-
});
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
describe("shadow DOM handling", () => {
|
|
384
|
-
let shadowHost;
|
|
385
|
-
let shadowRoot;
|
|
386
|
-
let shadowButton;
|
|
387
|
-
|
|
388
|
-
beforeEach(() => {
|
|
389
|
-
// Create shadow DOM for testing
|
|
390
|
-
shadowHost = document.createElement("div");
|
|
391
|
-
shadowRoot = shadowHost.attachShadow({ mode: "open" });
|
|
392
|
-
shadowButton = document.createElement("button");
|
|
393
|
-
shadowButton.textContent = "Shadow Button";
|
|
394
|
-
shadowRoot.appendChild(shadowButton);
|
|
395
|
-
container.appendChild(shadowHost);
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
it("should handle clicks inside shadow DOM correctly", async () => {
|
|
399
|
-
clickTracker = new ClickTracker({
|
|
400
|
-
target: container,
|
|
401
|
-
onClick: onClickSpy,
|
|
402
|
-
onInnerClick: onInnerClickSpy,
|
|
403
|
-
onOuterClick: onOuterClickSpy,
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
// Click on shadow button
|
|
407
|
-
const clickEvent = new MouseEvent("click", {
|
|
408
|
-
bubbles: true,
|
|
409
|
-
cancelable: true,
|
|
410
|
-
composed: true, // Important for shadow DOM
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
shadowButton.dispatchEvent(clickEvent);
|
|
414
|
-
|
|
415
|
-
// Wait for event processing
|
|
416
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
417
|
-
|
|
418
|
-
expect(onClickSpy.calledOnce).to.be.true;
|
|
419
|
-
expect(onClickSpy.calledWith({ clickLocation: "inside" })).to.be.true;
|
|
420
|
-
expect(onInnerClickSpy.calledOnce).to.be.true;
|
|
421
|
-
expect(onOuterClickSpy.called).to.be.false;
|
|
422
|
-
});
|
|
423
|
-
});
|
|
424
|
-
});
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
// Try this alternative approach
|
|
2
|
-
import { expect } from '@open-wc/testing';
|
|
3
|
-
import { html, render } from 'lit';
|
|
4
|
-
import sinon from 'sinon';
|
|
5
|
-
import { FocusTrap } from '../FocusTrap.mjs';
|
|
6
|
-
|
|
7
|
-
async function fixture(template) {
|
|
8
|
-
const wrapper = document.createElement('div');
|
|
9
|
-
render(template, wrapper);
|
|
10
|
-
document.body.appendChild(wrapper);
|
|
11
|
-
await new Promise(resolve => setTimeout(resolve, 0)); // Give browser time to render
|
|
12
|
-
return wrapper.firstElementChild;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
describe('FocusTrap', () => {
|
|
16
|
-
let container;
|
|
17
|
-
let focusTrap;
|
|
18
|
-
|
|
19
|
-
beforeEach(async () => {
|
|
20
|
-
// Create a container with focusable elements
|
|
21
|
-
container = await fixture(html`
|
|
22
|
-
<div>
|
|
23
|
-
<button id="first">First</button>
|
|
24
|
-
<input id="middle" type="text" />
|
|
25
|
-
<a href="#" id="last">Last</a>
|
|
26
|
-
</div>
|
|
27
|
-
`);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
afterEach(() => {
|
|
31
|
-
if (focusTrap) {
|
|
32
|
-
focusTrap.disconnect();
|
|
33
|
-
focusTrap = null;
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
describe('constructor', () => {
|
|
38
|
-
it('should create a new instance with a valid container', () => {
|
|
39
|
-
expect(() => new FocusTrap(container)).to.not.throw();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('should throw an error if container is not a valid HTMLElement', () => {
|
|
43
|
-
expect(() => new FocusTrap(null)).to.throw('FocusTrap requires a valid HTMLElement');
|
|
44
|
-
expect(() => new FocusTrap({})).to.throw('FocusTrap requires a valid HTMLElement');
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
describe('initialization', () => {
|
|
49
|
-
it('should set attributes on the container', () => {
|
|
50
|
-
focusTrap = new FocusTrap(container);
|
|
51
|
-
|
|
52
|
-
if ('inert' in HTMLElement.prototype) {
|
|
53
|
-
expect(container.inert).to.be.false;
|
|
54
|
-
expect(container.hasAttribute('data-focus-trap-container')).to.be.true;
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe('focus management', () => {
|
|
60
|
-
it('should focus the first element when focusFirstElement is called', () => {
|
|
61
|
-
focusTrap = new FocusTrap(container);
|
|
62
|
-
const firstButton = container.querySelector('#first');
|
|
63
|
-
|
|
64
|
-
focusTrap.focusFirstElement();
|
|
65
|
-
expect(document.activeElement).to.equal(firstButton);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('should focus the last element when focusLastElement is called', () => {
|
|
69
|
-
focusTrap = new FocusTrap(container);
|
|
70
|
-
const lastLink = container.querySelector('#last');
|
|
71
|
-
|
|
72
|
-
focusTrap.focusLastElement();
|
|
73
|
-
expect(document.activeElement).to.equal(lastLink);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
describe('keyboard navigation', () => {
|
|
78
|
-
it('should trap focus and cycle to the first element when tabbing from the last element', async () => {
|
|
79
|
-
focusTrap = new FocusTrap(container);
|
|
80
|
-
const lastLink = container.querySelector('#last');
|
|
81
|
-
const firstButton = container.querySelector('#first');
|
|
82
|
-
|
|
83
|
-
// Create a spy on the focus method of the first button
|
|
84
|
-
const firstButtonFocusSpy = sinon.spy(firstButton, 'focus');
|
|
85
|
-
|
|
86
|
-
lastLink.focus();
|
|
87
|
-
expect(document.activeElement).to.equal(lastLink);
|
|
88
|
-
|
|
89
|
-
// Simulate Tab key press
|
|
90
|
-
const tabEvent = new KeyboardEvent('keydown', {
|
|
91
|
-
key: 'Tab',
|
|
92
|
-
bubbles: true,
|
|
93
|
-
composed: true
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
lastLink.dispatchEvent(tabEvent);
|
|
97
|
-
|
|
98
|
-
// Check if focus method was called on the first element
|
|
99
|
-
expect(firstButtonFocusSpy.calledOnce).to.be.true;
|
|
100
|
-
firstButtonFocusSpy.restore(); // Clean up the spy
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('should trap focus and cycle to the last element when shift-tabbing from the first element', async () => {
|
|
104
|
-
focusTrap = new FocusTrap(container);
|
|
105
|
-
const firstButton = container.querySelector('#first');
|
|
106
|
-
const lastLink = container.querySelector('#last');
|
|
107
|
-
|
|
108
|
-
// Create a spy on the focus method of the last link
|
|
109
|
-
const lastLinkFocusSpy = sinon.spy(lastLink, 'focus');
|
|
110
|
-
|
|
111
|
-
firstButton.focus();
|
|
112
|
-
expect(document.activeElement).to.equal(firstButton);
|
|
113
|
-
|
|
114
|
-
// Simulate Shift+Tab key press
|
|
115
|
-
const shiftTabEvent = new KeyboardEvent('keydown', {
|
|
116
|
-
key: 'Tab',
|
|
117
|
-
shiftKey: true,
|
|
118
|
-
bubbles: true,
|
|
119
|
-
cancelable: true
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
firstButton.dispatchEvent(shiftTabEvent);
|
|
123
|
-
|
|
124
|
-
// Check if focus method was called on the last element
|
|
125
|
-
expect(lastLinkFocusSpy.calledOnce).to.be.true;
|
|
126
|
-
lastLinkFocusSpy.restore(); // Clean up the spy
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
describe('cleanup', () => {
|
|
131
|
-
it('should remove attributes when disconnected', () => {
|
|
132
|
-
focusTrap = new FocusTrap(container);
|
|
133
|
-
|
|
134
|
-
focusTrap.disconnect();
|
|
135
|
-
|
|
136
|
-
expect(container.hasAttribute('data-focus-trap-container')).to.be.false;
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
describe('shadow DOM support', () => {
|
|
141
|
-
it('should work with shadow DOM elements', async () => {
|
|
142
|
-
// Create a custom element with shadow DOM
|
|
143
|
-
const shadowHost = await fixture(html`
|
|
144
|
-
<div id="shadow-host"></div>
|
|
145
|
-
`);
|
|
146
|
-
|
|
147
|
-
// Create shadow root
|
|
148
|
-
const shadowRoot = shadowHost.attachShadow({ mode: 'open' });
|
|
149
|
-
|
|
150
|
-
// Add focusable elements to shadow DOM
|
|
151
|
-
shadowRoot.innerHTML = `
|
|
152
|
-
<button id="shadow-first">First</button>
|
|
153
|
-
<input id="shadow-middle" type="text" />
|
|
154
|
-
<a href="#" id="shadow-last">Last</a>
|
|
155
|
-
`;
|
|
156
|
-
|
|
157
|
-
focusTrap = new FocusTrap(shadowHost);
|
|
158
|
-
const firstButton = shadowRoot.querySelector('#shadow-first');
|
|
159
|
-
const lastLink = shadowRoot.querySelector('#shadow-last');
|
|
160
|
-
|
|
161
|
-
focusTrap.focusFirstElement();
|
|
162
|
-
expect(shadowRoot.activeElement).to.equal(firstButton);
|
|
163
|
-
|
|
164
|
-
focusTrap.focusLastElement();
|
|
165
|
-
expect(shadowRoot.activeElement).to.equal(lastLink);
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
});
|