@spectrum-web-components/picker 1.0.2 → 1.0.3

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/test/index.js ADDED
@@ -0,0 +1,1680 @@
1
+ "use strict";
2
+ import {
3
+ aTimeout,
4
+ elementUpdated,
5
+ expect,
6
+ fixture,
7
+ html,
8
+ nextFrame,
9
+ oneEvent,
10
+ waitUntil
11
+ } from "@open-wc/testing";
12
+ import "@spectrum-web-components/shared/src/focus-visible.js";
13
+ import { spy, stub } from "sinon";
14
+ import {
15
+ arrowDownEvent,
16
+ arrowRightEvent,
17
+ arrowUpEvent,
18
+ testForLitDevWarnings,
19
+ tEvent
20
+ } from "../../../test/testing-helpers.js";
21
+ import {
22
+ a11ySnapshot,
23
+ findAccessibilityNode,
24
+ sendKeys,
25
+ setViewport
26
+ } from "@web/test-runner-commands";
27
+ import {
28
+ Default,
29
+ disabled,
30
+ iconsOnly,
31
+ noVisibleLabel,
32
+ slottedLabel,
33
+ tooltip
34
+ } from "../stories/picker.stories.js";
35
+ import { M as pending } from "../stories/picker-pending.stories.js";
36
+ import { sendMouse } from "../../../test/plugins/browser.js";
37
+ import {
38
+ ignoreResizeObserverLoopError,
39
+ fixture as styledFixture
40
+ } from "../../../test/testing-helpers.js";
41
+ import "@spectrum-web-components/picker/sp-picker.js";
42
+ import "@spectrum-web-components/field-label/sp-field-label.js";
43
+ import "@spectrum-web-components/menu/sp-menu.js";
44
+ import "@spectrum-web-components/menu/sp-menu-group.js";
45
+ import "@spectrum-web-components/menu/sp-menu-item.js";
46
+ import "@spectrum-web-components/theme/src/themes.js";
47
+ import { isWebKit } from "@spectrum-web-components/shared";
48
+ import { SAFARI_FOCUS_RING_CLASS } from "@spectrum-web-components/picker/src/MobileController.js";
49
+ ignoreResizeObserverLoopError(before, after);
50
+ const isMenuActiveElement = function(el) {
51
+ var _a;
52
+ return ((_a = el.shadowRoot.activeElement) == null ? void 0 : _a.localName) === "sp-menu";
53
+ };
54
+ export function runPickerTests() {
55
+ let el;
56
+ const pickerFixture = async () => {
57
+ const test = await fixture(html`
58
+ <sp-theme scale="medium" color="light" system="spectrum">
59
+ <sp-field-label for="picker">Where do you live?</sp-field-label>
60
+ <sp-picker
61
+ id="picker"
62
+ style="width: 200px; --spectrum-alias-ui-icon-chevron-size-100: 10px;"
63
+ >
64
+ <sp-menu-item>Deselect</sp-menu-item>
65
+ <sp-menu-item value="option-2">Select Inverse</sp-menu-item>
66
+ <sp-menu-item>Feather...</sp-menu-item>
67
+ <sp-menu-item>Select and Mask...</sp-menu-item>
68
+ <sp-menu-item>Save Selection</sp-menu-item>
69
+ <sp-menu-item disabled>Make Work Path</sp-menu-item>
70
+ </sp-picker>
71
+ </sp-theme>
72
+ `);
73
+ return test.querySelector("sp-picker");
74
+ };
75
+ describe("accessibility model", () => {
76
+ it('accessible with "<sp-field-label>"', async function() {
77
+ const test = await fixture(html`
78
+ <div>
79
+ ${Default({
80
+ onChange: () => {
81
+ return;
82
+ }
83
+ })}
84
+ </div>
85
+ `);
86
+ const el2 = test.querySelector("sp-picker");
87
+ let snapshot = await a11ySnapshot({});
88
+ expect(
89
+ findAccessibilityNode(
90
+ snapshot,
91
+ (node) => node.name === "Select a Country with a very long label, too long, in fact Where do you live?"
92
+ ),
93
+ "`name` is the label text"
94
+ ).to.not.be.null;
95
+ el2.value = "option-2";
96
+ await elementUpdated(el2);
97
+ await nextFrame();
98
+ await nextFrame();
99
+ snapshot = await a11ySnapshot({});
100
+ expect(
101
+ findAccessibilityNode(
102
+ snapshot,
103
+ (node) => node.name === "Select Inverse Where do you live?"
104
+ ),
105
+ "`name` is the the selected item text plus the label text"
106
+ ).to.not.be.null;
107
+ });
108
+ it('accessible with "label" attribute', async () => {
109
+ const test = await fixture(html`
110
+ <div>
111
+ ${noVisibleLabel({
112
+ onChange: () => {
113
+ return;
114
+ }
115
+ })}
116
+ </div>
117
+ `);
118
+ const el2 = test.querySelector("sp-picker");
119
+ let snapshot = await a11ySnapshot({});
120
+ expect(
121
+ findAccessibilityNode(
122
+ snapshot,
123
+ (node) => node.name === "Where do you live?"
124
+ ),
125
+ "`name` is the label text"
126
+ ).to.not.be.null;
127
+ el2.value = "option-2";
128
+ await elementUpdated(el2);
129
+ await nextFrame();
130
+ await nextFrame();
131
+ snapshot = await a11ySnapshot({});
132
+ expect(
133
+ findAccessibilityNode(
134
+ snapshot,
135
+ (node) => node.name === "Select Inverse Where do you live?"
136
+ ),
137
+ "`name` is the the selected item text plus the label text"
138
+ ).to.not.be.null;
139
+ });
140
+ it('accessible with "label" slot', async function() {
141
+ const test = await fixture(html`
142
+ <div>
143
+ ${slottedLabel({
144
+ onChange: () => {
145
+ return;
146
+ }
147
+ })}
148
+ </div>
149
+ `);
150
+ const el2 = test.querySelector("sp-picker");
151
+ await elementUpdated(el2);
152
+ await nextFrame();
153
+ await nextFrame();
154
+ let snapshot = await a11ySnapshot({});
155
+ let name = "Where do you live?";
156
+ let node = findAccessibilityNode(
157
+ snapshot,
158
+ (node2) => node2.name === name
159
+ );
160
+ expect(
161
+ node,
162
+ `node not available: ${JSON.stringify(snapshot, null, " ")}`
163
+ ).to.not.be.null;
164
+ el2.value = "option-2";
165
+ await elementUpdated(el2);
166
+ await nextFrame();
167
+ await nextFrame();
168
+ snapshot = await a11ySnapshot({});
169
+ name = "Select Inverse Where do you live?";
170
+ node = findAccessibilityNode(
171
+ snapshot,
172
+ (node2) => node2.name === name
173
+ );
174
+ expect(
175
+ node,
176
+ `node not available: ${JSON.stringify(snapshot, null, " ")}`
177
+ ).to.not.be.null;
178
+ });
179
+ });
180
+ describe("standard", () => {
181
+ beforeEach(async () => {
182
+ el = await pickerFixture();
183
+ await elementUpdated(el);
184
+ await nextFrame();
185
+ await nextFrame();
186
+ });
187
+ it("loads accessibly", async () => {
188
+ await expect(el).to.be.accessible();
189
+ });
190
+ it("closes accessibly", async () => {
191
+ el.focus();
192
+ await elementUpdated(el);
193
+ expect(el.shadowRoot.activeElement).to.equal(el.button);
194
+ const opened = oneEvent(el, "sp-opened");
195
+ el.open = true;
196
+ await opened;
197
+ expect(el.open).to.be.true;
198
+ const accessibleCloseButton = el.shadowRoot.querySelector(
199
+ ".visually-hidden button"
200
+ );
201
+ expect(accessibleCloseButton).to.have.attribute(
202
+ "aria-label",
203
+ "Dismiss"
204
+ );
205
+ const closed = oneEvent(el, "sp-closed");
206
+ accessibleCloseButton.click();
207
+ await closed;
208
+ await elementUpdated(el);
209
+ expect(el.open).to.be.false;
210
+ expect(el.shadowRoot.activeElement).to.equal(el.button);
211
+ expect(document.activeElement).to.eq(el);
212
+ });
213
+ it("accepts new selected item content", async () => {
214
+ await nextFrame();
215
+ await nextFrame();
216
+ const option2 = el.querySelector('[value="option-2"');
217
+ el.value = "option-2";
218
+ await elementUpdated(option2);
219
+ await elementUpdated(el);
220
+ await aTimeout(150);
221
+ expect(el.value).to.equal("option-2");
222
+ expect((el.button.textContent || "").trim()).to.include(
223
+ "Select Inverse"
224
+ );
225
+ let itemUpdated = oneEvent(el, "sp-menu-item-added-or-updated");
226
+ const newLabel1 = "Invert Selection";
227
+ option2.innerHTML = newLabel1;
228
+ await itemUpdated;
229
+ await elementUpdated(el);
230
+ expect(el.value).to.equal("option-2");
231
+ expect((el.button.textContent || "").trim()).to.include(newLabel1);
232
+ itemUpdated = oneEvent(el, "sp-menu-item-added-or-updated");
233
+ const newLabel2 = "Other option";
234
+ option2.childNodes[0].textContent = newLabel2;
235
+ await itemUpdated;
236
+ await elementUpdated(el);
237
+ expect(el.value).to.equal("option-2");
238
+ expect((el.button.textContent || "").trim()).to.include(newLabel2);
239
+ });
240
+ it("accepts new selected item content when open", async () => {
241
+ await nextFrame();
242
+ const option2 = el.querySelector('[value="option-2"');
243
+ el.value = "option-2";
244
+ await elementUpdated(el);
245
+ await aTimeout(150);
246
+ expect(el.value).to.equal("option-2");
247
+ expect((el.button.textContent || "").trim()).to.include(
248
+ "Select Inverse"
249
+ );
250
+ const opened = oneEvent(el, "sp-opened");
251
+ el.open = true;
252
+ await opened;
253
+ const itemUpdated = oneEvent(
254
+ option2,
255
+ "sp-menu-item-added-or-updated"
256
+ );
257
+ option2.innerHTML = "Invert Selection";
258
+ await itemUpdated;
259
+ await elementUpdated(el);
260
+ await aTimeout(150);
261
+ expect(el.value).to.equal("option-2");
262
+ expect((el.button.textContent || "").trim()).to.include(
263
+ "Invert Selection"
264
+ );
265
+ });
266
+ it("unsets value when children removed", async () => {
267
+ await nextFrame();
268
+ el.value = "option-2";
269
+ await elementUpdated(el);
270
+ await aTimeout(150);
271
+ expect(el.value).to.equal("option-2");
272
+ expect((el.button.textContent || "").trim()).to.include(
273
+ "Select Inverse"
274
+ );
275
+ const items = el.querySelectorAll("sp-menu-item");
276
+ items.forEach((item) => {
277
+ item.remove();
278
+ });
279
+ await elementUpdated(el);
280
+ await nextFrame();
281
+ await aTimeout(150);
282
+ expect(
283
+ el.optionsMenu.childItems.length
284
+ ).to.equal(0);
285
+ if ("showPopover" in document.createElement("div")) {
286
+ return;
287
+ }
288
+ expect(el.value).to.equal("");
289
+ expect((el.button.textContent || "").trim()).to.not.include(
290
+ "Select Inverse"
291
+ );
292
+ });
293
+ it("accepts a new item and value at the same time", async () => {
294
+ el.value = "option-2";
295
+ await elementUpdated(el);
296
+ expect(el.value).to.equal("option-2");
297
+ const item = document.createElement("sp-menu-item");
298
+ item.value = "option-new";
299
+ item.textContent = "New Option";
300
+ el.append(item);
301
+ await elementUpdated(el);
302
+ el.value = "option-new";
303
+ await elementUpdated(el);
304
+ expect(el.value).to.equal("option-new");
305
+ });
306
+ it("accepts a new item that can be selected", async () => {
307
+ el.value = "option-2";
308
+ await elementUpdated(el);
309
+ expect(el.value).to.equal("option-2");
310
+ const item = document.createElement("sp-menu-item");
311
+ item.value = "option-new";
312
+ item.textContent = "New Option";
313
+ el.append(item);
314
+ await nextFrame();
315
+ await elementUpdated(el);
316
+ let opened = oneEvent(el, "sp-opened");
317
+ el.open = true;
318
+ await opened;
319
+ await nextFrame();
320
+ const close = oneEvent(el, "sp-closed");
321
+ item.click();
322
+ await close;
323
+ await nextFrame();
324
+ expect(el.value, "first time").to.equal("option-new");
325
+ opened = oneEvent(el, "sp-opened");
326
+ el.open = true;
327
+ await opened;
328
+ await nextFrame();
329
+ expect(el.value, "second time").to.equal("option-new");
330
+ });
331
+ it('manages its "name" value in the accessibility tree', async () => {
332
+ await nextFrame();
333
+ let snapshot = await a11ySnapshot({});
334
+ expect(
335
+ findAccessibilityNode(
336
+ snapshot,
337
+ (node) => node.name === "Where do you live?"
338
+ ),
339
+ "`name` is the label text"
340
+ ).to.not.be.null;
341
+ el.value = "option-2";
342
+ await elementUpdated(el);
343
+ await nextFrame();
344
+ await nextFrame();
345
+ snapshot = await a11ySnapshot({});
346
+ expect(
347
+ findAccessibilityNode(
348
+ snapshot,
349
+ (node) => node.name === "Select Inverse Where do you live?"
350
+ ),
351
+ "`name` is the selected item text plus the label text"
352
+ ).to.not.be.null;
353
+ });
354
+ it("manages `aria-activedescendant`", async () => {
355
+ const firstItem = el.querySelector("sp-menu-item:nth-child(1)");
356
+ const secondItem = el.querySelector("sp-menu-item:nth-child(2)");
357
+ const opened = oneEvent(el, "sp-opened");
358
+ el.open = true;
359
+ await opened;
360
+ expect(
361
+ el.optionsMenu.getAttribute(
362
+ "aria-activedescendant"
363
+ )
364
+ ).to.equal(firstItem == null ? void 0 : firstItem.id);
365
+ await sendKeys({ press: "ArrowDown" });
366
+ await elementUpdated(el);
367
+ expect(
368
+ el.optionsMenu.getAttribute(
369
+ "aria-activedescendant"
370
+ )
371
+ ).to.equal(secondItem == null ? void 0 : secondItem.id);
372
+ });
373
+ it("renders invalid accessibly", async () => {
374
+ el.invalid = true;
375
+ await elementUpdated(el);
376
+ expect(el.invalid).to.be.true;
377
+ await expect(el).to.be.accessible();
378
+ });
379
+ it("renders selection accessibly", async () => {
380
+ el.value = "option-2";
381
+ await elementUpdated(el);
382
+ await expect(el).to.be.accessible();
383
+ });
384
+ it("opens with visible focus on a menu item on `DownArrow`", async () => {
385
+ var _a, _b;
386
+ const firstItem = el.querySelector("sp-menu-item");
387
+ await elementUpdated(el);
388
+ expect(firstItem.focused, "should not visually focused").to.be.false;
389
+ el.focus();
390
+ await elementUpdated(el);
391
+ const opened = oneEvent(el, "sp-opened");
392
+ await sendKeys({ press: "ArrowRight" });
393
+ await sendKeys({ press: "ArrowLeft" });
394
+ await sendKeys({ press: "ArrowDown" });
395
+ await opened;
396
+ expect(el.open).to.be.true;
397
+ expect(firstItem.focused, "should be visually focused").to.be.true;
398
+ const closed = oneEvent(el, "sp-closed");
399
+ await sendKeys({
400
+ press: "Escape"
401
+ });
402
+ await closed;
403
+ expect(el.open).to.be.false;
404
+ expect(
405
+ document.activeElement === el,
406
+ `focused ${(_a = document.activeElement) == null ? void 0 : _a.localName} instead of back on Picker`
407
+ ).to.be.true;
408
+ expect(
409
+ el.shadowRoot.activeElement === el.button,
410
+ `focused ${(_b = el.shadowRoot.activeElement) == null ? void 0 : _b.localName} instead of back on button`
411
+ ).to.be.true;
412
+ await waitUntil(
413
+ () => !firstItem.focused,
414
+ "finally, not visually focused"
415
+ );
416
+ });
417
+ it("opens with visible focus on a menu item on `Space`", async function() {
418
+ var _a, _b;
419
+ const firstItem = el.querySelector("sp-menu-item");
420
+ await elementUpdated(el);
421
+ expect(firstItem.focused, "should not visually focused").to.be.false;
422
+ el.focus();
423
+ await elementUpdated(el);
424
+ const opened = oneEvent(el, "sp-opened");
425
+ await sendKeys({ press: "ArrowRight" });
426
+ await sendKeys({ press: "ArrowLeft" });
427
+ await sendKeys({ press: "Space" });
428
+ await opened;
429
+ expect(el.open).to.be.true;
430
+ expect(firstItem.focused, "should be visually focused").to.be.true;
431
+ const closed = oneEvent(el, "sp-closed");
432
+ await sendKeys({
433
+ press: "Escape"
434
+ });
435
+ await closed;
436
+ expect(el.open).to.be.false;
437
+ expect(
438
+ document.activeElement === el,
439
+ `focused ${(_a = document.activeElement) == null ? void 0 : _a.localName} instead of back on Picker`
440
+ ).to.be.true;
441
+ expect(
442
+ el.shadowRoot.activeElement === el.button,
443
+ `focused ${(_b = el.shadowRoot.activeElement) == null ? void 0 : _b.localName} instead of back on button`
444
+ ).to.be.true;
445
+ await waitUntil(
446
+ () => !firstItem.focused,
447
+ "finally, not visually focused"
448
+ );
449
+ });
450
+ it("opens, on click, without visible focus on a menu item", async () => {
451
+ await nextFrame();
452
+ await nextFrame();
453
+ const firstItem = el.querySelector("sp-menu-item");
454
+ const boundingRect = el.button.getBoundingClientRect();
455
+ expect(firstItem.focused, "not visually focused").to.be.false;
456
+ const opened = oneEvent(el, "sp-opened");
457
+ sendMouse({
458
+ steps: [
459
+ {
460
+ type: "click",
461
+ position: [
462
+ boundingRect.x + boundingRect.width / 2,
463
+ boundingRect.y + boundingRect.height / 2
464
+ ]
465
+ }
466
+ ]
467
+ });
468
+ await opened;
469
+ expect(el.open).to.be.true;
470
+ expect(firstItem.focused, "still not visually focused").to.be.false;
471
+ });
472
+ it("opens and selects in a single pointer button interaction", async () => {
473
+ await nextFrame();
474
+ await nextFrame();
475
+ const thirdItem = el.querySelector(
476
+ "sp-menu-item:nth-of-type(3)"
477
+ );
478
+ const boundingRect = el.button.getBoundingClientRect();
479
+ expect(el.value).to.not.equal(thirdItem.value);
480
+ const opened = oneEvent(el, "sp-opened");
481
+ await sendMouse({
482
+ steps: [
483
+ {
484
+ type: "move",
485
+ position: [
486
+ boundingRect.x + boundingRect.width / 2,
487
+ boundingRect.y + boundingRect.height / 2
488
+ ]
489
+ },
490
+ {
491
+ type: "down"
492
+ }
493
+ ]
494
+ });
495
+ await opened;
496
+ const thirdItemRect = thirdItem.getBoundingClientRect();
497
+ const closed = oneEvent(el, "sp-closed");
498
+ await sendMouse({
499
+ steps: [
500
+ {
501
+ type: "move",
502
+ position: [
503
+ thirdItemRect.x + thirdItemRect.width / 2,
504
+ thirdItemRect.y + thirdItemRect.height / 2
505
+ ]
506
+ },
507
+ {
508
+ type: "up"
509
+ }
510
+ ]
511
+ });
512
+ await closed;
513
+ expect(el.open).to.be.false;
514
+ expect(el.value).to.equal(thirdItem.value);
515
+ });
516
+ it("opens/closes multiple times", async () => {
517
+ await nextFrame();
518
+ await nextFrame();
519
+ expect(el.open).to.be.false;
520
+ const boundingRect = el.button.getBoundingClientRect();
521
+ let opened = oneEvent(el, "sp-opened");
522
+ sendMouse({
523
+ steps: [
524
+ {
525
+ type: "click",
526
+ position: [
527
+ boundingRect.x + boundingRect.width / 2,
528
+ boundingRect.y + boundingRect.height / 2
529
+ ]
530
+ }
531
+ ]
532
+ });
533
+ await opened;
534
+ expect(el.open).to.be.true;
535
+ let closed = oneEvent(el, "sp-closed");
536
+ sendMouse({
537
+ steps: [
538
+ {
539
+ type: "click",
540
+ position: [
541
+ boundingRect.x + boundingRect.width / 2,
542
+ boundingRect.y + boundingRect.height / 2
543
+ ]
544
+ }
545
+ ]
546
+ });
547
+ await closed;
548
+ expect(el.open).to.be.false;
549
+ opened = oneEvent(el, "sp-opened");
550
+ sendMouse({
551
+ steps: [
552
+ {
553
+ type: "click",
554
+ position: [
555
+ boundingRect.x + boundingRect.width / 2,
556
+ boundingRect.y + boundingRect.height / 2
557
+ ]
558
+ }
559
+ ]
560
+ });
561
+ await opened;
562
+ expect(el.open).to.be.true;
563
+ closed = oneEvent(el, "sp-closed");
564
+ sendMouse({
565
+ steps: [
566
+ {
567
+ type: "click",
568
+ position: [
569
+ boundingRect.x + boundingRect.width / 2,
570
+ boundingRect.y + boundingRect.height / 2
571
+ ]
572
+ }
573
+ ]
574
+ });
575
+ await closed;
576
+ expect(el.open).to.be.false;
577
+ });
578
+ it("closes when becoming disabled", async () => {
579
+ expect(el.open).to.be.false;
580
+ el.click();
581
+ await elementUpdated(el);
582
+ expect(el.open).to.be.true;
583
+ el.disabled = true;
584
+ await elementUpdated(el);
585
+ expect(el.open).to.be.false;
586
+ });
587
+ it("closes when clicking away", async () => {
588
+ el.id = "closing";
589
+ const other = document.createElement("div");
590
+ document.body.append(other);
591
+ await elementUpdated(el);
592
+ expect(el.open).to.be.false;
593
+ const opened = oneEvent(el, "sp-opened");
594
+ el.click();
595
+ await opened;
596
+ await elementUpdated(el);
597
+ expect(el.open).to.be.true;
598
+ const closed = oneEvent(el, "sp-closed");
599
+ other.click();
600
+ closed;
601
+ await elementUpdated(el);
602
+ other.remove();
603
+ });
604
+ it("selects", async () => {
605
+ var _a, _b;
606
+ const secondItem = el.querySelector(
607
+ "sp-menu-item:nth-of-type(2)"
608
+ );
609
+ const opened = oneEvent(el, "sp-opened");
610
+ el.click();
611
+ await opened;
612
+ expect(el.open).to.be.true;
613
+ expect((_a = el.selectedItem) == null ? void 0 : _a.itemText).to.be.undefined;
614
+ expect(el.value).to.equal("");
615
+ const closed = oneEvent(el, "sp-closed");
616
+ secondItem.click();
617
+ await closed;
618
+ expect(el.open).to.be.false;
619
+ expect((_b = el.selectedItem) == null ? void 0 : _b.itemText).to.equal("Select Inverse");
620
+ expect(el.value).to.equal("option-2");
621
+ });
622
+ it("re-selects", async () => {
623
+ var _a, _b, _c, _d;
624
+ const firstItem = el.querySelector(
625
+ "sp-menu-item:nth-of-type(1)"
626
+ );
627
+ const secondItem = el.querySelector(
628
+ "sp-menu-item:nth-of-type(2)"
629
+ );
630
+ let opened = oneEvent(el, "sp-opened");
631
+ el.click();
632
+ await opened;
633
+ expect(el.open).to.be.true;
634
+ expect((_a = el.selectedItem) == null ? void 0 : _a.itemText).to.be.undefined;
635
+ expect(el.value).to.equal("");
636
+ let closed = oneEvent(el, "sp-closed");
637
+ secondItem.click();
638
+ await closed;
639
+ expect(el.open).to.be.false;
640
+ expect((_b = el.selectedItem) == null ? void 0 : _b.itemText).to.equal("Select Inverse");
641
+ expect(el.value).to.equal("option-2");
642
+ opened = oneEvent(el, "sp-opened");
643
+ el.click();
644
+ await opened;
645
+ expect(el.open).to.be.true;
646
+ expect((_c = el.selectedItem) == null ? void 0 : _c.itemText).to.equal("Select Inverse");
647
+ expect(el.value).to.equal("option-2");
648
+ closed = oneEvent(el, "sp-closed");
649
+ firstItem.click();
650
+ await closed;
651
+ expect(el.open).to.be.false;
652
+ expect((_d = el.selectedItem) == null ? void 0 : _d.itemText).to.equal("Deselect");
653
+ expect(el.value).to.equal("Deselect");
654
+ });
655
+ it("dispatches bubbling and composed events", async () => {
656
+ const changeSpy = spy();
657
+ const parent = el.parentElement;
658
+ parent.shadowRoot.append(el);
659
+ const secondItem = el.querySelector(
660
+ "sp-menu-item:nth-of-type(2)"
661
+ );
662
+ parent.addEventListener("change", () => changeSpy());
663
+ expect(el.value).to.equal("");
664
+ const opened = oneEvent(el, "sp-opened");
665
+ el.open = true;
666
+ await opened;
667
+ const closed = oneEvent(el, "sp-closed");
668
+ secondItem.click();
669
+ await closed;
670
+ expect(el.value).to.equal(secondItem.value);
671
+ expect(changeSpy.calledOnce).to.be.true;
672
+ });
673
+ it("can have selection prevented", async () => {
674
+ var _a;
675
+ const preventChangeSpy = spy();
676
+ const secondItem = el.querySelector(
677
+ "sp-menu-item:nth-of-type(2)"
678
+ );
679
+ const opened = oneEvent(el, "sp-opened");
680
+ el.click();
681
+ await opened;
682
+ expect(el.open).to.be.true;
683
+ expect((_a = el.selectedItem) == null ? void 0 : _a.itemText).to.be.undefined;
684
+ expect(el.value).to.equal("");
685
+ expect(secondItem.selected).to.be.false;
686
+ el.addEventListener("change", (event) => {
687
+ event.preventDefault();
688
+ preventChangeSpy();
689
+ });
690
+ const changed = oneEvent(el, "change");
691
+ secondItem.click();
692
+ await changed;
693
+ expect(
694
+ preventChangeSpy.calledOnce,
695
+ preventChangeSpy.callCount.toString()
696
+ ).to.be.true;
697
+ expect(secondItem.selected, "selection prevented").to.be.false;
698
+ expect(el.open).to.be.true;
699
+ });
700
+ it("can throw focus after `change`", async () => {
701
+ var _a;
702
+ const input = document.createElement("input");
703
+ document.body.append(input);
704
+ await elementUpdated(el);
705
+ const secondItem = el.querySelector(
706
+ "sp-menu-item:nth-of-type(2)"
707
+ );
708
+ const opened = oneEvent(el, "sp-opened");
709
+ el.click();
710
+ await opened;
711
+ await elementUpdated(el);
712
+ expect(el.open).to.be.true;
713
+ expect((_a = el.selectedItem) == null ? void 0 : _a.itemText).to.be.undefined;
714
+ expect(el.value).to.equal("");
715
+ expect(secondItem.selected).to.be.false;
716
+ el.addEventListener("change", () => {
717
+ input.focus();
718
+ });
719
+ const closed = oneEvent(el, "sp-closed");
720
+ secondItem.click();
721
+ await closed;
722
+ await elementUpdated(el);
723
+ expect(el.open).to.be.false;
724
+ expect(el.value, "value changed").to.equal("option-2");
725
+ expect(secondItem.selected, "selected changed").to.be.true;
726
+ await waitUntil(
727
+ () => document.activeElement === input,
728
+ "focus throw"
729
+ );
730
+ input.remove();
731
+ });
732
+ it("opens on ArrowUp", async () => {
733
+ const button = el.button;
734
+ el.focus();
735
+ await elementUpdated(el);
736
+ expect(el.open, "inially closed").to.be.false;
737
+ button.dispatchEvent(tEvent());
738
+ await elementUpdated(el);
739
+ expect(el.open, "still closed").to.be.false;
740
+ const opened = oneEvent(el, "sp-opened");
741
+ button.dispatchEvent(arrowUpEvent());
742
+ await elementUpdated(el);
743
+ expect(el.open, "open by ArrowUp").to.be.true;
744
+ await opened;
745
+ const closed = oneEvent(el, "sp-closed");
746
+ sendKeys({
747
+ press: "Escape"
748
+ });
749
+ await closed;
750
+ expect(el.open).to.be.false;
751
+ });
752
+ it("opens on ArrowDown", async () => {
753
+ var _a, _b;
754
+ const firstItem = el.querySelector(
755
+ "sp-menu-item:nth-of-type(1)"
756
+ );
757
+ const button = el.button;
758
+ el.focus();
759
+ await elementUpdated(el);
760
+ expect(el.open, "inially closed").to.be.false;
761
+ const opened = oneEvent(el, "sp-opened");
762
+ button.dispatchEvent(arrowDownEvent());
763
+ await opened;
764
+ expect(el.open, "open by ArrowDown").to.be.true;
765
+ expect((_a = el.selectedItem) == null ? void 0 : _a.itemText).to.be.undefined;
766
+ expect(el.value).to.equal("");
767
+ const closed = oneEvent(el, "sp-closed");
768
+ firstItem.click();
769
+ await closed;
770
+ expect(el.open).to.be.false;
771
+ expect((_b = el.selectedItem) == null ? void 0 : _b.itemText).to.equal("Deselect");
772
+ expect(el.value).to.equal("Deselect");
773
+ });
774
+ it("quick selects on ArrowLeft/Right", async () => {
775
+ const selectionSpy = spy();
776
+ el.addEventListener("change", (event) => {
777
+ const { value } = event.target;
778
+ selectionSpy(value);
779
+ });
780
+ el.focus();
781
+ await elementUpdated(el);
782
+ await waitUntil(
783
+ () => el.menuItems.length === 6
784
+ );
785
+ await sendKeys({
786
+ press: "ArrowLeft"
787
+ });
788
+ await elementUpdated(el);
789
+ expect(selectionSpy.callCount).to.equal(1);
790
+ expect(selectionSpy.calledWith("Deselected"));
791
+ await sendKeys({
792
+ press: "ArrowLeft"
793
+ });
794
+ await elementUpdated(el);
795
+ expect(selectionSpy.callCount).to.equal(1);
796
+ await sendKeys({
797
+ press: "ArrowRight"
798
+ });
799
+ await nextFrame();
800
+ await nextFrame();
801
+ expect(selectionSpy.calledWith("option-2"));
802
+ await sendKeys({
803
+ press: "ArrowRight"
804
+ });
805
+ await nextFrame();
806
+ await nextFrame();
807
+ await sendKeys({
808
+ press: "ArrowRight"
809
+ });
810
+ await nextFrame();
811
+ await nextFrame();
812
+ await sendKeys({
813
+ press: "ArrowRight"
814
+ });
815
+ await nextFrame();
816
+ await nextFrame();
817
+ await sendKeys({
818
+ press: "ArrowRight"
819
+ });
820
+ await nextFrame();
821
+ await nextFrame();
822
+ expect(selectionSpy.calledWith("Save Selection"));
823
+ expect(selectionSpy.calledWith("Make Work Path")).to.be.false;
824
+ expect(selectionSpy.callCount).to.equal(5);
825
+ });
826
+ it("quick selects first item on ArrowRight when no value", async () => {
827
+ await nextFrame();
828
+ const selectionSpy = spy();
829
+ el.addEventListener("change", (event) => {
830
+ const { value } = event.target;
831
+ selectionSpy(value);
832
+ });
833
+ const button = el.button;
834
+ el.focus();
835
+ button.dispatchEvent(arrowRightEvent());
836
+ await elementUpdated(el);
837
+ expect(selectionSpy.callCount).to.equal(1);
838
+ expect(selectionSpy.calledWith("Deselected"));
839
+ });
840
+ it("loads", async () => {
841
+ expect(el).to.not.be.undefined;
842
+ });
843
+ it("closes when focusing away from the menu", async () => {
844
+ const firstItem = el.querySelector("sp-menu-item");
845
+ const thirdItem = el.querySelector(
846
+ "sp-menu-item:nth-of-type(3)"
847
+ );
848
+ const button = el.button;
849
+ const input = document.createElement("input");
850
+ el.insertAdjacentElement("afterend", input);
851
+ el.focus();
852
+ await sendKeys({ press: "Tab" });
853
+ expect(document.activeElement === input).to.be.true;
854
+ await sendKeys({ press: "Shift+Tab" });
855
+ expect(document.activeElement === el).to.be.true;
856
+ const opened = oneEvent(el, "sp-opened");
857
+ sendKeys({ press: "Enter" });
858
+ await opened;
859
+ await elementUpdated(el);
860
+ await waitUntil(
861
+ () => firstItem.focused,
862
+ "The first items should have become focused visually."
863
+ );
864
+ await sendKeys({ press: "ArrowDown" });
865
+ await sendKeys({ press: "ArrowDown" });
866
+ expect(thirdItem.focused).to.be.true;
867
+ const closed = oneEvent(el, "sp-closed");
868
+ button.focus();
869
+ await closed;
870
+ expect(isMenuActiveElement(el)).to.be.false;
871
+ expect(el.open).to.be.false;
872
+ });
873
+ it("does not listen to streaming `Enter` keydown", async () => {
874
+ const openSpy = spy();
875
+ const closedSpy = spy();
876
+ el.addEventListener("sp-opened", () => openSpy());
877
+ el.addEventListener("sp-closed", () => closedSpy());
878
+ const firstItem = el.querySelector("sp-menu-item");
879
+ const thirdItem = el.querySelector(
880
+ "sp-menu-item:nth-of-type(3)"
881
+ );
882
+ const input = document.createElement("input");
883
+ el.insertAdjacentElement("afterend", input);
884
+ el.focus();
885
+ await sendKeys({ press: "Tab" });
886
+ expect(document.activeElement === input).to.be.true;
887
+ await sendKeys({ press: "Shift+Tab" });
888
+ expect(document.activeElement === el).to.be.true;
889
+ const opened = oneEvent(el, "sp-opened");
890
+ sendKeys({ down: "Enter" });
891
+ await opened;
892
+ await aTimeout(300);
893
+ expect(openSpy.callCount).to.equal(1);
894
+ await sendKeys({ up: "Enter" });
895
+ await waitUntil(
896
+ () => firstItem.focused,
897
+ "The first items should have become focused visually."
898
+ );
899
+ await sendKeys({ press: "ArrowDown" });
900
+ await sendKeys({ press: "ArrowDown" });
901
+ expect(thirdItem.focused).to.be.true;
902
+ const closed = oneEvent(el, "sp-closed");
903
+ sendKeys({ down: "Enter" });
904
+ await closed;
905
+ await aTimeout(300);
906
+ expect(el.value).to.equal(thirdItem.value);
907
+ expect(openSpy.callCount).to.equal(1);
908
+ expect(closedSpy.callCount).to.equal(1);
909
+ await sendKeys({ up: "Enter" });
910
+ });
911
+ it("allows tabing to close", async () => {
912
+ const input = document.createElement("input");
913
+ el.insertAdjacentElement("afterend", input);
914
+ const opened = oneEvent(el, "sp-opened");
915
+ el.open = true;
916
+ await opened;
917
+ await nextFrame();
918
+ expect(el.open).to.be.true;
919
+ el.focus();
920
+ const closed = oneEvent(el, "sp-closed");
921
+ sendKeys({ press: "Tab" });
922
+ await closed;
923
+ expect(el.open, "closes").to.be.false;
924
+ });
925
+ describe("tab order", () => {
926
+ let input1;
927
+ let input2;
928
+ beforeEach(() => {
929
+ const surroundingInput = () => {
930
+ const input = document.createElement("input");
931
+ input.type = "text";
932
+ input.tabIndex = 0;
933
+ return input;
934
+ };
935
+ input1 = surroundingInput();
936
+ input2 = surroundingInput();
937
+ el.insertAdjacentElement("beforebegin", input1);
938
+ el.insertAdjacentElement("afterend", input2);
939
+ });
940
+ afterEach(() => {
941
+ input1.remove();
942
+ input2.remove();
943
+ });
944
+ it("tabs forward through the element", async () => {
945
+ input1.focus();
946
+ await nextFrame();
947
+ expect(document.activeElement === input1, "focuses input 1").to.true;
948
+ let focused = oneEvent(el, "focus");
949
+ await sendKeys({ press: "Tab" });
950
+ await focused;
951
+ expect(el.focused, "focused").to.be.true;
952
+ expect(el.open, "closed").to.be.false;
953
+ expect(document.activeElement === el, "focuses el").to.be.true;
954
+ focused = oneEvent(input2, "focus");
955
+ await sendKeys({ press: "Tab" });
956
+ await focused;
957
+ expect(document.activeElement === input2, "focuses input 2").to.true;
958
+ });
959
+ it("shift+tabs backwards through the element", async () => {
960
+ input2.focus();
961
+ await nextFrame();
962
+ expect(document.activeElement, "focuses input 2").to.equal(
963
+ input2
964
+ );
965
+ let focused = oneEvent(el, "focus");
966
+ await sendKeys({ press: "Shift+Tab" });
967
+ await focused;
968
+ expect(el.focused, "focused").to.be.true;
969
+ expect(el.open, "closed").to.be.false;
970
+ expect(document.activeElement, "focuses el").to.equal(el);
971
+ focused = oneEvent(input1, "focus");
972
+ await sendKeys({ press: "Shift+Tab" });
973
+ await focused;
974
+ expect(document.activeElement, "focuses input 1").to.equal(
975
+ input1
976
+ );
977
+ });
978
+ it("can close and immediately tab to the next tab stop", async () => {
979
+ el.focus();
980
+ await nextFrame();
981
+ expect(document.activeElement, "focuses el").to.equal(el);
982
+ const opened = oneEvent(el, "sp-opened");
983
+ await sendKeys({ press: "ArrowUp" });
984
+ await opened;
985
+ expect(el.open, "opened").to.be.true;
986
+ await waitUntil(
987
+ () => isMenuActiveElement(el),
988
+ "first item focused"
989
+ );
990
+ const closed = oneEvent(el, "sp-closed");
991
+ el.open = false;
992
+ await closed;
993
+ expect(el.open).to.be.false;
994
+ expect(document.activeElement === el).to.be.true;
995
+ const focused = oneEvent(input2, "focus");
996
+ await sendKeys({ press: "Tab" });
997
+ await focused;
998
+ expect(el.open).to.be.false;
999
+ expect(document.activeElement === input2).to.be.true;
1000
+ });
1001
+ it("can close and immediate shift+tab to the previous tab stop", async () => {
1002
+ el.focus();
1003
+ await nextFrame();
1004
+ expect(document.activeElement === el, "focuses el").to.be.true;
1005
+ const opened = oneEvent(el, "sp-opened");
1006
+ await sendKeys({ press: "ArrowUp" });
1007
+ await opened;
1008
+ expect(el.open, "opened").to.be.true;
1009
+ await waitUntil(
1010
+ () => isMenuActiveElement(el),
1011
+ "first item focused"
1012
+ );
1013
+ const closed = oneEvent(el, "sp-closed");
1014
+ el.open = false;
1015
+ await closed;
1016
+ expect(el.open).to.be.false;
1017
+ expect(document.activeElement === el).to.be.true;
1018
+ const focused = oneEvent(input1, "focus");
1019
+ sendKeys({ press: "Shift+Tab" });
1020
+ await focused;
1021
+ expect(el.open).to.be.false;
1022
+ expect(document.activeElement === input1).to.be.true;
1023
+ });
1024
+ });
1025
+ it("does not open when [readonly]", async () => {
1026
+ el.readonly = true;
1027
+ await elementUpdated(el);
1028
+ el.click();
1029
+ await elementUpdated(el);
1030
+ expect(el.open).to.be.false;
1031
+ });
1032
+ it("scrolls selected into view on open", async () => {
1033
+ const styles = document.createElement("style");
1034
+ styles.innerText = "sp-popover { height: 40px; }";
1035
+ el.shadowRoot.append(styles);
1036
+ const firstItem = el.querySelector(
1037
+ "sp-menu-item:first-child"
1038
+ );
1039
+ const lastItem = el.querySelector(
1040
+ "sp-menu-item:last-child"
1041
+ );
1042
+ lastItem.disabled = false;
1043
+ el.value = lastItem.value;
1044
+ await elementUpdated(el);
1045
+ const opened = oneEvent(el, "sp-opened");
1046
+ el.open = true;
1047
+ await opened;
1048
+ await waitUntil(
1049
+ () => isMenuActiveElement(el),
1050
+ "first item focused"
1051
+ );
1052
+ const getParentOffset = (el2) => {
1053
+ const parentScroll = el2.assignedSlot.parentElement.scrollTop;
1054
+ const parentOffset = el2.offsetTop - parentScroll;
1055
+ return parentOffset;
1056
+ };
1057
+ expect(getParentOffset(lastItem)).to.be.lessThan(40);
1058
+ expect(getParentOffset(firstItem)).to.be.lessThan(-1);
1059
+ lastItem.dispatchEvent(
1060
+ new FocusEvent("focusin", { bubbles: true })
1061
+ );
1062
+ await sendKeys({
1063
+ press: "ArrowDown"
1064
+ });
1065
+ await elementUpdated(el);
1066
+ await nextFrame();
1067
+ expect(getParentOffset(lastItem)).to.be.greaterThan(40);
1068
+ expect(getParentOffset(firstItem)).to.be.greaterThan(-1);
1069
+ });
1070
+ it("manages focus-ring styles", async () => {
1071
+ if (!isWebKit()) {
1072
+ return;
1073
+ }
1074
+ el.isMobile.matches = true;
1075
+ el.bindEvents();
1076
+ await setViewport({ width: 360, height: 640 });
1077
+ await nextFrame();
1078
+ let opened = oneEvent(el, "sp-opened");
1079
+ const boundingRect = el.button.getBoundingClientRect();
1080
+ sendMouse({
1081
+ steps: [
1082
+ {
1083
+ type: "click",
1084
+ position: [
1085
+ boundingRect.x + boundingRect.width / 2,
1086
+ boundingRect.y + boundingRect.height / 2
1087
+ ]
1088
+ }
1089
+ ]
1090
+ });
1091
+ await opened;
1092
+ const tray = el.shadowRoot.querySelector("sp-tray");
1093
+ expect(tray).to.not.be.null;
1094
+ let closed = oneEvent(el, "sp-closed");
1095
+ const firstItem = el.querySelector("sp-menu-item");
1096
+ firstItem.click();
1097
+ await elementUpdated(el);
1098
+ await closed;
1099
+ expect(el.open).to.be.false;
1100
+ const button = el.shadowRoot.querySelector(
1101
+ "#button"
1102
+ );
1103
+ expect(button).to.not.be.null;
1104
+ expect(button.classList.contains(SAFARI_FOCUS_RING_CLASS)).to.be.true;
1105
+ expect(document.activeElement === el).to.be.true;
1106
+ await sendMouse({
1107
+ steps: [
1108
+ {
1109
+ type: "click",
1110
+ position: [0, 0]
1111
+ }
1112
+ ]
1113
+ });
1114
+ expect(document.activeElement === el).to.be.false;
1115
+ opened = oneEvent(el, "sp-opened");
1116
+ await sendKeys({
1117
+ press: "Tab"
1118
+ });
1119
+ await sendKeys({
1120
+ press: "Enter"
1121
+ });
1122
+ await elementUpdated(el);
1123
+ await opened;
1124
+ closed = oneEvent(el, "sp-closed");
1125
+ firstItem.click();
1126
+ await elementUpdated(el);
1127
+ await closed;
1128
+ expect(el.open).to.be.false;
1129
+ expect(button.classList.contains(SAFARI_FOCUS_RING_CLASS)).to.be.false;
1130
+ });
1131
+ });
1132
+ describe("grouped", async () => {
1133
+ const groupedFixture = async () => {
1134
+ return fixture(html`
1135
+ <sp-picker
1136
+ quiet
1137
+ label="I would like to use Spectrum Web Components"
1138
+ value="0"
1139
+ >
1140
+ <sp-menu-group>
1141
+ <span slot="header">Timeline</span>
1142
+ <sp-menu-item value="0" id="should-be-selected">
1143
+ Immediately
1144
+ </sp-menu-item>
1145
+ <sp-menu-item value="1">
1146
+ I'm already using them
1147
+ </sp-menu-item>
1148
+ <sp-menu-item value="2">Soon</sp-menu-item>
1149
+ <sp-menu-item value="3">
1150
+ As part of my next project
1151
+ </sp-menu-item>
1152
+ <sp-menu-item value="4">In the future</sp-menu-item>
1153
+ </sp-menu-group>
1154
+ </sp-picker>
1155
+ `);
1156
+ };
1157
+ beforeEach(async () => {
1158
+ el = await groupedFixture();
1159
+ await elementUpdated(el);
1160
+ await nextFrame();
1161
+ await nextFrame();
1162
+ });
1163
+ it("selects the item with a matching value in a group", async () => {
1164
+ const item = el.querySelector("#should-be-selected");
1165
+ expect(item.selected).to.be.true;
1166
+ });
1167
+ });
1168
+ describe("slotted label", () => {
1169
+ const pickerFixture2 = async () => {
1170
+ const test = await fixture(html`
1171
+ <div>
1172
+ <sp-field-label for="picker-slotted">
1173
+ Where do you live?
1174
+ </sp-field-label>
1175
+ <sp-picker id="picker-slotted">
1176
+ <span slot="label">
1177
+ Select a Country with a very long label, too long in
1178
+ fact
1179
+ </span>
1180
+ <sp-menu-item>Deselect</sp-menu-item>
1181
+ <sp-menu-item value="option-2">
1182
+ Select Inverse
1183
+ </sp-menu-item>
1184
+ <sp-menu-item>Feather...</sp-menu-item>
1185
+ <sp-menu-item>Select and Mask...</sp-menu-item>
1186
+ <sp-menu-item>Save Selection</sp-menu-item>
1187
+ <sp-menu-item disabled>Make Work Path</sp-menu-item>
1188
+ </sp-picker>
1189
+ </div>
1190
+ `);
1191
+ return test.querySelector("sp-picker");
1192
+ };
1193
+ beforeEach(async () => {
1194
+ el = await pickerFixture2();
1195
+ await elementUpdated(el);
1196
+ await nextFrame();
1197
+ });
1198
+ afterEach(async () => {
1199
+ if (el.open) {
1200
+ const closed = oneEvent(el, "sp-closed");
1201
+ el.open = false;
1202
+ await closed;
1203
+ }
1204
+ });
1205
+ it("loads accessibly w/ slotted label", async () => {
1206
+ await expect(el).to.be.accessible();
1207
+ });
1208
+ });
1209
+ describe("Dev mode", () => {
1210
+ let consoleWarnStub;
1211
+ before(() => {
1212
+ window.__swc.verbose = true;
1213
+ consoleWarnStub = stub(console, "warn");
1214
+ });
1215
+ afterEach(() => {
1216
+ consoleWarnStub.resetHistory();
1217
+ });
1218
+ after(async () => {
1219
+ window.__swc.verbose = false;
1220
+ consoleWarnStub.restore();
1221
+ if (el.open) {
1222
+ const closed = oneEvent(el, "sp-closed");
1223
+ el.open = false;
1224
+ await closed;
1225
+ }
1226
+ });
1227
+ const pickerFixture2 = async () => {
1228
+ const test = await fixture(html`
1229
+ <div>
1230
+ <sp-field-label for="picker-deprecated">
1231
+ Where do you live?
1232
+ </sp-field-label>
1233
+ <sp-picker
1234
+ id="picker-deprecated"
1235
+ label="Select a Country with a very long label, too long in fact"
1236
+ >
1237
+ <sp-menu>
1238
+ <sp-menu-item>Deselect</sp-menu-item>
1239
+ <sp-menu-item value="option-2">
1240
+ Select Inverse
1241
+ </sp-menu-item>
1242
+ <sp-menu-item>Feather...</sp-menu-item>
1243
+ <sp-menu-item>Select and Mask...</sp-menu-item>
1244
+ <sp-menu-item>Save Selection</sp-menu-item>
1245
+ <sp-menu-item disabled>Make Work Path</sp-menu-item>
1246
+ </sp-menu>
1247
+ </sp-picker>
1248
+ </div>
1249
+ `);
1250
+ return test.querySelector("sp-picker");
1251
+ };
1252
+ it("does not warn in Dev Mode when accessible elements leveraged", async () => {
1253
+ const test = await fixture(html`
1254
+ <div>
1255
+ <sp-field-label for="test">Test label</sp-field-label>
1256
+ <sp-picker id="test">
1257
+ <sp-menu-item>Feather...</sp-menu-item>
1258
+ <sp-menu-item>Select and Mask...</sp-menu-item>
1259
+ <sp-menu-item>Save Selection</sp-menu-item>
1260
+ </sp-picker>
1261
+ </div>
1262
+ `);
1263
+ el = test.querySelector("sp-picker");
1264
+ await elementUpdated(el);
1265
+ await nextFrame();
1266
+ await nextFrame();
1267
+ expect(consoleWarnStub.called).to.be.false;
1268
+ });
1269
+ it("warns in Dev Mode when accessible attributes are not leveraged", async function() {
1270
+ this.retries(0);
1271
+ el = await fixture(html`
1272
+ <sp-picker>
1273
+ <sp-menu-item>Feather...</sp-menu-item>
1274
+ <sp-menu-item>Select and Mask...</sp-menu-item>
1275
+ <sp-menu-item>Save Selection</sp-menu-item>
1276
+ </sp-picker>
1277
+ `);
1278
+ await elementUpdated(el);
1279
+ await nextFrame();
1280
+ await nextFrame();
1281
+ expect(consoleWarnStub.called).to.be.true;
1282
+ const spyCall = consoleWarnStub.getCall(0);
1283
+ expect(
1284
+ spyCall.args.at(0).includes("accessible"),
1285
+ "confirm accessibility-centric message"
1286
+ ).to.be.true;
1287
+ expect(spyCall.args.at(-1), "confirm `data` shape").to.deep.equal({
1288
+ data: {
1289
+ localName: "sp-picker",
1290
+ type: "accessibility",
1291
+ level: "default"
1292
+ }
1293
+ });
1294
+ });
1295
+ describe("deprecated", () => {
1296
+ it("warns in Dev Mode of deprecated `<sp-menu>` usage", async () => {
1297
+ el = await pickerFixture2();
1298
+ await elementUpdated(el);
1299
+ expect(consoleWarnStub.called).to.be.true;
1300
+ const spyCall = consoleWarnStub.getCall(0);
1301
+ expect(
1302
+ spyCall.args.at(0).includes("<sp-menu>"),
1303
+ "confirm <sp-menu>-centric message"
1304
+ ).to.be.true;
1305
+ expect(
1306
+ spyCall.args.at(-1),
1307
+ "confirm `data` shape"
1308
+ ).to.deep.equal({
1309
+ data: {
1310
+ localName: "sp-picker",
1311
+ type: "api",
1312
+ level: "deprecation"
1313
+ }
1314
+ });
1315
+ });
1316
+ });
1317
+ describe("Dev mode ignored", () => {
1318
+ const { ignoreWarningLocalNames } = window.__swc;
1319
+ before(() => {
1320
+ window.__swc.ignoreWarningLocalNames = {
1321
+ "sp-picker": true
1322
+ };
1323
+ });
1324
+ before(() => {
1325
+ window.__swc.ignoreWarningLocalNames = ignoreWarningLocalNames;
1326
+ });
1327
+ beforeEach(async () => {
1328
+ el = await pickerFixture2();
1329
+ await elementUpdated(el);
1330
+ await nextFrame();
1331
+ });
1332
+ afterEach(async () => {
1333
+ if (el.open) {
1334
+ const closed = oneEvent(el, "sp-closed");
1335
+ el.open = false;
1336
+ await closed;
1337
+ }
1338
+ });
1339
+ it("selects with deprecated syntax", async () => {
1340
+ var _a, _b;
1341
+ const secondItem = el.querySelector(
1342
+ "sp-menu-item:nth-of-type(2)"
1343
+ );
1344
+ const opened = oneEvent(el, "sp-opened");
1345
+ el.click();
1346
+ await opened;
1347
+ expect(el.open).to.be.true;
1348
+ expect((_a = el.selectedItem) == null ? void 0 : _a.itemText).to.be.undefined;
1349
+ expect(el.value).to.equal("");
1350
+ const closed = oneEvent(el, "sp-closed");
1351
+ secondItem.click();
1352
+ await closed;
1353
+ expect(el.open).to.be.false;
1354
+ expect((_b = el.selectedItem) == null ? void 0 : _b.itemText).to.equal("Select Inverse");
1355
+ expect(el.value).to.equal("option-2");
1356
+ });
1357
+ });
1358
+ });
1359
+ testForLitDevWarnings(async () => await pickerFixture());
1360
+ it('manages its "name" value in the accessibility tree when [icons-only]', async () => {
1361
+ const test = await fixture(html`
1362
+ <div>${iconsOnly({})}</div>
1363
+ `);
1364
+ const el2 = test.querySelector("sp-picker");
1365
+ await elementUpdated(el2);
1366
+ await nextFrame();
1367
+ let snapshot = await a11ySnapshot({});
1368
+ expect(
1369
+ findAccessibilityNode(
1370
+ snapshot,
1371
+ (node) => node.name === "Delete Choose an action type..."
1372
+ ),
1373
+ "`name` is the label text"
1374
+ ).to.not.be.null;
1375
+ el2.value = "2";
1376
+ await elementUpdated(el2);
1377
+ await nextFrame();
1378
+ await nextFrame();
1379
+ expect(el2.value).to.equal("2");
1380
+ snapshot = await a11ySnapshot({});
1381
+ expect(
1382
+ findAccessibilityNode(
1383
+ snapshot,
1384
+ (node) => node.name === "Copy Choose an action type..."
1385
+ ),
1386
+ "`name` is the label text plus the selected item text"
1387
+ ).to.not.be.null;
1388
+ });
1389
+ it("toggles between pickers", async () => {
1390
+ const el2 = await pickerFixture();
1391
+ const el1 = await pickerFixture();
1392
+ el1.parentElement.style.float = "left";
1393
+ el2.parentElement.style.float = "left";
1394
+ el1.id = "away";
1395
+ el2.id = "other";
1396
+ await Promise.all([elementUpdated(el1), elementUpdated(el2)]);
1397
+ expect(el1.open, "closed 1").to.be.false;
1398
+ expect(el2.open, "closed 1").to.be.false;
1399
+ let open = oneEvent(el1, "sp-opened");
1400
+ el1.click();
1401
+ await open;
1402
+ expect(el1.open).to.be.true;
1403
+ expect(el2.open).to.be.false;
1404
+ open = oneEvent(el2, "sp-opened");
1405
+ let closed = oneEvent(el1, "sp-closed");
1406
+ el2.click();
1407
+ await Promise.all([open, closed]);
1408
+ expect(el1.open).to.be.false;
1409
+ expect(el2.open).to.be.true;
1410
+ open = oneEvent(el1, "sp-opened");
1411
+ closed = oneEvent(el2, "sp-closed");
1412
+ el1.click();
1413
+ await Promise.all([open, closed]);
1414
+ expect(el2.open).to.be.false;
1415
+ expect(el1.open).to.be.true;
1416
+ closed = oneEvent(el1, "sp-closed");
1417
+ sendKeys({
1418
+ press: "Escape"
1419
+ });
1420
+ await closed;
1421
+ expect(el1.open).to.be.false;
1422
+ });
1423
+ it("displays selected item text by default", async () => {
1424
+ var _a, _b, _c, _d, _e;
1425
+ const el2 = await fixture(html`
1426
+ <sp-picker
1427
+ value="inverse"
1428
+ label="Select a Country with a very long label, too long in fact"
1429
+ >
1430
+ <sp-menu-item value="deselect">Deselect Text</sp-menu-item>
1431
+ <sp-menu-item value="inverse">Select Inverse</sp-menu-item>
1432
+ <sp-menu-item>Feather...</sp-menu-item>
1433
+ <sp-menu-item>Select and Mask...</sp-menu-item>
1434
+ <sp-menu-item>Save Selection</sp-menu-item>
1435
+ <sp-menu-item disabled>Make Work Path</sp-menu-item>
1436
+ </sp-picker>
1437
+ `);
1438
+ await nextFrame();
1439
+ await elementUpdated(el2);
1440
+ await waitUntil(
1441
+ () => {
1442
+ var _a2;
1443
+ return ((_a2 = el2.selectedItem) == null ? void 0 : _a2.itemText) === "Select Inverse";
1444
+ },
1445
+ `Selected Item Text: ${(_a = el2.selectedItem) == null ? void 0 : _a.itemText}`
1446
+ );
1447
+ const firstItem = el2.querySelector(
1448
+ "sp-menu-item:nth-of-type(1)"
1449
+ );
1450
+ const secondItem = el2.querySelector(
1451
+ "sp-menu-item:nth-of-type(2)"
1452
+ );
1453
+ expect(el2.value).to.equal("inverse");
1454
+ expect((_b = el2.selectedItem) == null ? void 0 : _b.itemText).to.equal("Select Inverse");
1455
+ el2.focus();
1456
+ await elementUpdated(el2);
1457
+ expect(
1458
+ el2 === document.activeElement,
1459
+ `activeElement is ${(_c = document.activeElement) == null ? void 0 : _c.localName}`
1460
+ ).to.be.true;
1461
+ const opened = oneEvent(el2, "sp-opened");
1462
+ sendKeys({ press: "Enter" });
1463
+ await opened;
1464
+ expect(
1465
+ el2 === document.activeElement,
1466
+ `activeElement is ${(_d = document.activeElement) == null ? void 0 : _d.localName}`
1467
+ ).to.be.true;
1468
+ expect(
1469
+ el2.optionsMenu === el2.shadowRoot.activeElement,
1470
+ `activeElement is ${(_e = el2.shadowRoot.activeElement) == null ? void 0 : _e.localName}`
1471
+ ).to.be.true;
1472
+ expect(firstItem.focused, 'firstItem NOT "focused"').to.be.false;
1473
+ expect(secondItem.focused, 'secondItem "focused"').to.be.true;
1474
+ expect(
1475
+ el2.optionsMenu.getAttribute(
1476
+ "aria-activedescendant"
1477
+ )
1478
+ ).to.equal(secondItem.id);
1479
+ });
1480
+ it("resets value when item not available", async () => {
1481
+ var _a;
1482
+ const el2 = await fixture(html`
1483
+ <sp-picker
1484
+ value="missing"
1485
+ label="Select a Country with a very long label, too long in fact"
1486
+ >
1487
+ <sp-menu-item value="deselect">Deselect Text</sp-menu-item>
1488
+ <sp-menu-item value="inverse">Select Inverse</sp-menu-item>
1489
+ <sp-menu-item>Feather...</sp-menu-item>
1490
+ <sp-menu-item>Select and Mask...</sp-menu-item>
1491
+ <sp-menu-item>Save Selection</sp-menu-item>
1492
+ <sp-menu-item disabled>Make Work Path</sp-menu-item>
1493
+ </sp-picker>
1494
+ `);
1495
+ await elementUpdated(el2);
1496
+ await waitUntil(() => el2.value === "");
1497
+ expect(el2.value).to.equal("");
1498
+ expect((_a = el2.selectedItem) == null ? void 0 : _a.itemText).to.be.undefined;
1499
+ });
1500
+ it("allows event listeners on child items", async () => {
1501
+ const mouseenterSpy = spy();
1502
+ const handleMouseenter = () => mouseenterSpy();
1503
+ const el2 = await fixture(html`
1504
+ <sp-picker
1505
+ label="Select a Country with a very long label, too long in fact"
1506
+ >
1507
+ <sp-menu-item value="deselect" @mouseenter=${handleMouseenter}>
1508
+ Deselect Text
1509
+ </sp-menu-item>
1510
+ </sp-picker>
1511
+ `);
1512
+ await elementUpdated(el2);
1513
+ const hoverEl = el2.querySelector("sp-menu-item");
1514
+ const opened = oneEvent(el2, "sp-opened");
1515
+ el2.open = true;
1516
+ await opened;
1517
+ await elementUpdated(el2);
1518
+ expect(el2.open).to.be.true;
1519
+ hoverEl.dispatchEvent(new MouseEvent("mouseenter"));
1520
+ await elementUpdated(el2);
1521
+ expect(el2.open).to.be.true;
1522
+ const closed = oneEvent(el2, "sp-closed");
1523
+ el2.open = false;
1524
+ await closed;
1525
+ await elementUpdated(el2);
1526
+ expect(el2.open).to.be.false;
1527
+ expect(mouseenterSpy.calledOnce).to.be.true;
1528
+ });
1529
+ it("dispatches events on open/close", async () => {
1530
+ const openedSpy = spy();
1531
+ const closedSpy = spy();
1532
+ const handleOpenedSpy = (event) => openedSpy(event);
1533
+ const handleClosedSpy = (event) => closedSpy(event);
1534
+ const el2 = await fixture(html`
1535
+ <sp-picker
1536
+ label="Select a Country with a very long label, too long in fact"
1537
+ @sp-opened=${handleOpenedSpy}
1538
+ @sp-closed=${handleClosedSpy}
1539
+ >
1540
+ <sp-menu-item value="deselect">Deselect Text</sp-menu-item>
1541
+ </sp-picker>
1542
+ `);
1543
+ await elementUpdated(el2);
1544
+ const opened = oneEvent(el2, "sp-opened");
1545
+ el2.open = true;
1546
+ await opened;
1547
+ await elementUpdated(el2);
1548
+ expect(openedSpy.calledOnce).to.be.true;
1549
+ expect(closedSpy.calledOnce).to.be.false;
1550
+ const closed = oneEvent(el2, "sp-closed");
1551
+ el2.open = false;
1552
+ await closed;
1553
+ await elementUpdated(el2);
1554
+ expect(closedSpy.calledOnce).to.be.true;
1555
+ });
1556
+ it("closes tooltip on button blur", async () => {
1557
+ var _a;
1558
+ const test = await styledFixture(html`
1559
+ <div>${tooltip(tooltip.args)}</div>
1560
+ `);
1561
+ const el2 = test.querySelector("sp-picker");
1562
+ await elementUpdated(el2);
1563
+ const input1 = document.createElement("input");
1564
+ const input2 = document.createElement("input");
1565
+ const tooltipEl = el2.querySelector("sp-tooltip");
1566
+ el2.insertAdjacentElement("beforebegin", input1);
1567
+ el2.insertAdjacentElement("afterend", input2);
1568
+ input1.focus();
1569
+ expect(document.activeElement === input1).to.be.true;
1570
+ const tooltipOpened = oneEvent(el2, "sp-opened");
1571
+ await sendKeys({
1572
+ press: "Tab"
1573
+ });
1574
+ await tooltipOpened;
1575
+ expect(
1576
+ document.activeElement === el2,
1577
+ `Actually, ${(_a = document.activeElement) == null ? void 0 : _a.localName}`
1578
+ ).to.be.true;
1579
+ expect(tooltipEl.open).to.be.true;
1580
+ expect(el2.open).to.be.false;
1581
+ expect(el2.focused).to.be.true;
1582
+ const menuOpen = oneEvent(el2, "sp-opened");
1583
+ const tooltipClosed = oneEvent(el2, "sp-closed");
1584
+ await sendKeys({
1585
+ press: "ArrowDown"
1586
+ });
1587
+ await menuOpen;
1588
+ await tooltipClosed;
1589
+ expect(document.activeElement === el2).to.be.true;
1590
+ expect(tooltipEl.open).to.be.false;
1591
+ expect(el2.open).to.be.true;
1592
+ const menuClosed = oneEvent(el2, "sp-closed");
1593
+ await sendKeys({
1594
+ press: "Tab"
1595
+ });
1596
+ await menuClosed;
1597
+ expect(document.activeElement === el2).to.be.false;
1598
+ expect(tooltipEl.open).to.be.false;
1599
+ expect(el2.open).to.be.false;
1600
+ });
1601
+ describe("disabled", function() {
1602
+ beforeEach(async function() {
1603
+ const test = await fixture(html`
1604
+ <div>${disabled(disabled.args)}</div>
1605
+ `);
1606
+ this.label = test.querySelector("sp-field-label");
1607
+ this.el = test.querySelector("sp-picker");
1608
+ await elementUpdated(this.elel);
1609
+ });
1610
+ it("does not recieve focus from an `<sp-field-label>`", async function() {
1611
+ expect(this.el.disabled).to.be.true;
1612
+ expect(this.el.focused).to.be.false;
1613
+ this.label.click();
1614
+ await elementUpdated(this.el);
1615
+ expect(this.el.focused).to.be.false;
1616
+ });
1617
+ it("does not open from `click()`", async function() {
1618
+ expect(this.el.disabled).to.be.true;
1619
+ expect(this.el.open).to.be.false;
1620
+ this.el.click();
1621
+ await elementUpdated(this.el);
1622
+ expect(this.el.open).to.be.false;
1623
+ });
1624
+ it("does not open from `sendMouse()`", async function() {
1625
+ expect(this.el.disabled).to.be.true;
1626
+ expect(this.el.open).to.be.false;
1627
+ const boundingRect = this.el.button.getBoundingClientRect();
1628
+ sendMouse({
1629
+ steps: [
1630
+ {
1631
+ type: "click",
1632
+ position: [
1633
+ boundingRect.x + boundingRect.width / 2,
1634
+ boundingRect.y + boundingRect.height / 2
1635
+ ]
1636
+ }
1637
+ ]
1638
+ });
1639
+ await nextFrame();
1640
+ await nextFrame();
1641
+ await nextFrame();
1642
+ await nextFrame();
1643
+ expect(this.el.open).to.be.false;
1644
+ });
1645
+ });
1646
+ describe("pending", function() {
1647
+ beforeEach(async function() {
1648
+ const test = await fixture(html`
1649
+ <div>${pending({ pending: true })}</div>
1650
+ `);
1651
+ this.label = test.querySelector("sp-field-label");
1652
+ this.el = test.querySelector("sp-picker");
1653
+ await elementUpdated(this.elel);
1654
+ });
1655
+ it("receives focus from an `<sp-field-label>`", async function() {
1656
+ expect(this.el.focused).to.be.false;
1657
+ this.label.click();
1658
+ await elementUpdated(this.el);
1659
+ expect(this.el.focused).to.be.true;
1660
+ });
1661
+ it("does not open from `click()`", async function() {
1662
+ expect(this.el.open).to.be.false;
1663
+ this.el.click();
1664
+ await elementUpdated(this.el);
1665
+ expect(this.el.open).to.be.false;
1666
+ });
1667
+ it('manages its "name" value in the accessibility tree when [pending]', async () => {
1668
+ const snapshot = await a11ySnapshot(
1669
+ {}
1670
+ );
1671
+ expect(
1672
+ findAccessibilityNode(
1673
+ snapshot,
1674
+ (node) => node.name === "Pending Choose your neighborhood Where do you live?"
1675
+ )
1676
+ ).to.not.be.null;
1677
+ });
1678
+ });
1679
+ }
1680
+ //# sourceMappingURL=index.js.map