From faaca6c9590a51dd93a2bf44c551b6b153caf9f7 Mon Sep 17 00:00:00 2001 From: SJ Date: Wed, 4 Dec 2024 23:06:04 -0500 Subject: [PATCH] Added basic Field and Fieldset implementation with disabled context (#1) --- .github/workflows/web.yaml | 7 +- src/lib/button/Button.svelte | 6 +- src/lib/checkbox/Checkbox.svelte | 6 +- src/lib/field/Field.svelte | 57 ++++++++++ src/lib/field/field.dom.test.ts | 86 +++++++++++++++ src/lib/fieldset/Fieldset.svelte | 57 ++++++++++ src/lib/fieldset/fieldset.dom.test.ts | 150 ++++++++++++++++++++++++++ src/lib/input/Input.svelte | 16 +-- src/lib/switch/Switch.svelte | 6 +- 9 files changed, 371 insertions(+), 20 deletions(-) create mode 100644 src/lib/field/Field.svelte create mode 100644 src/lib/field/field.dom.test.ts create mode 100644 src/lib/fieldset/Fieldset.svelte create mode 100644 src/lib/fieldset/fieldset.dom.test.ts diff --git a/.github/workflows/web.yaml b/.github/workflows/web.yaml index 39d32f5..37bfb59 100644 --- a/.github/workflows/web.yaml +++ b/.github/workflows/web.yaml @@ -7,8 +7,6 @@ on: - ".github/workflows/web.yaml" pull_request: branches: ["main"] - paths: - - ".github/workflows/web.yaml" workflow_dispatch: env: @@ -54,7 +52,7 @@ jobs: # ${{ runner.os }}-pnpm-store- - name: Install - run: pnpm install + run: pnpm install --frozen-lockfile # - name: Lint # run: pnpm lint @@ -64,3 +62,6 @@ jobs: - name: Test run: pnpm test:unit + + - name: Build + run: pnpm build diff --git a/src/lib/button/Button.svelte b/src/lib/button/Button.svelte index 2d18d57..0598355 100644 --- a/src/lib/button/Button.svelte +++ b/src/lib/button/Button.svelte @@ -72,8 +72,8 @@ {@render children?.({ active, autofocus, disabled, focus, hover })} {:else} - {@const Component = as} - + {@const AsComponent = as} + {@render children?.(snippetProps)} - + {/if} diff --git a/src/lib/checkbox/Checkbox.svelte b/src/lib/checkbox/Checkbox.svelte index 457eb13..f8c6112 100644 --- a/src/lib/checkbox/Checkbox.svelte +++ b/src/lib/checkbox/Checkbox.svelte @@ -90,8 +90,8 @@ {@render children?.(snippetProps)} {:else} - {@const Component = as} - + {@const AsComponent = as} + {@render children?.(snippetProps)} - + {/if} diff --git a/src/lib/field/Field.svelte b/src/lib/field/Field.svelte new file mode 100644 index 0000000..b57a5bf --- /dev/null +++ b/src/lib/field/Field.svelte @@ -0,0 +1,57 @@ + + +{#if typeof as === "string"} + + {@render children?.(snippetProps)} + +{:else} + {@const AsComponent = as} + + {@render children?.(snippetProps)} + +{/if} diff --git a/src/lib/field/field.dom.test.ts b/src/lib/field/field.dom.test.ts new file mode 100644 index 0000000..560df51 --- /dev/null +++ b/src/lib/field/field.dom.test.ts @@ -0,0 +1,86 @@ +import { render, screen } from "@testing-library/svelte"; +import type { SvelteComponent } from "svelte"; + +function sveltify(input: string): Promise { + throw new Error("TODO"); +} + +describe("Rendering", () => { + it("should render a `Field` component", async () => { + const component = await sveltify(` + + + + + `); + const { container } = render(component); + + expect(container.firstElementChild).not.toHaveAttribute("aria-disabled", "true"); + }); + + it.skip("should render a `Field` component with a render prop", async () => { + const component = await sveltify(` + + + + + `); + const { container } = render(component); + + // let { container } = render( + // + // {(slot) => { + // return ( + //
+ // + //
+ // ) + // }} + //
+ // ) + + expect(container.querySelector("[data-slot]")?.getAttribute("data-slot")).toEqual( + JSON.stringify({ disabled: false }), + ); + expect(container.firstChild).not.toHaveAttribute("aria-disabled", "true"); + }); + + it("should add `aria-disabled` when a `Field` is disabled", async () => { + const component = await sveltify(` + + +
+
+ `); + const { container } = render(component); + + expect(container.firstElementChild).toHaveAttribute("aria-disabled", "true"); + }); + + it("should inherit the `disabled` state from a parent `Fieldset`", async () => { + const component = await sveltify(` + +
+ + + +
+ `); + const { container } = render(component); + + let fieldset = container.firstElementChild; + let field = fieldset?.firstElementChild; + + expect(fieldset).toHaveAttribute("disabled"); + expect(field).toHaveAttribute("aria-disabled", "true"); + }); +}); diff --git a/src/lib/fieldset/Fieldset.svelte b/src/lib/fieldset/Fieldset.svelte new file mode 100644 index 0000000..8c0669d --- /dev/null +++ b/src/lib/fieldset/Fieldset.svelte @@ -0,0 +1,57 @@ + + +{#if typeof as === "string"} + + {@render children?.(snippetProps)} + +{:else} + {@const AsComponent = as} + + {@render children?.(snippetProps)} + +{/if} diff --git a/src/lib/fieldset/fieldset.dom.test.ts b/src/lib/fieldset/fieldset.dom.test.ts new file mode 100644 index 0000000..7fe7e11 --- /dev/null +++ b/src/lib/fieldset/fieldset.dom.test.ts @@ -0,0 +1,150 @@ +import { render, screen } from "@testing-library/svelte"; +import type { SvelteComponent } from "svelte"; +import { + assertLinkedWithLabel, + assertNotLinkedWithLabel, + getControl, + getLabels, +} from "../../test-utils/accessibility-assertions"; + +function sveltify(input: string): Promise { + throw new Error("TODO"); +} + +describe("Rendering", () => { + it("should render a `Fieldset` component", async () => { + const component = await sveltify(` + +
+ +
+ `); + const { container } = render(component); + let fieldset = container.firstElementChild; + + expect(fieldset).toBeInstanceOf(HTMLFieldSetElement); + expect(fieldset).not.toHaveAttribute("role", "group"); + }); + + it("should render a `Fieldset` using a custom component", async () => { + const component = await sveltify(` + +
+ +
+ `); + const { container } = render(component); + let fieldset = container.firstElementChild; + + expect(fieldset).toBeInstanceOf(HTMLSpanElement); + expect(fieldset).toHaveAttribute("role", "group"); + }); + + it("should forward the `disabled` attribute when disabling the `Fieldset`", async () => { + const component = await sveltify(` + +
+ +
+ `); + const { container } = render(component); + let fieldset = container.firstElementChild; + + expect(fieldset).toHaveAttribute("disabled"); + }); + + it("should add an `aria-disabled` attribute when disabling the `Fieldset` when using another element via the `as` prop", async () => { + const component = await sveltify(` + +
+ +
+ `); + const { container } = render(component); + let fieldset = container.firstElementChild; + + expect(fieldset).toHaveAttribute("aria-disabled", "true"); + }); + + it("should make nested inputs disabled when the fieldset is disabled", async () => { + const component = await sveltify(` + +
+ +
+ `); + const { container } = render(component); + let fieldset = container.firstElementChild; + + expect(fieldset?.firstElementChild).toBeDisabled(); + }); + + it.skip("should link a `Fieldset` to a nested `Legend`", async () => { + const component = await sveltify(` + +
+ My Legend + +
+ `); + const { container } = render(component); + let fieldset = container.firstElementChild as HTMLElement; + + assertLinkedWithLabel(fieldset, getLabels()); + }); + + it.skip("should not link a `Label` inside a `Field` to the `Fieldset`", async () => { + const component = await sveltify(` + +
+ My Legend + + + + +
+ `); + render(component); + + let legend = screen.getByText("My Legend"); + let label = screen.getByText("My Label"); + + let fieldset = legend.parentElement; + let field = label.parentElement; + + let input = getControl(); + + // The fieldset should be linked with the legend + assertLinkedWithLabel(fieldset, legend); + + // The input/control should be linked with the label + assertLinkedWithLabel(input, label); + + // The fieldset should not be linked with the label + assertNotLinkedWithLabel(fieldset, label); + + // The input/control should not be linked with the legend + assertNotLinkedWithLabel(input, legend); + + // The field should not be linked with anything + assertNotLinkedWithLabel(field, legend); + assertNotLinkedWithLabel(field, label); + }); +}); diff --git a/src/lib/input/Input.svelte b/src/lib/input/Input.svelte index caf5968..2659038 100644 --- a/src/lib/input/Input.svelte +++ b/src/lib/input/Input.svelte @@ -46,6 +46,14 @@ // "aria-describedby": describedBy, }; + let snippetProps: SnippetProps = { + autofocus, + disabled, + focus: false, + hover: false, + invalid, + }; + // TODO: Utility function to create this let dataAttributes: DataAttributes = { "data-autofocus": autofocus, @@ -54,14 +62,6 @@ "data-hover": invalid, "data-invalid": invalid, }; - - let snippetProps: SnippetProps = { - autofocus, - disabled, - focus: false, - hover: false, - invalid, - }; diff --git a/src/lib/switch/Switch.svelte b/src/lib/switch/Switch.svelte index e6c44e3..cdd80ea 100644 --- a/src/lib/switch/Switch.svelte +++ b/src/lib/switch/Switch.svelte @@ -123,8 +123,8 @@ {@render children?.(snippetProps)} {:else} - {@const Component = as} - + {@const AsComponent = as} + {@render children?.(snippetProps)} - + {/if}