diff --git a/packages/web-components/fast-foundation/.storybook/main.cjs b/packages/web-components/fast-foundation/.storybook/main.cjs index 25dbc387df0..3cf425aa932 100644 --- a/packages/web-components/fast-foundation/.storybook/main.cjs +++ b/packages/web-components/fast-foundation/.storybook/main.cjs @@ -13,6 +13,10 @@ module.exports = { builder: "webpack5", }, webpackFinal: async config => { + config.performance = { + ...(config.performance ?? {}), + hints: false, + }; config.module.rules = [ { test: /\.ts$/, @@ -23,10 +27,18 @@ module.exports = { transpileOnly: true, }, }, + { + test: /\.m?js$/, + enforce: "pre", + loader: require.resolve("source-map-loader"), + resolve: { + fullySpecified: false, + }, + }, ]; config.resolve.plugins = [ - ...(config.resolve.plugins || []), + ...(config.resolve.plugins ?? []), new ResolveTypescriptPlugin({ includeNodeModules: true, }), diff --git a/packages/web-components/fast-foundation/.storybook/manager.js b/packages/web-components/fast-foundation/.storybook/manager.mjs similarity index 100% rename from packages/web-components/fast-foundation/.storybook/manager.js rename to packages/web-components/fast-foundation/.storybook/manager.mjs diff --git a/packages/web-components/fast-foundation/.storybook/package.json b/packages/web-components/fast-foundation/.storybook/package.json deleted file mode 100644 index 9e26dfeeb6e..00000000000 --- a/packages/web-components/fast-foundation/.storybook/package.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/packages/web-components/fast-foundation/.storybook/preview.js b/packages/web-components/fast-foundation/.storybook/preview.js index cbf38bb3638..b7eaea49411 100644 --- a/packages/web-components/fast-foundation/.storybook/preview.js +++ b/packages/web-components/fast-foundation/.storybook/preview.js @@ -1,3 +1,5 @@ +import { useArgs } from "@storybook/client-api"; + import "@microsoft/fast-element/polyfills"; import "../src/anchor/stories/anchor.register.js"; import "../src/anchored-region/stories/anchored-region.register.js"; @@ -56,3 +58,10 @@ import "../src/menu/stories/menu.register.js"; import "../src/tree-item/stories/tree-item.register.js"; import "../src/tree-view/stories/tree-view.register.js"; + +export const decorators = [ + (Story, context) => { + const [_, updateArgs] = useArgs(); + return Story({ ...context, updateArgs }); + }, +]; diff --git a/packages/web-components/fast-foundation/docs/api-report.md b/packages/web-components/fast-foundation/docs/api-report.md index 2bdad67fae8..59123665352 100644 --- a/packages/web-components/fast-foundation/docs/api-report.md +++ b/packages/web-components/fast-foundation/docs/api-report.md @@ -1648,7 +1648,9 @@ export class FASTRadioGroup extends FASTElement { name: string; // (undocumented) protected nameChanged(): void; - orientation: Orientation | "horizontal" | "vertical"; + orientation: Orientation; + // (undocumented) + positioningRegion: HTMLDivElement; readOnly: boolean; // (undocumented) protected readOnlyChanged(): void; diff --git a/packages/web-components/fast-foundation/package.json b/packages/web-components/fast-foundation/package.json index 98143510005..3cc8cf2b603 100644 --- a/packages/web-components/fast-foundation/package.json +++ b/packages/web-components/fast-foundation/package.json @@ -67,13 +67,13 @@ "@microsoft/tsdoc-config": "^0.13.4", "@playwright/test": "^1.25.1", "@rollup/plugin-node-resolve": "^13.3.0", - "@storybook/addon-docs": "6.5.9", - "@storybook/addon-essentials": "6.5.9", - "@storybook/addon-links": "6.5.9", - "@storybook/builder-webpack5": "6.5.9", + "@storybook/addon-docs": "6.5.10", + "@storybook/addon-essentials": "6.5.10", + "@storybook/addon-links": "6.5.10", + "@storybook/builder-webpack5": "6.5.10", "@storybook/csf": "0.0.2--canary.0899bb7.0", - "@storybook/html": "6.5.9", - "@storybook/manager-webpack5": "6.5.9", + "@storybook/html": "6.5.10", + "@storybook/manager-webpack5": "6.5.10", "concurrently": "^7.3.0", "esm": "^3.2.25", "express": "^4.18.1", diff --git a/packages/web-components/fast-foundation/src/__test__/helpers.ts b/packages/web-components/fast-foundation/src/__test__/helpers.ts index 89a34b82643..77c08a54e4b 100644 --- a/packages/web-components/fast-foundation/src/__test__/helpers.ts +++ b/packages/web-components/fast-foundation/src/__test__/helpers.ts @@ -4,6 +4,7 @@ import type { Args, ComponentAnnotations, StoryAnnotations, + StoryContext, } from "@storybook/csf"; import qs from "qs"; @@ -48,10 +49,10 @@ export function fixtureURL( */ export function renderComponent( template: ViewTemplate -): (args: TArgs) => Element | DocumentFragment | null { - return function (args) { +): (args: TArgs, context: StoryContext) => Element | DocumentFragment | null { + return function (args, { updateArgs }) { const storyFragment = new DocumentFragment(); - template.render(args, storyFragment); + template.render({...args, updateArgs }, storyFragment); if (storyFragment.childElementCount === 1) { return storyFragment.firstElementChild; } diff --git a/packages/web-components/fast-foundation/src/accordion-item/accordion-item.pw.spec.ts b/packages/web-components/fast-foundation/src/accordion-item/accordion-item.pw.spec.ts index aa4e8b474f3..81d23784f3c 100644 --- a/packages/web-components/fast-foundation/src/accordion-item/accordion-item.pw.spec.ts +++ b/packages/web-components/fast-foundation/src/accordion-item/accordion-item.pw.spec.ts @@ -1,118 +1,88 @@ +import type { Locator, Page } from "@playwright/test"; import { expect, test } from "@playwright/test"; import { fixtureURL } from "../__test__/helpers.js"; +import type { FASTAccordionItem } from "./accordion-item.js"; test.describe("Accordion item", () => { - test("should set an `aria-level` to the heading when provided", async ({ page }) => { - await page.goto( - fixtureURL("accordion-item--accordion-item", { - headinglevel: 4, - }) - ); + test.describe("States, Attributes, and Properties", () => { + let page: Page; + let element: Locator; + let heading: Locator; - const element = page.locator("fast-accordion-item"); + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); - await expect(element).toHaveJSProperty("headinglevel", 4); + element = page.locator("fast-accordion-item"); - const heading = element.locator(`[role="heading"]`); + heading = page.locator(`[role="heading"]`); - await expect(heading).toHaveAttribute("aria-level", "4"); - }); - - test("should set a default heading level of 2 when NOT provided a `headinglevel`", async ({ - page, - }) => { - await page.goto(fixtureURL("accordion-item--accordion-item")); - - const element = page.locator("fast-accordion-item"); - - await expect(element).toHaveJSProperty("headinglevel", 2); - - const heading = page.locator(`[role="heading"]`); - - await expect(heading).toHaveAttribute("aria-level", "2"); - }); + await page.goto(fixtureURL("accordion-item--accordion-item")); + }); - test("should set `aria-expanded` value to false on the internal button when expanded is undefined", async ({ - page, - }) => { - await page.goto(fixtureURL("accordion-item--accordion-item")); + test.afterAll(async () => { + await page.close(); + }); - const element = page.locator("fast-accordion-item button"); + test("should set a default heading level of 2 when `headinglevel` is not provided", async () => { + await expect(element).not.hasAttribute("headinglevel"); - await expect(element).toHaveAttribute("aria-expanded", "false"); - }); + await expect(element).toHaveJSProperty("headinglevel", 2); + }); - test.describe("when not expanded", () => { - test("should NOT have a class of `expanded`", async ({ page }) => { - await page.goto( - fixtureURL("accordion-item--accordion-item", { - expanded: false, - }) - ); + test("should set the `aria-level` attribute on the internal heading element equal to the heading level", async () => { + await expect(heading).toHaveAttribute("aria-level", "2"); - const element = page.locator("fast-accordion-item"); + await element.evaluate(node => { + node.headinglevel = 3; + }); - await expect(element).not.toHaveClass("expanded"); + await expect(heading).toHaveAttribute("aria-level", "3"); }); - test("should set `aria-expanded` value to false on the internal button", async ({ - page, - }) => { - await page.goto( - fixtureURL("accordion-item--accordion-item", { - expanded: false, - }) - ); - - const element = page.locator("fast-accordion-item button"); + test("should NOT have a class of `expanded` when the `expanded` property is false", async () => { + await element.evaluate(node => { + node.expanded = false; + }); - await expect(element).toHaveAttribute("aria-expanded", "false"); + await expect(element).not.toHaveClass(/expanded/); }); - }); - - test.describe("when expanded", () => { - test("should have a class of `expanded`", async ({ page }) => { - await page.goto( - fixtureURL("accordion-item--accordion-item", { - expanded: true, - }) - ); - const element = page.locator("fast-accordion-item"); + test("should have a class of `expanded` when the `expanded` property is true", async () => { + await element.evaluate(node => { + node.expanded = true; + }); - await expect(element).toHaveClass("expanded"); + await expect(element).toHaveClass(/expanded/); }); - test("should set `aria-expanded` value to true on the internal button", async ({ - page, - }) => { - await page.goto( - fixtureURL("accordion-item--accordion-item", { - expanded: true, - }) - ); + test("should set `aria-expanded` property on the internal control equal to the `expanded` property", async () => { + const button = element.locator("button"); - const element = page.locator("fast-accordion-item button"); + await element.evaluate(node => { + node.expanded = true; + }); - await expect(element).toHaveAttribute("aria-expanded", "true"); - }); - }); + await expect(button).toHaveAttribute("aria-expanded", "true"); - test("should set internal properties to match the id when provided", async ({ - page, - }) => { - const id = "testId"; + await element.evaluate(node => { + node.expanded = false; + }); - await page.goto(fixtureURL("accordion-item--accordion-item", { id })); + await expect(button).toHaveAttribute("aria-expanded", "false"); + }); - const element = page.locator("fast-accordion-item"); + test("should set internal properties to match the id when provided", async () => { + await element.evaluate(node => { + node.id = "testId"; + }); - const region = element.locator(`[role="region"]`); + const region = element.locator(`[role="region"]`); - await expect(region).toHaveAttribute("aria-labelledby", id); + await expect(region).toHaveAttribute("aria-labelledby", "testId"); - const button = element.locator("button"); + const button = element.locator("button"); - await expect(button).toHaveId(id); + await expect(button).toHaveId("testId"); + }); }); }); diff --git a/packages/web-components/fast-foundation/src/anchor/anchor.pw.spec.ts b/packages/web-components/fast-foundation/src/anchor/anchor.pw.spec.ts index cea6de20ffa..9ff2b3c0f99 100644 --- a/packages/web-components/fast-foundation/src/anchor/anchor.pw.spec.ts +++ b/packages/web-components/fast-foundation/src/anchor/anchor.pw.spec.ts @@ -1,47 +1,59 @@ import { spinalCase } from "@microsoft/fast-web-utilities"; import { expect, test } from "@playwright/test"; +import type { Locator, Page } from "@playwright/test"; import { fixtureURL } from "../__test__/helpers.js"; test.describe("Anchor", () => { - test.describe("should set the attribute on the internal anchor", () => { - const attributes = { - href: "href", - ping: "ping", - hreflang: "en-GB", - referrerpolicy: "no-referrer", - rel: "external", - target: "_blank", - type: "foo", - ariaAtomic: "true", - ariaBusy: "false", - ariaControls: "testId", - ariaCurrent: "page", - ariaDescribedby: "testId", - ariaDetails: "testId", - ariaDisabled: "true", - ariaErrormessage: "test", - ariaExpanded: "true", - ariaFlowto: "testId", - ariaHaspopup: "true", - ariaHidden: "true", - ariaInvalid: "spelling", - ariaKeyshortcuts: "F4", - ariaLabel: "foo", - ariaLabelledby: "testId", - ariaLive: "polite", - ariaOwns: "testId", - ariaRelevant: "removals", - ariaRoledescription: "slide", - }; - - Object.entries(attributes).forEach(([key, value]) => { - test(key, async ({ page }) => { - await page.goto(fixtureURL("anchor--anchor", { [key]: value })); - - const anchor = page.locator("fast-anchor a"); - - await expect(anchor).toHaveAttribute(spinalCase(key), `${value}`); - }); - }); + let page: Page; + let element: Locator; + + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); + + element = page.locator("fast-anchor"); + + await page.goto(fixtureURL("anchor", attributes)); + }); + + test.afterAll(async () => { + await page.close(); }); + + const attributes = { + href: "href", + ping: "ping", + hreflang: "en-GB", + referrerpolicy: "no-referrer", + rel: "external", + target: "_blank", + type: "foo", + ariaAtomic: "true", + ariaBusy: "false", + ariaControls: "testId", + ariaCurrent: "page", + ariaDescribedby: "testId", + ariaDetails: "testId", + ariaDisabled: "true", + ariaErrormessage: "test", + ariaExpanded: "true", + ariaFlowto: "testId", + ariaHaspopup: "true", + ariaHidden: "true", + ariaInvalid: "spelling", + ariaKeyshortcuts: "F4", + ariaLabel: "foo", + ariaLabelledby: "testId", + ariaLive: "polite", + ariaOwns: "testId", + ariaRelevant: "removals", + ariaRoledescription: "slide", + }; + + for (const [attribute, value] of Object.entries(attributes)) { + const attributeSpinalCase = spinalCase(attribute); + + test(`should set the \`${attributeSpinalCase}\` attribute to \`${value}\` on the internal control`, async () => { + await expect(element).toHaveAttribute(attributeSpinalCase, `${value}`); + }); + } }); diff --git a/packages/web-components/fast-foundation/src/breadcrumb-item/breadcrumb-item.pw.spec.ts b/packages/web-components/fast-foundation/src/breadcrumb-item/breadcrumb-item.pw.spec.ts index efe9520b635..443f27ae38a 100644 --- a/packages/web-components/fast-foundation/src/breadcrumb-item/breadcrumb-item.pw.spec.ts +++ b/packages/web-components/fast-foundation/src/breadcrumb-item/breadcrumb-item.pw.spec.ts @@ -1,96 +1,110 @@ import { spinalCase } from "@microsoft/fast-web-utilities"; +import type { Locator, Page } from "@playwright/test"; import { expect, test } from "@playwright/test"; import { fixtureURL } from "../__test__/helpers.js"; +import type { FASTBreadcrumbItem } from "./breadcrumb-item.js"; test.describe("Breadcrumb item", () => { - test("should include a `role` of `listitem`", async ({ page }) => { - await page.goto(fixtureURL("breadcrumb-item--breadcrumb-item")); + test.describe("States, Attributes, and Properties", () => { + let page: Page; + let element: Locator; - const listitem = page.locator(`fast-breadcrumb-item > div`); + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); - await expect(listitem).toHaveAttribute("role", "listitem"); - }); + element = page.locator("fast-breadcrumb-item"); - test("should add an element with a class of `separator` when `separator` is true", async ({ - page, - }) => { - await page.goto( - fixtureURL("breadcrumb-item--breadcrumb-item", { separator: true }) - ); + await page.goto(fixtureURL("breadcrumb-item--breadcrumb-item")); + }); - const separator = page.locator("fast-breadcrumb-item .separator"); + test.afterAll(async () => { + await page.close(); + }); - await expect(separator).toHaveCount(1); - }); + test("should include a `role` of `listitem`", async () => { + await expect(element.locator("> div")).toHaveAttribute("role", "listitem"); + }); - test("should render an internal anchor when `href` is provided", async ({ page }) => { - // Storybook doesn't allow URLs to be passed as arg values - // https://storybook.js.org/docs/react/writing-stories/args#setting-args-through-the-url + test("should render an internal anchor when the `href` attribute is not provided", async () => { + const anchor = element.locator("a"); - const href = "foo"; + await expect(element).not.hasAttribute("href"); - await page.goto(fixtureURL("breadcrumb-item--breadcrumb-item", { href })); + await expect(element).toHaveJSProperty("href", undefined); - const a = page.locator("fast-breadcrumb-item a"); + await expect(anchor).toHaveCount(1); - await expect(a).toHaveCount(1); + await expect(element.locator("a")).toHaveCount(1); + }); - await expect(a).toHaveAttribute("href", href); - }); + test("should render an internal anchor when the `href` attribute is provided", async () => { + await element.evaluate(node => { + node.setAttribute("href", "https://fast.design"); + }); - test("should render an internal anchor when `href` is not provided", async ({ - page, - }) => { - await page.goto(fixtureURL("breadcrumb-item--breadcrumb-item")); + const anchor = element.locator("a"); - const a = page.locator("fast-breadcrumb-item a"); + await expect(anchor).toHaveCount(1); - await expect(a).toHaveCount(1); - }); + await expect(anchor).toHaveAttribute("href", "https://fast.design"); - test.describe("should set the attribute on the internal anchor", () => { - const attributes = { - download: "foo", - hreflang: "en-GB", - ping: "foo", - referrerpolicy: "no-referrer", - rel: "external", - target: "_blank", - type: "foo", - ariaAtomic: "true", - ariaBusy: "false", - ariaControls: "testId", - ariaCurrent: "page", - ariaDescribedby: "testId", - ariaDetails: "testId", - ariaDisabled: "true", - ariaErrormessage: "test", - ariaExpanded: "true", - ariaFlowto: "testId", - ariaHaspopup: "true", - ariaHidden: "true", - ariaInvalid: "spelling", - ariaKeyshortcuts: "F4", - ariaLabel: "foo", - ariaLabelledby: "testId", - ariaLive: "polite", - ariaOwns: "testId", - ariaRelevant: "removals", - ariaRoledescription: "slide", - }; - - Object.entries(attributes).forEach(([key, value]) => { - test(key, async ({ page }) => { + await element.evaluate(node => { + node.removeAttribute("href"); + }); + }); + + test("should add an element with a class of `separator` when the `separator` property is true", async () => { + await element.evaluate(node => { + node.separator = true; + }); + + await expect(element.locator(".separator")).toHaveCount(1); + }); + + test.describe("should set the attribute on the internal control", () => { + const attributes = { + download: "foo", + hreflang: "en-GB", + ping: "foo", + referrerpolicy: "no-referrer", + rel: "external", + target: "_blank", + type: "foo", + ariaAtomic: "true", + ariaBusy: "false", + ariaControls: "testId", + ariaCurrent: "page", + ariaDescribedby: "testId", + ariaDetails: "testId", + ariaDisabled: "true", + ariaErrormessage: "test", + ariaExpanded: "true", + ariaFlowto: "testId", + ariaHaspopup: "true", + ariaHidden: "true", + ariaInvalid: "spelling", + ariaKeyshortcuts: "F4", + ariaLabel: "foo", + ariaLabelledby: "testId", + ariaLive: "polite", + ariaOwns: "testId", + ariaRelevant: "removals", + ariaRoledescription: "slide", + }; + + test.beforeAll(async () => { await page.goto( - fixtureURL("breadcrumb-item--breadcrumb-item-with-href", { - [key]: value, - }) + fixtureURL("breadcrumb-item--breadcrumb-item-with-href", attributes) ); + }); - const a = page.locator("fast-breadcrumb-item a"); + for (const [attribute, value] of Object.entries(attributes)) { + const attrToken = spinalCase(attribute); - await expect(a).toHaveAttribute(spinalCase(key), value); - }); + test(`should set the \`${attrToken}\` attribute to \`${value}\``, async () => { + await expect(element).toHaveAttribute(attrToken, `${value}`); + }); + } }); }); }); diff --git a/packages/web-components/fast-foundation/src/breadcrumb/breadcrumb.pw.spec.ts b/packages/web-components/fast-foundation/src/breadcrumb/breadcrumb.pw.spec.ts index 5f059fb8497..48156e4665f 100644 --- a/packages/web-components/fast-foundation/src/breadcrumb/breadcrumb.pw.spec.ts +++ b/packages/web-components/fast-foundation/src/breadcrumb/breadcrumb.pw.spec.ts @@ -1,61 +1,59 @@ +import type { Locator, Page } from "@playwright/test"; import { expect, test } from "@playwright/test"; import { fixtureURL } from "../__test__/helpers.js"; +import type { FASTBreadcrumb } from "./breadcrumb.js"; test.describe("Breadcrumb", () => { - test("should include a `role` of `navigation`", async ({ page }) => { - await page.goto(fixtureURL("breadcrumb--breadcrumb")); + test.describe("States, Attributes, and Properties", () => { + let page: Page; + let element: Locator; - const element = page.locator("fast-breadcrumb"); + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); - await expect(element).toHaveAttribute("role", "navigation"); - }); - - test("should include an internal element with a `role` of `list`", async ({ - page, - }) => { - await page.goto(fixtureURL("breadcrumb--breadcrumb")); + element = page.locator("fast-breadcrumb"); - const list = page.locator("fast-breadcrumb .list"); - - await expect(list).toHaveAttribute("role", "list"); - }); - - test("should not render a separator on last item", async ({ page }) => { - await page.goto(fixtureURL("breadcrumb--breadcrumb")); + await page.goto(fixtureURL("breadcrumb--breadcrumb")); + }); - const items = page.locator("fast-breadcrumb fast-breadcrumb-item"); + test.afterAll(async () => { + await page.close(); + }); - await expect(items).toHaveCount(3); + test("should have a role of 'navigation'", async () => { + await expect(element).toHaveAttribute("role", "navigation"); + }); - await expect(items.last()).toHaveJSProperty("separator", false); - }); + test("should include an internal element with a `role` of `list`", async () => { + await expect(element.locator(".list")).toHaveAttribute("role", "list"); + }); - test("should set `aria-current` on the internal anchor of the last node when `href` is present", async ({ - page, - }) => { - await page.goto(fixtureURL("breadcrumb--breadcrumb")); + test("should not render a separator on last item", async () => { + const items = element.locator("fast-breadcrumb-item"); - const a = page.locator("fast-breadcrumb fast-breadcrumb-item:last-of-type a"); + await expect(items).toHaveCount(3); - await expect(a).toHaveAttribute("aria-current", "page"); - }); + await expect(items.last()).toHaveJSProperty("separator", false); + }); - test("should remove `aria-current` from any prior breadcrumb item children with child anchors when a new node is appended", async ({ - page, - }) => { - await page.goto(fixtureURL("breadcrumb--breadcrumb")); + test("should set `aria-current` on the internal anchor of the last node when `href` is present", async () => { + await expect( + element.locator("fast-breadcrumb-item:last-of-type a") + ).toHaveAttribute("aria-current", "page"); + }); - const element = page.locator("fast-breadcrumb"); - const items = element.locator("fast-breadcrumb-item"); - const lastItemAnchor = items.last(); - const secondItemAnchor = items.nth(2).locator("a"); + test("should remove `aria-current` from any prior breadcrumb item children with child anchors when a new node is appended", async () => { + await expect( + element.locator("fast-breadcrumb-item:last-of-type a") + ).toHaveAttribute("aria-current", "page"); - await expect(lastItemAnchor).toHaveAttribute("aria-current", "page"); + await element.evaluate(node => { + node.append(document.createElement("fast-breadcrumb-item")); + }); - await element.evaluate(node => { - node.append(document.createElement("fast-breadcrumb-item")); + await expect( + element.locator("fast-breadcrumb-item:nth-of-type(2) a") + ).not.toHaveAttribute("aria-current", "page"); }); - - await expect(secondItemAnchor).not.toHaveAttribute("aria-current", "page"); }); }); diff --git a/packages/web-components/fast-foundation/src/button/button.pw.spec.ts b/packages/web-components/fast-foundation/src/button/button.pw.spec.ts index 7a7cc821ac0..e4f21aa4cb5 100644 --- a/packages/web-components/fast-foundation/src/button/button.pw.spec.ts +++ b/packages/web-components/fast-foundation/src/button/button.pw.spec.ts @@ -1,383 +1,130 @@ +import { spinalCase } from "@microsoft/fast-web-utilities"; +import type { Locator, Page } from "@playwright/test"; import { expect, test } from "@playwright/test"; import { fixtureURL } from "../__test__/helpers.js"; test.describe("Button", () => { - test("should set the `autofocus` boolean attribute on the internal button", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { autofocus: true })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveBooleanAttribute("autofocus"); - }); - - test("should set the `disabled` boolean attribute on the internal button", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { disabled: true })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveBooleanAttribute("disabled"); - }); - - test("should set the `formnovalidate` boolean attribute on the internal button", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { formnovalidate: true })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveBooleanAttribute("formnovalidate"); - }); - - test("should set the `formaction` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { formaction: "foo" })); - - const element = page.locator("fast-button"); - - const control = element.locator("button"); - - await expect(control).toHaveAttribute("formaction", "foo"); - }); - - test("should set the `formenctype` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { formenctype: "foo" })); - - const element = page.locator("fast-button"); - - const control = element.locator("button"); - - await expect(control).toHaveAttribute("formenctype", "foo"); - }); - - test("should set the `formmethod` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { formmethod: "post" })); - - const element = page.locator("fast-button"); - - const control = element.locator("button"); - - await expect(control).toHaveAttribute("formmethod", "post"); - }); - - test("should set the `formtarget` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { formtarget: "_blank" })); - - const element = page.locator("fast-button"); - - const control = element.locator("button"); - - await expect(control).toHaveAttribute("formtarget", "_blank"); - }); - - test("should set the `name` attribute on the internal control", async ({ page }) => { - await page.goto(fixtureURL("button--button", { name: "foo" })); - - const element = page.locator("fast-button"); - - const control = element.locator("button"); - - await expect(control).toHaveAttribute("name", "foo"); - }); - - test("should set the `type` attribute on the internal control", async ({ page }) => { - await page.goto(fixtureURL("button--button", { type: "submit" })); - - const element = page.locator("fast-button"); - - const control = element.locator("button"); - - await expect(control).toHaveAttribute("type", "submit"); - }); - - test("should set the `value` attribute on the internal control", async ({ page }) => { - await page.goto(fixtureURL("button--button", { value: "Reset" })); - - const element = page.locator("fast-button"); - - const control = element.locator("button"); - - await expect(control).toHaveAttribute("value", "Reset"); - }); - - test("should set the `form` attribute on the internal button when `formId` is provided", async ({ - page, - }) => { - const formId = "foo"; - - await page.goto(fixtureURL("button--button", { formId })); - - const button = page.locator("fast-button button"); - - await expect(button).toHaveAttribute("form", formId); - }); - - test("should set the `aria-atomic` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { ariaAtomic: "true" })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveAttribute("aria-atomic", "true"); - }); - - test("should set the `aria-busy` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { ariaBusy: "false" })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveAttribute("aria-busy", "false"); - }); - - test("should set the `aria-controls` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { ariaControls: "testId" })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveAttribute("aria-controls", "testId"); - }); - - test("should set the `aria-current` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { ariaCurrent: "page" })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveAttribute("aria-current", "page"); - }); - - test("should set the `aria-describedby` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { ariaDescribedby: "testId" })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveAttribute("aria-describedby", "testId"); - }); - - test("should set the `aria-details` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { ariaDetails: "testId" })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveAttribute("aria-details", "testId"); - }); - - test("should set the `aria-disabled` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { ariaDisabled: "true" })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveAttribute("aria-disabled", "true"); - }); - - test("should set the `aria-errormessage` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { ariaErrormessage: "test" })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveAttribute("aria-errormessage", "test"); - }); - - test("should set the `aria-expanded` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { ariaExpanded: "true" })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveAttribute("aria-expanded", "true"); - }); - - test("should set the `aria-flowto` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { ariaFlowto: "testId" })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveAttribute("aria-flowto", "testId"); - }); - - test("should set the `aria-haspopup` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { ariaHaspopup: "true" })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveAttribute("aria-haspopup", "true"); - }); - - test("should set the `aria-hidden` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { ariaHidden: "true" })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveAttribute("aria-hidden", "true"); - }); - - test("should set the `aria-invalid` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { ariaInvalid: "spelling" })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveAttribute("aria-invalid", "spelling"); - }); - - test("should set the `aria-keyshortcuts` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { ariaKeyshortcuts: "F4" })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveAttribute("aria-keyshortcuts", "F4"); - }); - - test("should set the `aria-label` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { ariaLabel: "Foo label" })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveAttribute("aria-label", "Foo label"); - }); - - test("should set the `aria-labelledby` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { ariaLabelledby: "testId" })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveAttribute("aria-labelledby", "testId"); - }); - - test("should set the `aria-live` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { ariaLive: "polite" })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveAttribute("aria-live", "polite"); - }); - - test("should set the `aria-owns` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { ariaOwns: "testId" })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveAttribute("aria-owns", "testId"); - }); - - test("should set the `aria-pressed` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { ariaPressed: "true" })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveAttribute("aria-pressed", "true"); - }); - - test("should set the `aria-relevant` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { ariaRelevant: "removals" })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); - - await expect(button).toHaveAttribute("aria-relevant", "removals"); - }); - - test("should set the `aria-roledescription` attribute on the internal control", async ({ - page, - }) => { - await page.goto(fixtureURL("button--button", { ariaRoledescription: "slide" })); - - const element = page.locator("fast-button"); - - const button = element.locator("button"); + test.describe("States, Attributes, and Properties", () => { + // Syncronous tests all run on the same page instance + let page: Page; + let element: Locator; + let control: Locator; + + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); + + element = page.locator("fast-button"); + + control = element.locator(".control"); + + await page.goto(fixtureURL("button--button")); + }); + + test.afterAll(async () => { + await page.close(); + }); + + test("should set the `autofocus` attribute on the internal control", async () => { + await element.evaluate(node => { + node.toggleAttribute("autofocus"); + return new Promise(requestAnimationFrame); + }); + + await expect(control).toHaveBooleanAttribute("autofocus"); + + await element.evaluate(node => { + node.toggleAttribute("autofocus"); + return new Promise(requestAnimationFrame); + }); + + await expect(control).not.toHaveBooleanAttribute("autofocus"); + }); + + test("should set the `disabled` attribute on the internal control", async () => { + await element.evaluate(node => { + node.toggleAttribute("disabled"); + return new Promise(requestAnimationFrame); + }); + + await expect(control).toHaveBooleanAttribute("disabled"); + + await element.evaluate(node => { + node.toggleAttribute("disabled"); + return new Promise(requestAnimationFrame); + }); + + await expect(control).not.toHaveBooleanAttribute("disabled"); + }); + + test("should set the `formnovalidate` attribute on the internal control", async () => { + await element.evaluate(node => { + node.toggleAttribute("formnovalidate"); + return new Promise(requestAnimationFrame); + }); + + await expect(control).toHaveBooleanAttribute("formnovalidate"); + + await element.evaluate(node => { + node.toggleAttribute("formnovalidate"); + return new Promise(requestAnimationFrame); + }); + + await expect(control).not.toHaveBooleanAttribute("formnovalidate"); + }); + + test.describe("should set the attribute on the internal control", () => { + const attributes = { + formaction: "foo", + formenctype: "foo", + formmethod: "post", + formtarget: "_blank", + name: "foo", + type: "submit", + value: "reset", + ariaAtomic: "true", + ariaBusy: "false", + ariaControls: "testId", + ariaCurrent: "page", + ariaDescribedby: "testId", + ariaDetails: "testId", + ariaDisabled: "true", + ariaErrormessage: "test", + ariaExpanded: "true", + ariaFlowto: "testId", + ariaHaspopup: "true", + ariaHidden: "true", + ariaInvalid: "spelling", + ariaKeyshortcuts: "F4", + ariaLabel: "Foo label", + ariaLabelledby: "testId", + ariaLive: "polite", + ariaOwns: "testId", + ariaPressed: "true", + ariaRelevant: "removals", + ariaRoledescription: "slide", + }; + + test.beforeAll(async () => { + await page.goto( + fixtureURL("button", { + ...attributes, + // The `formId` on the element is set as the `form` attribute on the control + formId: "foo", + }) + ); + }); + + for (const [attribute, value] of Object.entries(attributes)) { + const attrToken = spinalCase(attribute); + test(`should set the \`${attrToken}\` attribute to \`${value}\``, async () => { + await expect(control).toHaveAttribute(attrToken, `${value}`); + }); + } - await expect(button).toHaveAttribute("aria-roledescription", "slide"); + test("should set the `form` attribute on the internal button when `formId` is provided", async () => { + await expect(control).toHaveAttribute("form", "foo"); + }); + }); }); test("of type `submit` should submit the parent form when clicked", async ({ diff --git a/packages/web-components/fast-foundation/src/checkbox/checkbox.pw.spec.ts b/packages/web-components/fast-foundation/src/checkbox/checkbox.pw.spec.ts index ecf16a39914..b566e6fb3c3 100644 --- a/packages/web-components/fast-foundation/src/checkbox/checkbox.pw.spec.ts +++ b/packages/web-components/fast-foundation/src/checkbox/checkbox.pw.spec.ts @@ -1,349 +1,296 @@ +import type { Locator, Page } from "@playwright/test"; import { expect, test } from "@playwright/test"; import { fixtureURL } from "../__test__/helpers.js"; import type { FASTCheckbox } from "./checkbox.js"; test.describe("Checkbox", () => { - test("should have a role of `checkbox`", async ({ page }) => { - await page.goto(fixtureURL("checkbox--checkbox")); + test.describe("States, attributes, and classes", () => { + test.describe.configure({ mode: "serial" }); - const element = page.locator("fast-checkbox"); + // Syncronous tests all run on the same page instance + let page: Page; + let element: Locator; - await expect(element).toHaveAttribute("role", "checkbox"); - }); - - test("should set the `aria-checked` attribute equal to the `checked` value", async ({ - page, - }) => { - await page.goto(fixtureURL("checkbox--checkbox")); + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); - const element = page.locator("fast-checkbox"); + element = page.locator("fast-checkbox"); - await element.click(); + await page.goto(fixtureURL("checkbox--checkbox")); + }); - await expect(element).toHaveAttribute("aria-checked", "true"); + test.afterAll(async () => { + await page.close(); + }); - await expect(element).toHaveClass("checked"); + test("should have a role of `checkbox`", async () => { + await expect(element).toHaveAttribute("role", "checkbox"); + }); - await element.click(); + test("should set a tabindex of 0 on the element", async () => { + await expect(element).toHaveJSProperty("tabIndex", 0); - await expect(element).toHaveAttribute("aria-checked", "false"); + await expect(element).toHaveAttribute("tabindex", "0"); + }); - await expect(element).not.toHaveClass("checked"); - }); + test("should set a default `aria-checked` value when `checked` is not defined", async () => { + await expect(element).not.toHaveBooleanAttribute("checked"); - test("should set a default `aria-checked` value when `checked` is not defined", async ({ - page, - }) => { - await page.goto(fixtureURL("checkbox--checkbox")); + await expect(element).toHaveAttribute("aria-checked", "false"); + }); - const element = page.locator("fast-checkbox"); + test("should set the `aria-checked` attribute equal to the `checked` property", async () => { + await element.evaluate((node: FASTCheckbox) => { + node.checked = true; + }); - await expect(element).toHaveAttribute("aria-checked", "false"); - }); + await expect(element).toHaveAttribute("aria-checked", "true"); - test("should set the `aria-required` attribute equal to the `required` value", async ({ - page, - }) => { - await page.goto(fixtureURL("checkbox--checkbox")); + await expect(element).toHaveClass("checked"); - const element = page.locator("fast-checkbox"); + await element.evaluate((node: FASTCheckbox) => { + node.checked = false; + }); - await expect(element).toHaveAttribute("aria-required", "false"); + await expect(element).toHaveAttribute("aria-checked", "false"); - await element.evaluate((node: FASTCheckbox) => { - node.required = true; + await expect(element).not.toHaveClass("checked"); }); - await expect(element).toHaveAttribute("aria-required", "true"); - }); - - test("should set the `aria-disabled` attribute equal to the `disabled` value", async ({ - page, - }) => { - await page.goto(fixtureURL("checkbox--checkbox")); + test("should NOT set a default `aria-required` value when `required` is not defined", async () => { + await expect(element).not.toHaveBooleanAttribute("required"); - const element = page.locator("fast-checkbox"); - - await expect(element).toHaveAttribute("aria-disabled", "false"); - - await element.evaluate((node: FASTCheckbox) => { - node.disabled = true; + await expect(element).toHaveAttribute("aria-required", "false"); }); - await expect(element).toHaveAttribute("aria-disabled", "true"); - }); - - test("should NOT set a default `aria-readonly` value when `readonly` is not defined", async ({ - page, - }) => { - await page.goto(fixtureURL("checkbox--checkbox")); - - const element = page.locator("fast-checkbox"); - - expect( - await element.evaluate(node => node.hasAttribute("aria-readonly")) - ).toBeFalsy(); - }); + test("should set the `aria-required` attribute equal to the `required` property", async () => { + await element.evaluate((node: FASTCheckbox) => { + node.required = true; + }); - test("should set the `aria-readonly` attribute equal to the `readonly` value", async ({ - page, - }) => { - await page.goto(fixtureURL("checkbox--checkbox")); + await expect(element).toHaveAttribute("aria-required", "true"); - const element = page.locator("fast-checkbox"); + await expect(element).toHaveClass(/required/); - await element.evaluate((node: FASTCheckbox) => { - node.readOnly = true; - }); + await element.evaluate((node: FASTCheckbox) => { + node.required = false; + }); - await expect(element).toHaveAttribute("aria-readonly", "true"); + await expect(element).toHaveAttribute("aria-required", "false"); - await element.evaluate((node: FASTCheckbox) => { - node.readOnly = false; + await expect(element).not.toHaveClass(/required/); }); - await expect(element).toHaveAttribute("aria-readonly", "false"); - }); - - test("should add a class of `readonly` when readonly is true", async ({ page }) => { - await page.goto(fixtureURL("checkbox--checkbox")); - - const element = page.locator("fast-checkbox"); + test("should set a default `aria-disabled` value when `disabled` is not defined", async () => { + await expect(element).not.toHaveBooleanAttribute("disabled"); - await element.evaluate((node: FASTCheckbox) => { - node.readOnly = true; + await expect(element).toHaveAttribute("aria-disabled", "false"); }); - await expect(element).toHaveClass("readonly"); - }); - - test("should set a tabindex of 0 on the element", async ({ page }) => { - await page.goto(fixtureURL("checkbox--checkbox")); + test("should set the `aria-disabled` attribute equal to the `disabled` property", async () => { + await element.evaluate((node: FASTCheckbox) => { + node.disabled = true; + }); - const element = page.locator("fast-checkbox"); + await expect(element).toHaveAttribute("aria-disabled", "true"); - await expect(element).toHaveJSProperty("tabIndex", 0); + await expect(element).toHaveClass(/disabled/); - await expect(element).toHaveAttribute("tabindex", "0"); - }); - - test("should NOT set a tabindex when disabled is `true`", async ({ page }) => { - await page.goto(fixtureURL("checkbox--checkbox", { disabled: true })); + await element.evaluate((node: FASTCheckbox) => { + node.disabled = false; + }); - const element = page.locator("fast-checkbox"); + await expect(element).toHaveAttribute("aria-disabled", "false"); - await element.evaluate((node: FASTCheckbox) => { - node.disabled = true; + await expect(element).not.toHaveClass(/disabled/); }); - await expect(element).not.toHaveJSProperty("tabIndex", 0); + test("should NOT set a tabindex when `disabled` is true", async () => { + await element.evaluate((node: FASTCheckbox) => { + node.disabled = true; + }); - await expect(element).not.toHaveAttribute("tabindex", "0"); - }); + await expect(element).not.toHaveJSProperty("tabIndex", 0); - test("should add a class of `indeterminate` when indeterminate is true", async ({ - page, - }) => { - await page.goto(fixtureURL("checkbox--checkbox", { indeterminate: true })); + await expect(element).not.toHaveAttribute("tabindex", "0"); + }); - const element = page.locator("fast-checkbox"); + test("should NOT set a default `aria-readonly` value when `readonly` is not defined", async () => { + await expect(element).not.toHaveBooleanAttribute("readonly"); - await element.evaluate((node: FASTCheckbox) => { - node.indeterminate = true; + await expect(element).not.hasAttribute("aria-readonly"); }); - await expect(element).toHaveClass("indeterminate"); - }); + test("should set the `aria-readonly` attribute equal to the `readonly` property", async () => { + await element.evaluate((node: FASTCheckbox) => { + node.readOnly = true; + }); - test("should set off `indeterminate` on `checked` change by user click", async ({ - page, - }) => { - await page.goto(fixtureURL("checkbox--checkbox", { indeterminate: true })); + await expect(element).toHaveAttribute("aria-readonly", "true"); - const element = page.locator("fast-checkbox"); + await element.evaluate((node: FASTCheckbox) => { + node.readOnly = false; + }); - await element.evaluate((node: FASTCheckbox) => { - node.indeterminate = true; + await expect(element).toHaveAttribute("aria-readonly", "false"); }); - await expect(element).toHaveJSProperty("indeterminate", true); - - await element.click(); - - await expect(element).toHaveJSProperty("indeterminate", false); - }); + test("should add a class of `readonly` when `readonly` is true", async () => { + await element.evaluate((node: FASTCheckbox) => { + node.readOnly = true; + }); - test("should set off `indeterminate` on `checked` change by user keypress", async ({ - page, - }) => { - await page.goto(fixtureURL("checkbox--checkbox", { indeterminate: true })); + await expect(element).toHaveClass(/readonly/); - const element = page.locator("fast-checkbox"); + await element.evaluate((node: FASTCheckbox) => { + node.readOnly = false; + }); - await element.evaluate((node: FASTCheckbox) => { - node.indeterminate = true; + await expect(element).not.toHaveClass(/readonly/); }); - await expect(element).toHaveJSProperty("indeterminate", true); - - await element.press(" "); + test("should add a class of `indeterminate` when indeterminate is true", async () => { + await element.evaluate((node: FASTCheckbox) => { + node.indeterminate = true; + }); - await expect(element).toHaveJSProperty("indeterminate", false); - }); + await expect(element).toHaveClass(/indeterminate/); - test("should initialize to the initial value if no value property is set", async ({ - page, - }) => { - await page.goto(fixtureURL("checkbox--checkbox")); + await element.evaluate((node: FASTCheckbox) => { + node.indeterminate = false; + }); - const element = page.locator("fast-checkbox"); + await expect(element).not.toHaveClass(/indeterminate/); + }); - const initialValue = await element.evaluate( - (node: FASTCheckbox) => node.initialValue - ); + test("should set off `indeterminate` on `checked` change by user click", async () => { + await page.reload(); - // Playwright doesn't yet see our components as input elements - await expect(element).toHaveJSProperty("value", initialValue); - }); + await element.evaluate((node: FASTCheckbox) => { + node.indeterminate = true; + }); - test("should initialize to the provided value attribute if set pre-connection", async ({ - page, - }) => { - const expectedValue = "foobar"; + await expect(element).toHaveJSProperty("indeterminate", true); - await page.goto(fixtureURL("checkbox--checkbox", { value: expectedValue })); + await element.click(); - const element = page.locator("fast-checkbox"); + await expect(element).toHaveJSProperty("indeterminate", false); + }); - const value = await element.evaluate((node: FASTCheckbox) => node.value); + test("should set off `indeterminate` on `checked` change by user keypress", async () => { + await page.reload(); - expect(value).toBe(expectedValue); - }); + await element.evaluate((node: FASTCheckbox) => { + node.indeterminate = true; + }); - test("should initialize to the provided value attribute if set post-connection", async ({ - page, - }) => { - await page.goto(fixtureURL("checkbox--checkbox")); + await expect(element).toHaveJSProperty("indeterminate", true); - const element = page.locator("fast-checkbox"); + await element.press(" "); - const expectedValue = "foobar"; + await expect(element).toHaveJSProperty("indeterminate", false); + }); - await element.evaluate((node: FASTCheckbox, expectedValue) => { - node.setAttribute("value", expectedValue); - }, expectedValue); + test("should add a class of `label` to the internal label when default slotted content exists", async () => { + const label = element.locator("label"); - await expect(element).toHaveJSProperty("value", expectedValue); - }); + await expect(label).toHaveClass(/label/); + }); - test("should initialize to the provided value property if set pre-connection", async ({ - page, - }) => { - const expectedValue = "foobar"; + test("should add classes of `label` and `label__hidden` to the internal label when default slotted content exists", async () => { + const label = element.locator("label"); - const value = await page.evaluate(expectedValue => { - const node = document.createElement("fast-checkbox") as FASTCheckbox; + await element.evaluate((node: FASTCheckbox) => { + while (node.firstChild) { + node.removeChild(node.firstChild); + } + }); - node.value = expectedValue; + await expect(label).toHaveClass(/label label__hidden/); + }); - return Promise.resolve(node.value); - }, expectedValue); + test("should initialize to the initial value if no value property is set", async () => { + const initialValue = await element.evaluate( + (node: FASTCheckbox) => node.initialValue + ); - expect(value).toBe(expectedValue); + // Playwright doesn't yet see our components as input elements + await expect(element).toHaveJSProperty("value", initialValue); + }); }); - test.describe("label", () => { - test("should add a class of `label` to the internal label when default slotted content exists", async ({ + test.describe("initial value", () => { + test("should initialize to the provided `value` attribute when set pre-connection", async ({ page, }) => { - await page.goto(fixtureURL("checkbox--checkbox")); + await page.goto(fixtureURL("checkbox--checkbox", { value: "foo" })); const element = page.locator("fast-checkbox"); - const label = element.locator("label"); + const value = await element.evaluate((node: FASTCheckbox) => node.value); - await expect(label).toHaveClass("label"); + expect(value).toBe("foo"); }); - test("should add classes of `label` and `label__hidden` to the internal label when default slotted content exists", async ({ + test("should initialize to the provided `value` attribute when set post-connection", async ({ page, }) => { await page.goto(fixtureURL("checkbox--checkbox")); const element = page.locator("fast-checkbox"); - const label = element.locator("label"); + const expectedValue = "foobar"; - await element.evaluate((node: FASTCheckbox) => { - while (node.firstChild) { - node.removeChild(node.firstChild); - } - }); + await element.evaluate((node: FASTCheckbox, expectedValue) => { + node.setAttribute("value", expectedValue); + }, expectedValue); - await expect(label).toHaveClass("label label__hidden"); + await expect(element).toHaveJSProperty("value", expectedValue); }); - }); - test.describe("events", () => { - test("should fire an event on click", async ({ page }) => { - await page.goto(fixtureURL("checkbox--checkbox")); + test("should initialize to the provided `value` property when set pre-connection", async ({ + page, + }) => { + const expectedValue = "foobar"; - const element = page.locator("fast-checkbox"); + const value = await page.evaluate(expectedValue => { + const node = document.createElement("fast-checkbox") as FASTCheckbox; - const [wasClicked] = await Promise.all([ - element.evaluate(node => { - return new Promise(resolve => { - node.addEventListener("click", () => resolve(true)); - }); - }), + node.value = expectedValue; - element.click(), - ]); + return Promise.resolve(node.value); + }, expectedValue); - await expect(wasClicked).toBe(true); + expect(value).toBe(expectedValue); }); + }); - test("should fire an event when spacebar is invoked", async ({ page }) => { - await page.goto(fixtureURL("checkbox--checkbox")); - - const element = page.locator("fast-checkbox"); + test.describe("when the `required` property is true", () => { + let page: Page; + let element: Locator; - const wasPressed = await element.evaluate(node => { - return new Promise(resolve => { - node.addEventListener("keydown", () => resolve(true)); + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); - node.dispatchEvent( - new KeyboardEvent("keydown", { - key: " ", - }) - ); - }); - }); + element = page.locator("fast-checkbox"); - await expect(wasPressed).toBe(true); - }); - }); - - test.describe("when required", () => { - test("should be invalid when unchecked", async ({ page }) => { await page.goto(fixtureURL("checkbox--checkbox-in-form", { required: true })); + }); - const element = page.locator("fast-checkbox"); + test.afterAll(async () => { + await page.close(); + }); + test("should be invalid when unchecked", async () => { expect( await element.evaluate((node: FASTCheckbox) => node.validity.valueMissing) ).toBe(true); }); - test("should be valid when checked", async ({ page }) => { - await page.goto( - fixtureURL("checkbox--checkbox-in-form", { - checked: true, - required: true, - }) - ); + test("should be valid when checked", async () => { + await element.click(); - const element = page.locator("fast-checkbox"); + await expect(element).toHaveJSProperty("checked", true); expect( await element.evaluate((node: FASTCheckbox) => node.validity.valueMissing) @@ -352,13 +299,25 @@ test.describe("Checkbox", () => { }); test.describe("whose parent form has its reset() method invoked", () => { - test("should set its checked property to false if the checked attribute is unset", async ({ - page, - }) => { + let page: Page; + let element: Locator; + let form: Locator; + + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); + + element = page.locator("fast-checkbox"); + + form = page.locator("form"); + await page.goto(fixtureURL("checkbox--checkbox-in-form", { required: true })); + }); - const element = page.locator("fast-checkbox"); + test.afterAll(async () => { + await page.close(); + }); + test("should set the `checked` property to false if the `checked` attribute is unset", async () => { await expect(element).toHaveJSProperty("checked", false); await element.evaluate((node: FASTCheckbox) => { @@ -367,24 +326,14 @@ test.describe("Checkbox", () => { await expect(element).toHaveJSProperty("checked", true); - const form = page.locator("form"); - await form.evaluate((node: HTMLFormElement) => { node.reset(); }); await expect(element).toHaveJSProperty("checked", false); }); - }); - - test.describe("whose parent form has its reset() method invoked", () => { - test("should set its checked property to true if the checked attribute is set", async ({ - page, - }) => { - await page.goto(fixtureURL("checkbox--checkbox-in-form", { required: true })); - - const element = page.locator("fast-checkbox"); + test("should set its checked property to true if the checked attribute is set", async () => { await expect(element).toHaveJSProperty("checked", false); await element.evaluate((node: FASTCheckbox) => { @@ -393,8 +342,6 @@ test.describe("Checkbox", () => { await expect(element).toHaveJSProperty("checked", true); - const form = page.locator("form"); - await form.evaluate((node: HTMLFormElement) => { node.reset(); }); @@ -402,14 +349,8 @@ test.describe("Checkbox", () => { await expect(element).toHaveJSProperty("checked", true); }); - test("should put the control into a clean state, where checked attribute modifications change the checked property prior to user or programmatic interaction", async ({ - page, - }) => { - await page.goto(fixtureURL("checkbox--checkbox-in-form", { required: true })); - - const element = page.locator("fast-checkbox"); - - const form = page.locator("form"); + test("should put the control into a clean state, where checked attribute modifications change the checked property prior to user or programmatic interaction", async () => { + page.goto(fixtureURL("checkbox--checkbox-in-form", { required: true })); await element.evaluate((node: FASTCheckbox) => { node.checked = true; diff --git a/packages/web-components/fast-foundation/src/combobox/combobox.pw.spec.ts b/packages/web-components/fast-foundation/src/combobox/combobox.pw.spec.ts index 0cfc4bda615..da823493359 100644 --- a/packages/web-components/fast-foundation/src/combobox/combobox.pw.spec.ts +++ b/packages/web-components/fast-foundation/src/combobox/combobox.pw.spec.ts @@ -1,69 +1,127 @@ import { expect, test } from "@playwright/test"; +import type { Locator, Page } from "@playwright/test"; import { fixtureURL } from "../__test__/helpers.js"; import type { FASTCombobox } from "./combobox.js"; test.describe("Combobox", () => { - test("should include a control with a role of `combobox`", async ({ page }) => { - await page.goto(fixtureURL("combobox--combobox")); - const element = page.locator("fast-combobox"); + test.describe("States, Attributes, and Properties", () => { + let page: Page; + let element: Locator; + let field: Locator; - expect( - await element.evaluate((node: FASTCombobox) => - node.control?.getAttribute("role") - ) - ).toBe("combobox"); - }); + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); - test("should set the `aria-disabled` attribute equal to the `disabled` value", async ({ - page, - }) => { - await page.goto( - fixtureURL("combobox--combobox", { - disabled: true, - }) - ); + element = page.locator("fast-combobox"); - const element = page.locator("fast-combobox"); + field = element.locator(`input[role="combobox"]`); - await expect(element).toHaveAttribute("aria-disabled", "true"); + await page.goto(fixtureURL("combobox--combobox")); + }); - await element.evaluate((node: FASTCombobox) => (node.disabled = false)); + test.afterAll(async () => { + await page.close(); + }); - await expect(element).toHaveAttribute("aria-disabled", "false"); - }); + test('should include a control with a `role` attribute equal to "combobox"', async () => { + await expect(field).toHaveCount(1); + }); - test("should have a tabindex of 0 when `disabled` is not defined", async ({ - page, - }) => { - await page.goto(fixtureURL("combobox--combobox")); + test("should set the `aria-disabled` attribute equal to the `disabled` property", async () => { + await expect(element).toHaveAttribute("aria-disabled", "false"); - const element = page.locator("fast-combobox"); + await element.evaluate((node: FASTCombobox) => { + node.disabled = true; + }); - await expect(element).toHaveAttribute("tabindex", "0"); - }); + await expect(element).toHaveAttribute("aria-disabled", "true"); - test("should NOT have a tabindex when `disabled` is true", async ({ page }) => { - await page.goto( - fixtureURL("combobox--combobox", { - disabled: true, - }) - ); + await element.evaluate((node: FASTCombobox) => { + node.disabled = false; + }); - const element = page.locator("fast-combobox"); + await expect(element).toHaveAttribute("aria-disabled", "false"); + }); - await expect(element).toHaveAttribute("tabindex", ""); - }); + test("should set and remove the `tabindex` attribute based on the value of the `disabled` property", async () => { + await element.evaluate((node: FASTCombobox) => { + node.disabled = true; + }); - test("should NOT set its value to the first available option", async ({ page }) => { - await page.goto(fixtureURL("combobox--combobox")); + await expect(element).not.hasAttribute("tabindex"); - const element = page.locator("fast-combobox"); + await element.evaluate((node: FASTCombobox) => { + node.disabled = false; + }); + + await expect(element).toHaveAttribute("tabindex", "0"); + }); + + test("should NOT set the `value` property to the first available option", async () => { + await expect(element).toHaveJSProperty("value", ""); + + await expect(field).toHaveValue(""); + }); + + test("should set the `placeholder` attribute on the internal control equal to the `placeholder` attribute", async () => { + await element.evaluate((node: FASTCombobox) => { + node.placeholder = "placeholder text"; + }); + + await expect(element).toHaveJSProperty("placeholder", "placeholder text"); + + await expect(field).toHaveAttribute("placeholder", "placeholder text"); + }); + + test("should set the control's `aria-controls` attribute to the ID of the internal listbox element while open", async () => { + const listbox = element.locator(".listbox"); + + const listboxId = (await listbox.getAttribute("id")) as string; + + await expect(field).toHaveAttribute("aria-controls", ""); + + await element.evaluate((node: FASTCombobox) => (node.open = true)); + + await expect(field).toHaveAttribute("aria-controls", listboxId); + + await element.evaluate((node: FASTCombobox) => (node.open = false)); + + await expect(field).toHaveAttribute("aria-controls", ""); + }); + + test("should set the control's `aria-activedescendant` property to the ID of the currently selected option while open", async () => { + const options = element.locator("fast-option"); + + await expect(field).not.hasAttribute("aria-activedescendant"); + + await element.evaluate((node: FASTCombobox) => { + node.open = true; + }); + + await expect(element).toHaveAttribute("aria-activedescendant", ""); + + const optionsCount = await options.count(); + + for (let i = 0; i < optionsCount; i++) { + const option = options.nth(i); + + await element.evaluate((node: FASTCombobox) => { + node.selectNextOption(); + }); - const input = element.locator(`input[role="combobox"]`); + const optionId = await option.evaluate(node => node.id); - await expect(element).toHaveJSProperty("value", ""); + await expect(field).toHaveAttribute("aria-activedescendant", optionId); + } - await expect(input).toHaveValue(""); + await element.evaluate((node: FASTCombobox) => { + node.value = "other"; + }); + + await expect(field).hasAttribute("aria-activedescendant"); + + await expect(element).toHaveAttribute("aria-activedescendant", ""); + }); }); test("should set its value to the first option with the `selected` attribute present", async ({ @@ -95,7 +153,7 @@ test.describe("Combobox", () => { await expect(element).toHaveJSProperty("value", "two"); }); - test("should return the same value when the value property is set before connect", async ({ + test("should return the same value when the `value` property is set before connecting", async ({ page, }) => { await page.goto(fixtureURL("debug--blank")); @@ -111,7 +169,7 @@ test.describe("Combobox", () => { expect(await element.evaluate((node: FASTCombobox) => node.value)).toBe("test"); }); - test("should return the same value when the value property is set after connect", async ({ + test("should return the same value when the `value` property is set after connecting", async ({ page, }) => { await page.goto(fixtureURL("debug--blank")); @@ -238,24 +296,6 @@ test.describe("Combobox", () => { } ); - test("should set the `placeholder` attribute on the internal control equal to the value provided", async ({ - page, - }) => { - await page.goto( - fixtureURL("combobox--combobox", { - placeholder: "test", - }) - ); - - const element = page.locator("fast-combobox"); - - const input = element.locator(`input[role="combobox"]`); - - await expect(element).toHaveJSProperty("placeholder", "test"); - - await expect(input).toHaveAttribute("placeholder", "test"); - }); - test.describe("when the owning form's reset() function is invoked", () => { test("should reset the value property to its initial value", async ({ page }) => { await page.goto( @@ -330,68 +370,4 @@ test.describe("Combobox", () => { await expect(element).toBeFocused(); }); - - test("should set the control's `aria-activedescendant` property to the ID of the currently selected option while open", async ({ - page, - }) => { - await page.goto(fixtureURL("combobox--combobox")); - - const element = page.locator("fast-combobox"); - - const options = element.locator("fast-option"); - - const control = element.locator(`input[role="combobox"]`); - - expect(await control.getAttribute("aria-activedescendant")).toBeNull(); - - await element.evaluate((node: FASTCombobox) => { - node.open = true; - }); - - await expect(element).toHaveAttribute("aria-activedescendant", ""); - - const optionsCount = await options.count(); - - for (let i = 0; i < optionsCount; i++) { - const option = options.nth(i); - - await element.evaluate((node: FASTCombobox) => { - node.selectNextOption(); - }); - - const optionId = await option.evaluate(node => node.id); - - await expect(control).toHaveAttribute("aria-activedescendant", optionId); - } - - await element.evaluate((node: FASTCombobox) => { - node.value = "other"; - }); - - await expect(element).toHaveAttribute("aria-activedescendant", ""); - }); - - test("should set the control's `aria-controls` attribute to the ID of the internal listbox element while open", async ({ - page, - }) => { - await page.goto(fixtureURL("combobox--combobox")); - - const element = page.locator("fast-combobox"); - - const control = element.locator(`input[role="combobox"]`); - - const listboxId = (await element - .locator(".listbox") - .getAttribute("id")) as string; - - await expect(control).toHaveAttribute("aria-controls", ""); - - await element.evaluate((node: FASTCombobox) => (node.open = true)); - - await expect(control).toHaveAttribute("aria-controls", listboxId); - - await element.evaluate((node: FASTCombobox) => (node.open = false)); - - await expect(control).toHaveAttribute("aria-controls", ""); - }); }); diff --git a/packages/web-components/fast-foundation/src/data-grid/data-grid-cell.pw.spec.ts b/packages/web-components/fast-foundation/src/data-grid/data-grid-cell.pw.spec.ts index 2af89f1a838..56fa9c71af2 100644 --- a/packages/web-components/fast-foundation/src/data-grid/data-grid-cell.pw.spec.ts +++ b/packages/web-components/fast-foundation/src/data-grid/data-grid-cell.pw.spec.ts @@ -1,87 +1,75 @@ +import type { Locator, Page } from "@playwright/test"; import { expect, test } from "@playwright/test"; import { fixtureURL } from "../__test__/helpers.js"; import type { FASTDataGridCell } from "./data-grid-cell.js"; import { DataGridCellTypes } from "./data-grid.options.js"; test.describe("Data grid cell", () => { - test("should set role to 'gridcell' by default", async ({ page }) => { - await page.goto(fixtureURL("data-grid-data-grid-cell--data-grid-cell")); - const element = page.locator("fast-data-grid-cell"); + test.describe("States, Attributes, and Properties", () => { + let page: Page; + let element: Locator; - await expect(element).toHaveAttribute("role", "gridcell"); - }); + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); - test("should set role to 'columnheader' when cell-type is 'columnheader'", async ({ - page, - }) => { - await page.goto(fixtureURL("data-grid-data-grid-cell--data-grid-cell")); + element = page.locator("fast-data-grid-cell"); - const element = page.locator("fast-data-grid-cell"); - - await element.evaluate(node => { - node.setAttribute("cell-type", "columnheader"); + await page.goto(fixtureURL("data-grid-data-grid-cell--data-grid-cell")); }); - await expect(element).toHaveAttribute("role", "columnheader"); - }); - - test("should set role to 'rowheader' when cell-type is 'rowheader'", async ({ - page, - }) => { - await page.goto(fixtureURL("data-grid-data-grid-cell--data-grid-cell")); - - const element = page.locator("fast-data-grid-cell"); - - await element.evaluate(node => node.setAttribute("cell-type", "rowheader")); - - await expect(element).toHaveAttribute("role", "rowheader"); - }); - - test("should apply 'column-header' css class when cell-type is 'columnheader'", async ({ - page, - }) => { - await page.goto(fixtureURL("data-grid-data-grid-cell--data-grid-cell")); + test.afterAll(async () => { + await page.close(); + }); - const element = page.locator("fast-data-grid-cell"); + test('should set the `role` attribute to "gridcell" by default', async () => { + await expect(element).toHaveAttribute("role", "gridcell"); + }); - await element.evaluate(node => { - node.setAttribute("cell-type", "columnheader"); + test("should have a tabIndex of -1 by default", async () => { + await expect(element).toHaveAttribute("tabindex", "-1"); }); - await expect(element).toHaveClass("column-header"); - }); + test('should set the `role` attribute to "columnheader" when the `cell-type` attribute is "columnheader"', async () => { + await element.evaluate(node => { + node.setAttribute("cell-type", "columnheader"); + }); - test("should apply 'row-header' css class when cell-type is 'rowheader'", async ({ - page, - }) => { - await page.goto(fixtureURL("data-grid-data-grid-cell--data-grid-cell")); + await expect(element).toHaveAttribute("role", "columnheader"); + }); - const element = page.locator("fast-data-grid-cell"); + test('should add the "column-header" class when the `cell-type` attribute is "columnheader"', async () => { + await element.evaluate(node => { + node.setAttribute("cell-type", "columnheader"); + }); - await element.evaluate(node => { - node.setAttribute("cell-type", "rowheader"); + await expect(element).toHaveClass(/column-header/); }); - await expect(element).toHaveClass("row-header"); - }); + test('should set the `role` attribute to "rowheader" when the `cell-type` attribute is "rowheader"', async () => { + await element.evaluate(node => { + node.setAttribute("cell-type", "rowheader"); + }); - test("should have a tabIndex of -1 by default", async ({ page }) => { - await page.goto(fixtureURL("data-grid-data-grid-cell--data-grid-cell")); + await expect(element).toHaveAttribute("role", "rowheader"); + }); - const element = page.locator("fast-data-grid-cell"); + test('should add the "row-header" class when the `cell-type` attribute is "rowheader"', async () => { + await element.evaluate(node => { + node.setAttribute("cell-type", "rowheader"); + }); - await expect(element).toHaveAttribute("tabindex", "-1"); - }); + await expect(element).toHaveClass(/row-header/); + }); - test("should set css grid-column style to match attribute", async ({ page }) => { - await page.goto( - fixtureURL("data-grid-data-grid-cell--data-grid-cell", { gridColumn: "2" }) - ); + test("should set the `grid-column` CSS property to match the `grid-column` attribute", async () => { + await element.evaluate(node => { + node.setAttribute("grid-column", "2"); + }); - const element = page.locator("fast-data-grid-cell"); + await expect(element).toHaveCSS("grid-column-start", "2"); - await expect(element).toHaveCSS("grid-column-start", "2"); - await expect(element).toHaveCSS("grid-column-end", "auto"); + await expect(element).toHaveCSS("grid-column-end", "auto"); + }); }); test("should not render data if no columndefinition provided", async ({ page }) => { diff --git a/packages/web-components/fast-foundation/src/data-grid/data-grid-row.pw.spec.ts b/packages/web-components/fast-foundation/src/data-grid/data-grid-row.pw.spec.ts index 138377b53d8..3a3a7e6546d 100644 --- a/packages/web-components/fast-foundation/src/data-grid/data-grid-row.pw.spec.ts +++ b/packages/web-components/fast-foundation/src/data-grid/data-grid-row.pw.spec.ts @@ -1,3 +1,4 @@ +import type { Locator, Page } from "@playwright/test"; import { expect, test } from "@playwright/test"; import { fixtureURL } from "../__test__/helpers.js"; import type { FASTDataGridRow } from "./data-grid-row.js"; @@ -6,61 +7,125 @@ test.describe("DataGridRow", () => { const cellQueryString = '[role="cell"], [role="gridcell"], [role="columnheader"], [role="rowheader"]'; - test("should set role to 'row'", async ({ page }) => { - await page.goto(fixtureURL("data-grid-data-grid-row--data-grid-row")); + test.describe("States, Attributes, and Properties", () => { + let page: Page; + let element: Locator; - const element = page.locator("fast-data-grid-row"); + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); - await expect(element).toHaveAttribute("role", "row"); - }); + element = page.locator("fast-data-grid-row"); - test("should apply 'header' css class when row-type is 'header'", async ({ - page, - }) => { - await page.goto( - fixtureURL("data-grid-data-grid-row--data-grid-row", { - rowType: "header", - }) - ); + await page.goto(fixtureURL("data-grid-data-grid-row--data-grid-row")); + }); - const element = page.locator("fast-data-grid-row"); + test.afterAll(async () => { + await page.close(); + }); - await expect(element).toHaveAttribute("class", "header"); - }); + test('should set the `role` attribute to "row" by default', async () => { + await expect(element).toHaveAttribute("role", "row"); + }); - test("should apply 'sticky-header' css class when row-type is 'sticky-header'", async ({ - page, - }) => { - await page.goto( - fixtureURL("data-grid-data-grid-row--data-grid-row", { - rowType: "sticky-header", - }) - ); + test('should add the "header" class when the `row-type` attribute is "header"', async () => { + await element.evaluate(node => { + node.setAttribute("row-type", "header"); + }); - const element = page.locator("fast-data-grid-row"); + await expect(element).toHaveClass(/header/); - await expect(element).toHaveAttribute("class", "sticky-header"); - }); + await element.evaluate(node => { + node.removeAttribute("row-type"); + }); - test("should set `grid-template-columns` style to match attribute", async ({ - page, - }) => { - await page.goto( - fixtureURL("data-grid-data-grid-row--data-grid-row", { - gridTemplateColumns: "100px+200px", - }) - ); + await expect(element).not.toHaveClass(/header/); + }); - const element = page.locator("fast-data-grid-row"); + test('should apply "sticky-header" class when the `row-type` attribute is "sticky-header"', async () => { + await element.evaluate(node => { + node.setAttribute("row-type", "sticky-header"); + }); - await expect(element).toHaveAttribute( - "style", - "grid-template-columns: 100px 200px;" - ); + await expect(element).toHaveClass(/sticky-header/); + + await element.evaluate(node => { + node.removeAttribute("row-type"); + }); + + await expect(element).not.toHaveClass(/sticky-header/); + }); + + test("should set `grid-template-columns` style to match attribute", async ({ + page, + }) => { + await page.goto( + fixtureURL("data-grid-data-grid-row--data-grid-row", { + gridTemplateColumns: "100px+200px", + }) + ); + + const element = page.locator("fast-data-grid-row"); + + await expect(element).toHaveAttribute( + "style", + "grid-template-columns: 100px 200px;" + ); + }); + + test("should fire an event when a child cell is focused", async () => { + const cell = page.locator(cellQueryString).first(); + + const [wasFocused] = await Promise.all([ + element.evaluate(node => { + return new Promise(resolve => { + node.addEventListener("row-focused", () => { + resolve(true); + }); + }); + }), + cell.evaluate(node => { + node.focus(); + }), + ]); + + expect(wasFocused).toBeTruthy(); + }); + + test("should move focus with left/right arrow key strokes", async () => { + await element.locator(cellQueryString).first().focus(); + + await expect(element).toHaveJSProperty("focusColumnIndex", 0); + + await page.keyboard.press("ArrowRight"); + + await expect(element).toHaveJSProperty("focusColumnIndex", 1); + + await page.keyboard.press("ArrowLeft"); + + await expect(element).toHaveJSProperty("focusColumnIndex", 0); + }); + + test("should move focus to the start/end of the row with home/end keystrokes", async () => { + await element.locator(cellQueryString).first().focus(); + + await expect(element).toHaveJSProperty("focusColumnIndex", 0); + + await page.keyboard.press("End"); + + await expect(element).toHaveJSProperty("focusColumnIndex", 1); + + await page.keyboard.press("Home"); + + await expect(element).toHaveJSProperty("focusColumnIndex", 0); + }); }); test("should render no cells if provided no column definitions", async ({ page }) => { - await page.goto(fixtureURL()); + await page.goto( + fixtureURL("data-grid-data-grid-row--data-grid-row", { + columnDefinitions: "!undefined", + }) + ); await page.evaluate(() => { const node = document.createElement("fast-data-grid-row"); @@ -77,7 +142,7 @@ test.describe("DataGridRow", () => { test("should render as many column header cells as specified in column definitions", async ({ page, }) => { - await page.goto(fixtureURL()); + await page.goto(fixtureURL("debug--blank")); const element = page.locator("fast-data-grid-row"); @@ -100,62 +165,4 @@ test.describe("DataGridRow", () => { await expect(cells).toHaveCount(2); }); - - test("should fire an event when a child cell is focused", async ({ page }) => { - await page.goto(fixtureURL("data-grid-data-grid-row--data-grid-row")); - - const element = page.locator("fast-data-grid-row"); - - await expect( - await element.evaluate((node: FASTDataGridRow) => { - let wasInvoked = false; - - node.addEventListener("row-focused", () => { - wasInvoked = true; - }); - - node.cellElements[0].focus(); - - return Promise.resolve(wasInvoked); - }) - ).toBe(true); - }); - - test("should move focus with left/right arrow key strokes", async ({ page }) => { - await page.goto(fixtureURL("data-grid-data-grid-row--data-grid-row")); - - const element = page.locator("fast-data-grid-row"); - - await element.locator(cellQueryString).first().focus(); - - await expect(element).toHaveJSProperty("focusColumnIndex", 0); - - await page.keyboard.press("ArrowRight"); - - await expect(element).toHaveJSProperty("focusColumnIndex", 1); - - await page.keyboard.press("ArrowLeft"); - - await expect(element).toHaveJSProperty("focusColumnIndex", 0); - }); - - test("should move focus to the start/end of the row with home/end keystrokes", async ({ - page, - }) => { - await page.goto(fixtureURL("data-grid-data-grid-row--data-grid-row")); - - const element = page.locator("fast-data-grid-row"); - - await element.locator(cellQueryString).first().focus(); - - await expect(element).toHaveJSProperty("focusColumnIndex", 0); - - await page.keyboard.press("End"); - - await expect(element).toHaveJSProperty("focusColumnIndex", 1); - - await page.keyboard.press("Home"); - - await expect(element).toHaveJSProperty("focusColumnIndex", 0); - }); }); diff --git a/packages/web-components/fast-foundation/src/data-grid/data-grid-row.template.ts b/packages/web-components/fast-foundation/src/data-grid/data-grid-row.template.ts index d6c713d503f..a1ae7eb0f48 100644 --- a/packages/web-components/fast-foundation/src/data-grid/data-grid-row.template.ts +++ b/packages/web-components/fast-foundation/src/data-grid/data-grid-row.template.ts @@ -57,7 +57,8 @@ export function dataGridRowTemplate( return html`