Skip to content

Commit

Permalink
fix(segmented-control): refresh items when added dynamically (#7567)
Browse files Browse the repository at this point in the history
**Related Issue:** #5736 

## Summary

Sets up a mutation observer to update the item state when children are
added/removed after initialization.

**Note**: left/right + up/down arrow navigation test were consolidated.
  • Loading branch information
jcfranco authored Aug 23, 2023
1 parent b7092d3 commit 2e36eb3
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -172,100 +172,92 @@ describe("calcite-segmented-control", () => {
});

describe("keyboard navigation", () => {
it("selects item with left and arrow keys", async () => {
const page = await newE2EPage();
await page.setContent(
`<calcite-segmented-control>
<calcite-segmented-control-item value="1" checked>one</calcite-segmented-control-item>
<calcite-segmented-control-item value="2">two</calcite-segmented-control-item>
<calcite-segmented-control-item value="3">three</calcite-segmented-control-item>
</calcite-segmented-control>`
);
async function assertArrowSelection(page: E2EPage): Promise<void> {
const element = await page.find("calcite-segmented-control");
const spy = await element.spyOnEvent("calciteSegmentedControlChange");

const firstElement = await element.find("calcite-segmented-control-item[checked]");
await firstElement.click();
await element.press("ArrowRight");
await page.waitForChanges();

let selected = await element.find("calcite-segmented-control-item[checked]");
let value = await selected.getProperty("value");
expect(value).toBe("2");

await element.press("ArrowRight");
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("3");

await element.press("ArrowRight");
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("1");

await element.press("ArrowLeft");
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("3");

await element.press("ArrowLeft");
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("2");

await element.press("ArrowLeft");
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("1");

await tabIntoFirstElement();
await cycleThroughItemsAndAssertValue("left-right");
expect(spy).toHaveReceivedEventTimes(6);
});

it("selects item with up and down keys", async () => {
await tabIntoFirstElement();
await cycleThroughItemsAndAssertValue("up-down");
expect(spy).toHaveReceivedEventTimes(12);

async function tabIntoFirstElement(): Promise<void> {
const firstElement = await element.find("calcite-segmented-control-item[checked]");
await firstElement.click();

await page.keyboard.down("Shift");
await page.keyboard.press("Tab");
await page.keyboard.up("Shift");

await page.keyboard.press("Tab");
}

async function cycleThroughItemsAndAssertValue(keys: "left-right" | "up-down"): Promise<void> {
const [moveBeforeArrowKey, moveAfterArrowKey] =
keys === "left-right" ? ["ArrowLeft", "ArrowRight"] : ["ArrowUp", "ArrowDown"];

await element.press(moveAfterArrowKey);
await page.waitForChanges();

let selected = await element.find("calcite-segmented-control-item[checked]");
let value = await selected.getProperty("value");
expect(value).toBe("2");

await element.press(moveAfterArrowKey);
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("3");

await element.press(moveAfterArrowKey);
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("1");

await element.press(moveBeforeArrowKey);
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("3");

await element.press(moveBeforeArrowKey);
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("2");

await element.press(moveBeforeArrowKey);
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("1");
}
}

it("selects item with left-right/up-down arrow keys", async () => {
const page = await newE2EPage();
await page.setContent(
`<calcite-segmented-control>
html`<calcite-segmented-control>
<calcite-segmented-control-item value="1" checked>one</calcite-segmented-control-item>
<calcite-segmented-control-item value="2">two</calcite-segmented-control-item>
<calcite-segmented-control-item value="3">three</calcite-segmented-control-item>
</calcite-segmented-control>`
);
const element = await page.find("calcite-segmented-control");
const spy = await element.spyOnEvent("calciteSegmentedControlChange");

const firstElement = await element.find("calcite-segmented-control-item[checked]");
await firstElement.click();
await element.press("ArrowDown");
let selected = await element.find("calcite-segmented-control-item[checked]");
let value = await selected.getProperty("value");
expect(value).toBe("2");

await element.press("ArrowDown");
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("3");

await element.press("ArrowDown");
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("1");

await element.press("ArrowUp");
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("3");

await element.press("ArrowUp");
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("2");

await element.press("ArrowUp");
selected = await element.find("calcite-segmented-control-item[checked]");
value = await selected.getProperty("value");
expect(value).toBe("1");
await assertArrowSelection(page);
});

expect(spy).toHaveReceivedEventTimes(6);
it("selects item with left-right/up-down arrow keys after adding items programmatically", async () => {
const page = await newE2EPage();
await page.setContent(html`<calcite-segmented-control></calcite-segmented-control>`);

await page.$eval("calcite-segmented-control", (segmentedControl: HTMLCalciteSegmentedControlElement) => {
segmentedControl.innerHTML = `
<calcite-segmented-control-item value="1" checked>one</calcite-segmented-control-item>
<calcite-segmented-control-item value="2">two</calcite-segmented-control-item>
<calcite-segmented-control-item value="3">three</calcite-segmented-control-item>`;
});

await assertArrowSelection(page);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
setUpLoadableComponent,
} from "../../utils/loadable";
import { Appearance, Layout, Scale, Width } from "../interfaces";
import { createObserver } from "../../utils/observers";

/**
* @slot - A slot for adding `calcite-segmented-control-item`s.
Expand Down Expand Up @@ -134,15 +135,7 @@ export class SegmentedControl

componentWillLoad(): void {
setUpLoadableComponent(this);

const items = this.getItems();
const lastChecked = items.filter((item) => item.checked).pop();

if (lastChecked) {
this.selectItem(lastChecked);
} else if (items[0]) {
items[0].tabIndex = 0;
}
this.setUpItems();
}

componentDidLoad(): void {
Expand All @@ -154,12 +147,14 @@ export class SegmentedControl
connectInteractive(this);
connectLabel(this);
connectForm(this);
this.mutationObserver?.observe(this.el, { childList: true });
}

disconnectedCallback(): void {
disconnectInteractive(this);
disconnectLabel(this);
disconnectForm(this);
this.mutationObserver?.unobserve(this.el);
}

componentDidRender(): void {
Expand Down Expand Up @@ -287,6 +282,8 @@ export class SegmentedControl

defaultValue: SegmentedControl["value"];

private mutationObserver = createObserver("mutation", () => this.setUpItems());

//--------------------------------------------------------------------------
//
// Private Methods
Expand Down Expand Up @@ -332,4 +329,15 @@ export class SegmentedControl
match.focus();
}
}

private setUpItems(): void {
const items = this.getItems();
const lastChecked = items.filter((item) => item.checked).pop();

if (lastChecked) {
this.selectItem(lastChecked);
} else if (items[0]) {
items[0].tabIndex = 0;
}
}
}

0 comments on commit 2e36eb3

Please sign in to comment.