Skip to content

Commit

Permalink
fix(input-time-zone): ensure selected item is properly displayed when…
Browse files Browse the repository at this point in the history
… there are other items with the same offset (#9134)

**Related Issue:** #8272 

## Summary

This updates `input-time-zone` to also check for an item's label when
updating state to properly reflect user selection.
  • Loading branch information
jcfranco authored Apr 18, 2024
1 parent 75686b7 commit 1f94903
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,23 @@ import {
import { toUserFriendlyName } from "./utils";

describe("calcite-input-time-zone", () => {
type TestTimeZoneItem = {
name: string;
offset: number;
label: string;
};

// for stability, we use time zones that are unaffected by daylight savings time
const testTimeZoneNamesAndOffsets = [
const testTimeZoneItems: TestTimeZoneItem[] = [
{ name: "America/Mexico_City", offset: -360, label: "GMT-6" },
{ name: "America/Phoenix", offset: -420, label: "GMT-7" },
{ name: "Pacific/Guam", offset: 600, label: "GMT+10" },
{ name: "Pacific/Galapagos", offset: -360, label: "GMT-6" },
];

async function simpleTestProvider(): Promise<TagAndPage> {
const page = await newE2EPage();
await page.emulateTimezone(testTimeZoneNamesAndOffsets[0].name);
await page.emulateTimezone(testTimeZoneItems[0].name);
await page.setContent(addTimeZoneNamePolyfill(html`<calcite-input-time-zone></calcite-input-time-zone>`));

return {
Expand All @@ -47,7 +54,7 @@ describe("calcite-input-time-zone", () => {
{
tagOrHTML: addTimeZoneNamePolyfill(html`<calcite-input-time-zone></calcite-input-time-zone>`),
beforeContent: async (page) => {
await page.emulateTimezone(testTimeZoneNamesAndOffsets[0].name);
await page.emulateTimezone(testTimeZoneItems[0].name);
},
},
{
Expand All @@ -69,7 +76,7 @@ describe("calcite-input-time-zone", () => {
labelable({
tagOrHTML: addTimeZoneNamePolyfill(html`<calcite-input-time-zone></calcite-input-time-zone>`),
beforeContent: async (page) => {
await page.emulateTimezone(testTimeZoneNamesAndOffsets[0].name);
await page.emulateTimezone(testTimeZoneItems[0].name);
},
});
});
Expand Down Expand Up @@ -115,7 +122,7 @@ describe("calcite-input-time-zone", () => {
describe("mode", () => {
describe("offset (default)", () => {
describe("selects user's matching time zone offset on initialization", () => {
testTimeZoneNamesAndOffsets.forEach(({ name, offset, label }) => {
testTimeZoneItems.forEach(({ name, offset, label }) => {
it(`selects default time zone for "${name}"`, async () => {
const page = await newE2EPage();
await page.emulateTimezone(name);
Expand All @@ -134,52 +141,52 @@ describe("calcite-input-time-zone", () => {

it("allows users to preselect a time zone offset", async () => {
const page = await newE2EPage();
await page.emulateTimezone(testTimeZoneNamesAndOffsets[0].name);
await page.emulateTimezone(testTimeZoneItems[0].name);
await page.setContent(
await addTimeZoneNamePolyfill(
html`<calcite-input-time-zone value="${testTimeZoneNamesAndOffsets[1].offset}"></calcite-input-time-zone>`,
html`<calcite-input-time-zone value="${testTimeZoneItems[1].offset}"></calcite-input-time-zone>`,
),
);

const input = await page.find("calcite-input-time-zone");

expect(await input.getProperty("value")).toBe(`${testTimeZoneNamesAndOffsets[1].offset}`);
expect(await input.getProperty("value")).toBe(`${testTimeZoneItems[1].offset}`);

const timeZoneItem = await page.find("calcite-input-time-zone >>> calcite-combobox-item[selected]");

expect(await timeZoneItem.getProperty("textLabel")).toMatch(testTimeZoneNamesAndOffsets[1].label);
expect(await timeZoneItem.getProperty("textLabel")).toMatch(testTimeZoneItems[1].label);
});

it("ignores invalid values", async () => {
const page = await newE2EPage();
await page.emulateTimezone(testTimeZoneNamesAndOffsets[0].name);
await page.emulateTimezone(testTimeZoneItems[0].name);
await page.setContent(
await addTimeZoneNamePolyfill(html`<calcite-input-time-zone value="9000"></calcite-input-time-zone>`),
);

const input = await page.find("calcite-input-time-zone");

expect(await input.getProperty("value")).toBe(`${testTimeZoneNamesAndOffsets[0].offset}`);
expect(await input.getProperty("value")).toBe(`${testTimeZoneItems[0].offset}`);

const timeZoneItem = await page.find("calcite-input-time-zone >>> calcite-combobox-item[selected]");

expect(await timeZoneItem.getProperty("textLabel")).toMatch(testTimeZoneNamesAndOffsets[0].label);
expect(await timeZoneItem.getProperty("textLabel")).toMatch(testTimeZoneItems[0].label);
});

it("omits filtered or non-localized time zones (incoming to browser)", async () => {
const page = await newE2EPage();
await page.emulateTimezone(testTimeZoneNamesAndOffsets[0].name);
await page.emulateTimezone(testTimeZoneItems[0].name);
await page.setContent(
await addTimeZoneNamePolyfill(html`<calcite-input-time-zone value="600"></calcite-input-time-zone>`),
);

const input = await page.find("calcite-input-time-zone");

expect(await input.getProperty("value")).toBe(`${testTimeZoneNamesAndOffsets[2].offset}`);
expect(await input.getProperty("value")).toBe(`${testTimeZoneItems[2].offset}`);

const timeZoneItem = await page.find("calcite-input-time-zone >>> calcite-combobox-item[selected]");

expect(await timeZoneItem.getProperty("textLabel")).toMatch(testTimeZoneNamesAndOffsets[2].label);
expect(await timeZoneItem.getProperty("textLabel")).toMatch(testTimeZoneItems[2].label);
});

it("looks up in label and time zone groups (not displayed)", async () => {
Expand All @@ -189,7 +196,7 @@ describe("calcite-input-time-zone", () => {
const searchTerms = [displayLabelSearchTerm, groupedTimeZoneSearchTerm, gmtSearchTerm];

const page = await newE2EPage();
await page.emulateTimezone(testTimeZoneNamesAndOffsets[0].name);
await page.emulateTimezone(testTimeZoneItems[0].name);
await page.setContent(await addTimeZoneNamePolyfill(html`<calcite-input-time-zone></calcite-input-time-zone>`));

const input = await page.find("calcite-input-time-zone");
Expand Down Expand Up @@ -240,7 +247,7 @@ describe("calcite-input-time-zone", () => {

describe("name", () => {
describe("selects user's matching time zone name on initialization", () => {
testTimeZoneNamesAndOffsets.forEach(({ name }) => {
testTimeZoneItems.forEach(({ name }) => {
it(`selects default time zone for "${name}"`, async () => {
const page = await newE2EPage();
await page.emulateTimezone(name);
Expand All @@ -261,30 +268,25 @@ describe("calcite-input-time-zone", () => {

it("allows users to preselect a time zone by name", async () => {
const page = await newE2EPage();
await page.emulateTimezone(testTimeZoneNamesAndOffsets[0].name);
await page.emulateTimezone(testTimeZoneItems[0].name);
await page.setContent(
await addTimeZoneNamePolyfill(
html`<calcite-input-time-zone
mode="name"
value="${testTimeZoneNamesAndOffsets[1].name}"
></calcite-input-time-zone>`,
html`<calcite-input-time-zone mode="name" value="${testTimeZoneItems[1].name}"></calcite-input-time-zone>`,
),
);

const input = await page.find("calcite-input-time-zone");

expect(await input.getProperty("value")).toBe(testTimeZoneNamesAndOffsets[1].name);
expect(await input.getProperty("value")).toBe(testTimeZoneItems[1].name);

const timeZoneItem = await page.find("calcite-input-time-zone >>> calcite-combobox-item[selected]");

expect(await timeZoneItem.getProperty("textLabel")).toMatch(
toUserFriendlyName(testTimeZoneNamesAndOffsets[1].name),
);
expect(await timeZoneItem.getProperty("textLabel")).toMatch(toUserFriendlyName(testTimeZoneItems[1].name));
});

it("ignores invalid values", async () => {
const page = await newE2EPage();
await page.emulateTimezone(testTimeZoneNamesAndOffsets[0].name);
await page.emulateTimezone(testTimeZoneItems[0].name);
await page.setContent(
await addTimeZoneNamePolyfill(
html`<calcite-input-time-zone mode="name" value="Does/Not/Exist"></calcite-input-time-zone>`,
Expand All @@ -293,23 +295,21 @@ describe("calcite-input-time-zone", () => {

const input = await page.find("calcite-input-time-zone");

expect(await input.getProperty("value")).toBe(testTimeZoneNamesAndOffsets[0].name);
expect(await input.getProperty("value")).toBe(testTimeZoneItems[0].name);

const timeZoneItem = await page.find("calcite-input-time-zone >>> calcite-combobox-item[selected]");

expect(await timeZoneItem.getProperty("textLabel")).toMatch(
toUserFriendlyName(testTimeZoneNamesAndOffsets[0].name),
);
expect(await timeZoneItem.getProperty("textLabel")).toMatch(toUserFriendlyName(testTimeZoneItems[0].name));
});
});
});

it("does not allow users to deselect a time zone offset", async () => {
const page = await newE2EPage();
await page.emulateTimezone(testTimeZoneNamesAndOffsets[0].name);
await page.emulateTimezone(testTimeZoneItems[0].name);
await page.setContent(
addTimeZoneNamePolyfill(html`
<calcite-input-time-zone value="${testTimeZoneNamesAndOffsets[1].offset}" open></calcite-input-time-zone>
<calcite-input-time-zone value="${testTimeZoneItems[1].offset}" open></calcite-input-time-zone>
`),
);
await page.waitForChanges();
Expand All @@ -321,13 +321,61 @@ describe("calcite-input-time-zone", () => {
selectedTimeZoneItem = await page.find("calcite-input-time-zone >>> calcite-combobox-item[selected]");
const input = await page.find("calcite-input-time-zone");

expect(await input.getProperty("value")).toBe(`${testTimeZoneNamesAndOffsets[1].offset}`);
expect(await selectedTimeZoneItem.getProperty("textLabel")).toMatch(testTimeZoneNamesAndOffsets[1].label);
expect(await input.getProperty("value")).toBe(`${testTimeZoneItems[1].offset}`);
expect(await selectedTimeZoneItem.getProperty("textLabel")).toMatch(testTimeZoneItems[1].label);
});

describe("selection of subsequent items with the same offset", () => {
const testCases: {
name: string;
initialTimeZoneItem: TestTimeZoneItem;
}[] = [
{
name: "displays selected item when changing from another offset",
initialTimeZoneItem: testTimeZoneItems[1],
},
{
name: "displays selected item when changing from the same offset",
initialTimeZoneItem: testTimeZoneItems[0],
},
];

testCases.forEach(({ name, initialTimeZoneItem }) => {
it(`${name}`, async () => {
const page = await newE2EPage();
await page.emulateTimezone(initialTimeZoneItem.name);
await page.setContent(
addTimeZoneNamePolyfill(
html`<calcite-input-time-zone value="${initialTimeZoneItem.offset}"></calcite-input-time-zone> `,
),
);

const input = await page.find("calcite-input-time-zone");
await input.click();
await page.waitForChanges();
await input.type("(GMT-6)");
await page.waitForChanges();

const sharedOffsetTimeZoneItems = await page.findAll(
"calcite-input-time-zone >>> calcite-combobox-item:not([hidden])",
);
expect(sharedOffsetTimeZoneItems).toHaveLength(2);

await sharedOffsetTimeZoneItems[1].click();
await page.waitForChanges();

const selectedTimeZoneItem = await page.find("calcite-input-time-zone >>> calcite-combobox-item[selected]");
const expectedTimeZoneItem = testTimeZoneItems[3];

expect(await input.getProperty("value")).toBe(`${expectedTimeZoneItem.offset}`);
expect(await selectedTimeZoneItem.getProperty("value")).toMatch(expectedTimeZoneItem.name);
});
});
});

it("supports setting maxItems to display", async () => {
const page = await newE2EPage();
await page.emulateTimezone(testTimeZoneNamesAndOffsets[0].name);
await page.emulateTimezone(testTimeZoneItems[0].name);
await page.setContent(
addTimeZoneNamePolyfill(html`<calcite-input-time-zone max-items="7"></calcite-input-time-zone>`),
);
Expand All @@ -339,7 +387,7 @@ describe("calcite-input-time-zone", () => {

it("recreates time zone items when item-dependent props change", async () => {
const page = await newE2EPage();
await page.emulateTimezone(testTimeZoneNamesAndOffsets[0].name);
await page.emulateTimezone(testTimeZoneItems[0].name);
await page.setContent(addTimeZoneNamePolyfill(html`<calcite-input-time-zone></calcite-input-time-zone>`));
const inputTimeZone = await page.find("calcite-input-time-zone");

Expand Down Expand Up @@ -405,7 +453,7 @@ function addTimeZoneNamePolyfill(testHtml: string): string {
} else {
offsetString =
"GMT" +
(timeZone === "America/Mexico_City"
(timeZone === "America/Mexico_City" || timeZone === "Pacific/Galapagos"
? "-6"
: timeZone === "America/Phoenix"
? "-7"
Expand Down Expand Up @@ -442,6 +490,7 @@ function addTimeZoneNamePolyfill(testHtml: string): string {
return [
"America/Mexico_City",
"America/Phoenix",
"Pacific/Galapagos",
"Pacific/Guam",
// not available in Chromium v92 at time of testing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ import {
FormComponent,
HiddenFormInputSlot,
} from "../../utils/form";
import { createTimeZoneItems, getUserTimeZoneName, getUserTimeZoneOffset } from "./utils";
import {
createTimeZoneItems,
findTimeZoneItemByProp,
getUserTimeZoneName,
getUserTimeZoneOffset,
} from "./utils";
import { InputTimeZoneMessages } from "./assets/input-time-zone/t9n";
import { TimeZoneItem, TimeZoneMode } from "./interfaces";

Expand Down Expand Up @@ -297,10 +302,10 @@ export class InputTimeZone
private onComboboxChange = (event: CustomEvent): void => {
event.stopPropagation();
const combobox = event.target as HTMLCalciteComboboxElement;
const selected = this.findTimeZoneItem(combobox.selectedItems[0].getAttribute("data-value"));

const selected = this.findTimeZoneItemByLabel(combobox.selectedItems[0].textLabel);
const selectedValue = `${selected.value}`;
if (this.value === selectedValue) {

if (this.value === selectedValue && selected.label === this.selectedTimeZoneItem.label) {
return;
}

Expand All @@ -322,13 +327,11 @@ export class InputTimeZone
};

private findTimeZoneItem(value: number | string): TimeZoneItem {
const valueToMatch = value;
return findTimeZoneItemByProp(this.timeZoneItems, "value", value);
}

return this.timeZoneItems.find(
({ value }) =>
// intentional == to match string to number
value == valueToMatch,
);
private findTimeZoneItemByLabel(label: string): TimeZoneItem {
return findTimeZoneItemByProp(this.timeZoneItems, "label", label);
}

private async updateTimeZoneItemsAndSelection(): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,15 @@ function getTimeZoneShortOffset(
const parts = dateTimeFormat.formatToParts(referenceDateInMs);
return parts.find(({ type }) => type === "timeZoneName").value;
}

export function findTimeZoneItemByProp(
timeZoneItems: TimeZoneItem[],
prop: string,
valueToMatch: string | number,
): TimeZoneItem {
return timeZoneItems.find(
(item) =>
// intentional == to match string to number
item[prop] == valueToMatch,
);
}

0 comments on commit 1f94903

Please sign in to comment.