Skip to content

Commit b7a4c77

Browse files
authored
fix(chip-group): Improve programmatic Chip selection behavior (#9213)
**Related Issue:** #8801 ## Summary - Adds support for programmatically selecting Chips within a Chip Group through internal events. - Events will *not* emit when programmatic selection of a Chip has occurred, only on user interaction. - Adds tests, local demo of behavior.
1 parent 1201138 commit b7a4c77

File tree

5 files changed

+414
-30
lines changed

5 files changed

+414
-30
lines changed

packages/calcite-components/src/components.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,7 @@ export namespace Components {
926926
* Made into a prop for testing purposes only
927927
*/
928928
"messages": ChipMessages;
929+
"parentChipGroup": HTMLCalciteChipGroupElement;
929930
/**
930931
* Specifies the size of the component. When contained in a parent `calcite-chip-group` inherits the parent's `scale` value.
931932
*/
@@ -5984,6 +5985,8 @@ declare global {
59845985
"calciteChipClose": void;
59855986
"calciteChipSelect": void;
59865987
"calciteInternalChipKeyEvent": KeyboardEvent;
5988+
"calciteInternalChipSelect": void;
5989+
"calciteInternalSyncSelectedChips": void;
59875990
}
59885991
interface HTMLCalciteChipElement extends Components.CalciteChip, HTMLStencilElement {
59895992
addEventListener<K extends keyof HTMLCalciteChipElementEventMap>(type: K, listener: (this: HTMLCalciteChipElement, ev: CalciteChipCustomEvent<HTMLCalciteChipElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
@@ -8327,6 +8330,9 @@ declare namespace LocalJSX {
83278330
*/
83288331
"onCalciteChipSelect"?: (event: CalciteChipCustomEvent<void>) => void;
83298332
"onCalciteInternalChipKeyEvent"?: (event: CalciteChipCustomEvent<KeyboardEvent>) => void;
8333+
"onCalciteInternalChipSelect"?: (event: CalciteChipCustomEvent<void>) => void;
8334+
"onCalciteInternalSyncSelectedChips"?: (event: CalciteChipCustomEvent<void>) => void;
8335+
"parentChipGroup"?: HTMLCalciteChipGroupElement;
83308336
/**
83318337
* Specifies the size of the component. When contained in a parent `calcite-chip-group` inherits the parent's `scale` value.
83328338
*/

packages/calcite-components/src/components/chip-group/chip-group.e2e.ts

Lines changed: 280 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -439,26 +439,289 @@ describe("calcite-chip-group", () => {
439439
await page.waitForChanges();
440440
expect(await page.evaluate(() => document.activeElement.id)).toEqual(chip4.id);
441441
});
442+
443+
it("selectedItems property is correctly populated at load when property is set on chips in DOM", async () => {
444+
const page = await newE2EPage();
445+
await page.setContent(
446+
html`<calcite-chip-group label="test-label" selection-mode="multiple">
447+
<calcite-chip label="test-label"></calcite-chip>
448+
<calcite-chip label="test-label"></calcite-chip>
449+
<calcite-chip label="test-label"></calcite-chip>
450+
<calcite-chip id="chip-4" selected label="test-label"></calcite-chip>
451+
<calcite-chip id="chip-5" selected label="test-label"></calcite-chip>
452+
</calcite-chip-group>`,
453+
);
454+
const element = await page.find("calcite-chip-group");
455+
const chip4 = await page.find("#chip-4");
456+
const chip5 = await page.find("#chip-5");
457+
await assertSelectedItems.setUpEvents(page);
458+
await page.waitForChanges();
459+
460+
expect(await element.getProperty("selectedItems")).toHaveLength(2);
461+
await assertSelectedItems(page, { expectedItemIds: [chip4.id, chip5.id] });
462+
});
442463
});
443464

444-
it("selectedItems property is correctly populated at load when property is set on chips in DOM", async () => {
445-
const page = await newE2EPage();
446-
await page.setContent(
447-
html`<calcite-chip-group label="test-label" selection-mode="multiple">
448-
<calcite-chip label="test-label"></calcite-chip>
449-
<calcite-chip label="test-label"></calcite-chip>
450-
<calcite-chip label="test-label"></calcite-chip>
451-
<calcite-chip id="chip-4" selected label="test-label"></calcite-chip>
452-
<calcite-chip id="chip-5" selected label="test-label"></calcite-chip>
453-
</calcite-chip-group>`,
454-
);
455-
const element = await page.find("calcite-chip-group");
456-
const chip4 = await page.find("#chip-4");
457-
const chip5 = await page.find("#chip-5");
458-
await page.waitForChanges();
465+
describe("programmatically selecting Chips", () => {
466+
it("programmatically setting selected on a chip should update the component but not emit public events", async () => {
467+
const page = await newE2EPage();
468+
await page.setContent(
469+
html`<calcite-chip-group label="test-label" selection-mode="single">
470+
<calcite-chip label="test-label"></calcite-chip>
471+
<calcite-chip label="test-label"></calcite-chip>
472+
<calcite-chip label="test-label"></calcite-chip>
473+
<calcite-chip id="chip-4" selected label="test-label"></calcite-chip>
474+
<calcite-chip id="chip-5" label="test-label"></calcite-chip>
475+
</calcite-chip-group>`,
476+
);
477+
const element = await page.find("calcite-chip-group");
478+
const chip4 = await page.find("#chip-4");
479+
const chip5 = await page.find("#chip-5");
480+
const chipGroupSelectSpy = await element.spyOnEvent("calciteChipGroupSelect");
481+
await assertSelectedItems.setUpEvents(page);
482+
483+
const chipSelectSpy1 = await chip4.spyOnEvent("calciteChipSelect");
484+
const chipSelectSpy2 = await chip5.spyOnEvent("calciteChipSelect");
485+
await page.waitForChanges();
486+
487+
expect(await element.getProperty("selectedItems")).toHaveLength(1);
488+
await assertSelectedItems(page, { expectedItemIds: [chip4.id] });
489+
490+
expect(chipGroupSelectSpy).toHaveReceivedEventTimes(0);
491+
expect(chipSelectSpy1).toHaveReceivedEventTimes(0);
492+
expect(chipSelectSpy2).toHaveReceivedEventTimes(0);
493+
494+
await chip5.setAttribute("selected", true);
495+
await page.waitForChanges();
496+
expect(chipGroupSelectSpy).toHaveReceivedEventTimes(0);
497+
expect(chipSelectSpy1).toHaveReceivedEventTimes(0);
498+
expect(chipSelectSpy2).toHaveReceivedEventTimes(0);
499+
expect(await element.getProperty("selectedItems")).toHaveLength(1);
500+
await assertSelectedItems(page, { expectedItemIds: [chip5.id] });
501+
});
502+
503+
it("programmatically setting selected on a chip in single-persist should update the component but not emit public events", async () => {
504+
const page = await newE2EPage();
505+
await page.setContent(
506+
html`<calcite-chip-group label="test-label" selection-mode="single-persist">
507+
<calcite-chip label="test-label"></calcite-chip>
508+
<calcite-chip label="test-label"></calcite-chip>
509+
<calcite-chip label="test-label"></calcite-chip>
510+
<calcite-chip id="chip-4" selected label="test-label"></calcite-chip>
511+
<calcite-chip id="chip-5" label="test-label"></calcite-chip>
512+
</calcite-chip-group>`,
513+
);
514+
const element = await page.find("calcite-chip-group");
515+
const chip4 = await page.find("#chip-4");
516+
const chip5 = await page.find("#chip-5");
517+
const chipGroupSelectSpy = await element.spyOnEvent("calciteChipGroupSelect");
518+
await assertSelectedItems.setUpEvents(page);
519+
520+
const chipSelectSpy1 = await chip4.spyOnEvent("calciteChipSelect");
521+
const chipSelectSpy2 = await chip5.spyOnEvent("calciteChipSelect");
522+
await page.waitForChanges();
523+
524+
expect(await element.getProperty("selectedItems")).toHaveLength(1);
525+
await assertSelectedItems(page, { expectedItemIds: [chip4.id] });
526+
527+
expect(chipGroupSelectSpy).toHaveReceivedEventTimes(0);
528+
expect(chipSelectSpy1).toHaveReceivedEventTimes(0);
529+
expect(chipSelectSpy2).toHaveReceivedEventTimes(0);
530+
531+
chip4.removeAttribute("selected");
532+
await page.waitForChanges();
533+
expect(chipGroupSelectSpy).toHaveReceivedEventTimes(0);
534+
expect(chipSelectSpy1).toHaveReceivedEventTimes(0);
535+
expect(chipSelectSpy2).toHaveReceivedEventTimes(0);
536+
expect(await element.getProperty("selectedItems")).toHaveLength(0);
537+
await assertSelectedItems(page, { expectedItemIds: [] });
538+
539+
chip5.setAttribute("selected", true);
540+
await page.waitForChanges();
541+
expect(chipGroupSelectSpy).toHaveReceivedEventTimes(0);
542+
expect(chipSelectSpy1).toHaveReceivedEventTimes(0);
543+
expect(chipSelectSpy2).toHaveReceivedEventTimes(0);
544+
expect(await element.getProperty("selectedItems")).toHaveLength(1);
545+
await assertSelectedItems(page, { expectedItemIds: [chip5.id] });
546+
547+
chip4.setAttribute("selected", true);
548+
await page.waitForChanges();
549+
expect(chipGroupSelectSpy).toHaveReceivedEventTimes(0);
550+
expect(chipSelectSpy1).toHaveReceivedEventTimes(0);
551+
expect(chipSelectSpy2).toHaveReceivedEventTimes(0);
552+
expect(await element.getProperty("selectedItems")).toHaveLength(1);
553+
await assertSelectedItems(page, { expectedItemIds: [chip4.id] });
554+
});
555+
it("programmatically setting selected on a chip in multiple should update the component but not emit public events", async () => {
556+
const page = await newE2EPage();
557+
await page.setContent(
558+
html`<calcite-chip-group label="test-label" selection-mode="multiple">
559+
<calcite-chip label="test-label"></calcite-chip>
560+
<calcite-chip label="test-label"></calcite-chip>
561+
<calcite-chip label="test-label"></calcite-chip>
562+
<calcite-chip id="chip-4" selected label="test-label"></calcite-chip>
563+
<calcite-chip id="chip-5" label="test-label"></calcite-chip>
564+
</calcite-chip-group>`,
565+
);
566+
const element = await page.find("calcite-chip-group");
567+
const chip4 = await page.find("#chip-4");
568+
const chip5 = await page.find("#chip-5");
569+
const chipGroupSelectSpy = await element.spyOnEvent("calciteChipGroupSelect");
570+
await assertSelectedItems.setUpEvents(page);
459571

460-
expect(await element.getProperty("selectedItems")).toHaveLength(2);
461-
await assertSelectedItems(page, { expectedItemIds: [chip4.id, chip5.id] });
572+
const chipSelectSpy1 = await chip4.spyOnEvent("calciteChipSelect");
573+
const chipSelectSpy2 = await chip5.spyOnEvent("calciteChipSelect");
574+
await page.waitForChanges();
575+
576+
expect(await element.getProperty("selectedItems")).toHaveLength(1);
577+
await assertSelectedItems(page, { expectedItemIds: [chip4.id] });
578+
579+
expect(chipGroupSelectSpy).toHaveReceivedEventTimes(0);
580+
expect(chipSelectSpy1).toHaveReceivedEventTimes(0);
581+
expect(chipSelectSpy2).toHaveReceivedEventTimes(0);
582+
583+
chip5.setAttribute("selected", true);
584+
await page.waitForChanges();
585+
expect(chipGroupSelectSpy).toHaveReceivedEventTimes(0);
586+
expect(chipSelectSpy1).toHaveReceivedEventTimes(0);
587+
expect(chipSelectSpy2).toHaveReceivedEventTimes(0);
588+
expect(await element.getProperty("selectedItems")).toHaveLength(2);
589+
await assertSelectedItems(page, { expectedItemIds: [chip4.id, chip5.id] });
590+
});
591+
});
592+
593+
describe("updating component after page load", () => {
594+
it("should update selected items without emitting event if chips are added after page load in multiple", async () => {
595+
const page = await newE2EPage();
596+
await page.setContent(
597+
`<calcite-chip-group label="test-label" selection-mode="multiple">
598+
<calcite-chip label="test-label"></calcite-chip>
599+
<calcite-chip label="test-label"></calcite-chip>
600+
<calcite-chip label="test-label"></calcite-chip>
601+
<calcite-chip id="chip-4" selected label="test-label"></calcite-chip>
602+
<calcite-chip id="chip-5" selected label="test-label"></calcite-chip>
603+
</calcite-chip-group>`,
604+
);
605+
const element = await page.find("calcite-chip-group");
606+
const chip4 = await page.find("#chip-4");
607+
const chip5 = await page.find("#chip-5");
608+
const chipGroupSelectSpy = await element.spyOnEvent("calciteChipGroupSelect");
609+
await assertSelectedItems.setUpEvents(page);
610+
611+
const chipSelectSpy1 = await chip4.spyOnEvent("calciteChipSelect");
612+
const chipSelectSpy2 = await chip5.spyOnEvent("calciteChipSelect");
613+
await page.waitForChanges();
614+
615+
expect(chipGroupSelectSpy).toHaveReceivedEventTimes(0);
616+
expect(chipSelectSpy1).toHaveReceivedEventTimes(0);
617+
expect(chipSelectSpy2).toHaveReceivedEventTimes(0);
618+
expect(await element.getProperty("selectedItems")).toHaveLength(2);
619+
await assertSelectedItems(page, { expectedItemIds: [chip4.id, chip5.id] });
620+
621+
await page.evaluate(() => {
622+
const group = document.querySelector("calcite-chip-group");
623+
const newChip = document.createElement("calcite-chip");
624+
newChip.id = "chip-6";
625+
newChip.selected = true;
626+
group.appendChild(newChip);
627+
});
628+
629+
await page.waitForChanges();
630+
expect(chipGroupSelectSpy).toHaveReceivedEventTimes(0);
631+
expect(chipSelectSpy1).toHaveReceivedEventTimes(0);
632+
expect(chipSelectSpy2).toHaveReceivedEventTimes(0);
633+
expect(await element.getProperty("selectedItems")).toHaveLength(3);
634+
await assertSelectedItems(page, { expectedItemIds: [chip4.id, chip5.id, "chip-6"] });
635+
});
636+
637+
it("should update selected items without emitting event if chips are added after page load in single", async () => {
638+
const page = await newE2EPage();
639+
await page.setContent(
640+
`<calcite-chip-group label="test-label" selection-mode="single">
641+
<calcite-chip label="test-label"></calcite-chip>
642+
<calcite-chip label="test-label"></calcite-chip>
643+
<calcite-chip label="test-label"></calcite-chip>
644+
<calcite-chip id="chip-4" selected label="test-label"></calcite-chip>
645+
<calcite-chip id="chip-5" label="test-label"></calcite-chip>
646+
</calcite-chip-group>`,
647+
);
648+
const element = await page.find("calcite-chip-group");
649+
const chip4 = await page.find("#chip-4");
650+
const chipGroupSelectSpy = await element.spyOnEvent("calciteChipGroupSelect");
651+
await assertSelectedItems.setUpEvents(page);
652+
653+
const chipSelectSpy1 = await chip4.spyOnEvent("calciteChipSelect");
654+
await page.waitForChanges();
655+
656+
expect(chipGroupSelectSpy).toHaveReceivedEventTimes(0);
657+
expect(chipSelectSpy1).toHaveReceivedEventTimes(0);
658+
expect(await element.getProperty("selectedItems")).toHaveLength(1);
659+
await assertSelectedItems(page, { expectedItemIds: [chip4.id] });
660+
661+
await page.evaluate(() => {
662+
const group = document.querySelector("calcite-chip-group");
663+
const newChip = document.createElement("calcite-chip");
664+
newChip.id = "chip-6";
665+
newChip.selected = true;
666+
group.appendChild(newChip);
667+
});
668+
669+
await page.waitForChanges();
670+
expect(chipGroupSelectSpy).toHaveReceivedEventTimes(0);
671+
expect(chipSelectSpy1).toHaveReceivedEventTimes(0);
672+
expect(await element.getProperty("selectedItems")).toHaveLength(1);
673+
await assertSelectedItems(page, { expectedItemIds: ["chip-6"] });
674+
});
675+
676+
it("should update selected items without emitting event if chips are removed after page load", async () => {
677+
const page = await newE2EPage();
678+
await page.setContent(
679+
`<calcite-chip-group label="test-label" selection-mode="multiple">
680+
<calcite-chip label="test-label"></calcite-chip>
681+
<calcite-chip label="test-label"></calcite-chip>
682+
<calcite-chip label="test-label"></calcite-chip>
683+
<calcite-chip id="chip-4" selected label="test-label"></calcite-chip>
684+
<calcite-chip id="chip-5" selected label="test-label"></calcite-chip>
685+
</calcite-chip-group>`,
686+
);
687+
const element = await page.find("calcite-chip-group");
688+
const chip4 = await page.find("#chip-4");
689+
const chip5 = await page.find("#chip-5");
690+
const chipGroupSelectSpy = await element.spyOnEvent("calciteChipGroupSelect");
691+
await assertSelectedItems.setUpEvents(page);
692+
693+
const chipSelectSpy1 = await chip4.spyOnEvent("calciteChipSelect");
694+
const chipSelectSpy2 = await chip5.spyOnEvent("calciteChipSelect");
695+
await page.waitForChanges();
696+
697+
expect(chipGroupSelectSpy).toHaveReceivedEventTimes(0);
698+
expect(chipSelectSpy1).toHaveReceivedEventTimes(0);
699+
expect(chipSelectSpy2).toHaveReceivedEventTimes(0);
700+
expect(await element.getProperty("selectedItems")).toHaveLength(2);
701+
await assertSelectedItems(page, { expectedItemIds: [chip4.id, chip5.id] });
702+
703+
await page.evaluate(() => {
704+
document.querySelector("calcite-chip:last-child").remove();
705+
});
706+
707+
await page.waitForChanges();
708+
expect(chipGroupSelectSpy).toHaveReceivedEventTimes(0);
709+
expect(chipSelectSpy1).toHaveReceivedEventTimes(0);
710+
expect(chipSelectSpy2).toHaveReceivedEventTimes(0);
711+
expect(await element.getProperty("selectedItems")).toHaveLength(1);
712+
await assertSelectedItems(page, { expectedItemIds: [chip4.id] });
713+
714+
await page.evaluate(() => {
715+
document.querySelector("calcite-chip:last-child").remove();
716+
});
717+
718+
await page.waitForChanges();
719+
expect(chipGroupSelectSpy).toHaveReceivedEventTimes(0);
720+
expect(chipSelectSpy1).toHaveReceivedEventTimes(0);
721+
expect(chipSelectSpy2).toHaveReceivedEventTimes(0);
722+
expect(await element.getProperty("selectedItems")).toHaveLength(0);
723+
await assertSelectedItems(page, { expectedItemIds: [] });
724+
});
462725
});
463726
});
464727

0 commit comments

Comments
 (0)