Skip to content

Commit

Permalink
feat(input-time-zone): add time zone list mode
Browse files Browse the repository at this point in the history
  • Loading branch information
jcfranco committed Oct 1, 2023
1 parent 50f85f1 commit b58e40e
Show file tree
Hide file tree
Showing 10 changed files with 958 additions and 98 deletions.
19 changes: 17 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion packages/calcite-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@
"focus-trap": "7.5.2",
"form-request-submit-polyfill": "2.0.0",
"lodash-es": "4.17.21",
"sortablejs": "1.15.0"
"sortablejs": "1.15.0",
"timezone-groups": "0.1.0"
},
"devDependencies": {
"@esri/calcite-design-tokens": "1.0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { newE2EPage } from "@stencil/core/testing";
import { html } from "../../../support/formatting";
import {
accessible,
defaults,
Expand All @@ -11,105 +12,205 @@ import {
renders,
t9n,
} from "../../tests/commonTests";
import { html } from "../../../support/formatting";
import { toGMTLabel } from "./utils";
import { toUserFriendlyName } from "./utils";

describe("calcite-input-time-zone", () => {
describe("accessible", () => {
accessible("calcite-input-time-zone");
accessible(addTimeZoneNamePolyfill(html` <calcite-input-time-zone></calcite-input-time-zone>`));
});

describe("focusable", () => {
focusable("calcite-input-time-zone");
focusable(addTimeZoneNamePolyfill(html` <calcite-input-time-zone></calcite-input-time-zone>`));
});

describe("formAssociated", () => {
formAssociated("calcite-input-time-zone", { testValue: "-360", clearable: false });
formAssociated(addTimeZoneNamePolyfill(html` <calcite-input-time-zone></calcite-input-time-zone>`), {
testValue: "-360",
clearable: false,
});
});

describe("hidden", () => {
hidden("calcite-input-time-zone");
hidden(addTimeZoneNamePolyfill(html` <calcite-input-time-zone></calcite-input-time-zone>`));
});

describe("renders", () => {
renders("calcite-input-time-zone", { display: "block" });
renders(addTimeZoneNamePolyfill(html` <calcite-input-time-zone></calcite-input-time-zone>`), { display: "block" });
});

describe("labelable", () => {
labelable("calcite-input-time-zone");
describe.skip("labelable", () => {
labelable(addTimeZoneNamePolyfill(html` <calcite-input-time-zone></calcite-input-time-zone>`));
});

describe("reflects", () => {
reflects("calcite-input-time-zone", [
reflects(addTimeZoneNamePolyfill(html` <calcite-input-time-zone></calcite-input-time-zone>`), [
{ propertyName: "disabled", value: true },
{ propertyName: "maxItems", value: 0 },
{ propertyName: "mode", value: "offset" },
{ propertyName: "open", value: true },
{ propertyName: "scale", value: "m" },
{ propertyName: "overlayPositioning", value: "absolute" },
]);
});

describe("defaults", () => {
defaults("calcite-input-time-zone", [
defaults(addTimeZoneNamePolyfill(html` <calcite-input-time-zone></calcite-input-time-zone>`), [
{ propertyName: "disabled", defaultValue: false },
{ propertyName: "maxItems", defaultValue: 0 },
{ propertyName: "messageOverrides", defaultValue: undefined },
{ propertyName: "mode", defaultValue: "offset" },
{ propertyName: "open", defaultValue: false },
{ propertyName: "overlayPositioning", defaultValue: "absolute" },
{ propertyName: "scale", defaultValue: "m" },
]);
});

describe("disabled", () => {
disabled("calcite-input-time-zone", { shadowAriaAttributeTargetSelector: "calcite-combobox" });
disabled(addTimeZoneNamePolyfill(html` <calcite-input-time-zone></calcite-input-time-zone>`), {
shadowAriaAttributeTargetSelector: "calcite-combobox",
});
});

describe("t9n", () => {
t9n("calcite-input-time-zone");
t9n(addTimeZoneNamePolyfill(html` <calcite-input-time-zone></calcite-input-time-zone>`));
});

describe("selects user's matching timezone offset by default", () => {
const timeZoneNamesAndOffsets = [
{ name: "America/Los_Angeles", offset: -420 },
{ name: "Europe/London", offset: 60 },
];
const testTimeZoneNamesAndOffsets = [
{ name: "America/Los_Angeles", offset: -420, label: "GMT-7" },
{ name: "America/Denver", offset: -360, label: "GMT-6" },
{ name: "Europe/London", offset: 60, label: "GMT+1" },
];

describe("mode", () => {
describe("offset (default)", () => {
describe("selects user's matching time zone offset on initialization", () => {
testTimeZoneNamesAndOffsets.forEach(({ name, offset, label }) => {
it(`selects default time zone for "${name}"`, async () => {
const page = await newE2EPage();
await page.emulateTimezone(name);
await page.setContent(addTimeZoneNamePolyfill(html`<calcite-input-time-zone></calcite-input-time-zone>`));
await page.waitForChanges();

const input = await page.find("calcite-input-time-zone");
expect(await input.getProperty("value")).toBe(`${offset}`);

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

timeZoneNamesAndOffsets.forEach(({ name, offset }) => {
it(`selects default timezone for "${name}"`, async () => {
expect(await timeZoneItem.getProperty("textLabel")).toMatch(label);
});
});
});

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

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

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

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

expect(await timeZoneItem.getProperty("textLabel")).toMatch(toGMTLabel(offset / 60));
expect(await timeZoneItem.getProperty("textLabel")).toMatch(testTimeZoneNamesAndOffsets[1].label);
});

it("ignores invalid values", async () => {
const page = await newE2EPage();
await page.emulateTimezone(testTimeZoneNamesAndOffsets[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}`);

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

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

it("allows users to preselect a timezone offset", async () => {
const page = await newE2EPage();
await page.emulateTimezone("America/Los_Angeles");
await page.setContent(html`<calcite-input-time-zone value="-360"></calcite-input-time-zone>`);
describe("name", () => {
describe("selects user's matching time zone name on initialization", () => {
testTimeZoneNamesAndOffsets.forEach(({ name }) => {
it(`selects default time zone for "${name}"`, async () => {
const page = await newE2EPage();
await page.emulateTimezone(name);
await page.setContent(
await addTimeZoneNamePolyfill(html` <calcite-input-time-zone mode="name"></calcite-input-time-zone>`)
);
await page.waitForChanges();

const input = await page.find("calcite-input-time-zone");
const input = await page.find("calcite-input-time-zone");
expect(await input.getProperty("value")).toBe(name);

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

expect(await timeZoneItem.getProperty("textLabel")).toMatch(toUserFriendlyName(name));
});
});
});

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

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

const timeZoneItem = await page.find("calcite-input-time-zone >>> calcite-combobox-item[selected]");
expect(await input.getProperty("value")).toBe(testTimeZoneNamesAndOffsets[1].name);

expect(await timeZoneItem.getProperty("textLabel")).toMatch("GMT-6");
const timeZoneItem = await page.find("calcite-input-time-zone >>> calcite-combobox-item[selected]");

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

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

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

expect(await input.getProperty("value")).toBe(testTimeZoneNamesAndOffsets[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)
);
});
});
});

it("does not allow users to deselect a timezone offset", async () => {
it("does not allow users to deselect a time zone offset", async () => {
const page = await newE2EPage();
await page.emulateTimezone("America/Los_Angeles");
await page.setContent(html`<calcite-input-time-zone value="-360" open></calcite-input-time-zone>`);
await page.emulateTimezone(testTimeZoneNamesAndOffsets[0].name);
await page.setContent(
addTimeZoneNamePolyfill(
html`
<calcite-input-time-zone value="${testTimeZoneNamesAndOffsets[1].offset}" open></calcite-input-time-zone>
`
)
);
await page.waitForChanges();

let selectedTimeZoneItem = await page.find("calcite-input-time-zone >>> calcite-combobox-item[selected]");
Expand All @@ -119,17 +220,86 @@ 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("-360");
expect(await selectedTimeZoneItem.getProperty("textLabel")).toMatch("GMT-6");
expect(await input.getProperty("value")).toBe(`${testTimeZoneNamesAndOffsets[1].offset}`);
expect(await selectedTimeZoneItem.getProperty("textLabel")).toMatch(testTimeZoneNamesAndOffsets[1].label);
});

it("supports setting maxItems to display", async () => {
const page = await newE2EPage();
await page.setContent(html`<calcite-input-time-zone max-items="7"></calcite-input-time-zone>`);

await page.setContent(
addTimeZoneNamePolyfill(html` <calcite-input-time-zone max-items="7"></calcite-input-time-zone>`)
);
const internalCombobox = await page.find("calcite-input-time-zone >>> calcite-combobox");

// we assume maxItems works properly on combobox
expect(await internalCombobox.getProperty("maxItems")).toBe(7);
});
});

/**
* Helper to inject an Intl polyfill to support time zone-related APIs
* Extended due to lack of support for "Intl.DateTimeFormatOptions#timeZoneName" in Chromium v92 (bundled in Puppeteer v10).
*
* @param testHtml
*/
function addTimeZoneNamePolyfill(testHtml: string): string {
return html` <script>
const OriginalDateTimeFormat = Intl.DateTimeFormat;
class ExtendedDateTimeFormat extends OriginalDateTimeFormat {
constructor(locales, options) {
const originalOptions = { ...options };
delete options?.timeZoneName;
super(locales, options);
this.originalOptions = originalOptions;
}
formatToParts(date) {
const originalParts = super.formatToParts(date);
const timeZoneName = this.originalOptions.timeZoneName;
if (timeZoneName === "shortOffset") {
const { timeZone } = this.originalOptions;
// hardcoding GMT and time zone names for this particular test suite
const offsetString =
"GMT" +
(timeZone === "America/Los_Angeles"
? "-7"
: timeZone === "America/Denver"
? "-6"
: timeZone === "Europe/London"
? "+1"
: "+0");
originalParts.push({ type: "timeZoneName", value: offsetString });
}
return originalParts;
}
resolvedOptions() {
const originalResolvedOptions = OriginalDateTimeFormat.resolvedOptions;
const options = originalResolvedOptions.call(this);
const timeZoneName = options.timeZoneName;
if (timeZoneName === "shortOffset") {
options.timeZoneName = undefined;
options.timeZone = options.timeZone || "UTC";
return options;
}
return options;
}
}
Intl.DateTimeFormat = ExtendedDateTimeFormat;
Intl.supportedValuesOf = function (key) {
if (key === "timeZone") {
return ["America/Los_Angeles", "America/Denver", "Europe/London"];
}
};
</script>
${testHtml}`;
}
Loading

0 comments on commit b58e40e

Please sign in to comment.