Add Label fixes and tests

Fixes #33
This commit is contained in:
Ryan Gossiaux
2022-01-17 22:13:05 -05:00
parent 6224278607
commit ac1f86ac15
2 changed files with 151 additions and 20 deletions

View File

@@ -1,7 +1,16 @@
<script lang="ts"> <script lang="ts">
import type { HTMLActionArray } from "$lib/hooks/use-actions";
import Render from "$lib/utils/Render.svelte";
import { useId } from "$lib/hooks/use-id"; import { useId } from "$lib/hooks/use-id";
import type { SupportedAs } from "$lib/internal/elements";
import { forwardEventsBuilder } from "$lib/internal/forwardEventsBuilder";
import { onMount } from "svelte"; import { onMount } from "svelte";
import { get_current_component } from "svelte/internal";
import { useLabelContext } from "./LabelProvider.svelte"; import { useLabelContext } from "./LabelProvider.svelte";
const forwardEvents = forwardEventsBuilder(get_current_component());
export let as: SupportedAs = "label";
export let use: HTMLActionArray = [];
const id = `headlessui-label-${useId()}`; const id = `headlessui-label-${useId()}`;
export let passive = false; export let passive = false;
let contextStore = useLabelContext(); let contextStore = useLabelContext();
@@ -13,22 +22,20 @@
onMount(() => $contextStore!.register(id)); onMount(() => $contextStore!.register(id));
let allProps = { ...$$restProps, ...$contextStore!.props, id } as any; let allProps: any = {};
$: allProps = { ...$$restProps, ...$contextStore!.props, id };
if (passive) delete allProps["onClick"]; if (passive) delete allProps["onClick"];
</script> </script>
<!-- svelte-ignore a11y-label-has-associated-control --> <!-- svelte-ignore a11y-label-has-associated-control -->
<label <Render
{...allProps} {...allProps}
on:blur name={"Label"}
on:click {as}
on:focus use={[...use, forwardEvents]}
on:keyup
on:keydown
on:keypress
on:click={(event) => { on:click={(event) => {
if (!passive) allProps["onClick"]?.(event); if (!passive) allProps["onClick"]?.(event);
}} }}
> >
<slot /> <slot />
</label> </Render>

View File

@@ -2,6 +2,9 @@ import { render } from "@testing-library/svelte";
import Label from "./Label.svelte"; import Label from "./Label.svelte";
import LabelProvider from "./LabelProvider.svelte"; import LabelProvider from "./LabelProvider.svelte";
import svelte from "svelte-inline-compile"; import svelte from "svelte-inline-compile";
import { suppressConsoleLogs } from "$lib/test-utils/suppress-console-logs";
import { writable, type Writable } from "svelte/store";
import { tick } from "svelte";
let mockId = 0; let mockId = 0;
jest.mock("../../hooks/use-id", () => { jest.mock("../../hooks/use-id", () => {
@@ -13,6 +16,19 @@ jest.mock("../../hooks/use-id", () => {
beforeEach(() => (mockId = 0)); beforeEach(() => (mockId = 0));
afterAll(() => jest.restoreAllMocks()); afterAll(() => jest.restoreAllMocks());
beforeEach(() => {
document.body.innerHTML = "";
});
it(
"should error when we are using a <Label /> without a parent <LabelProvider />",
suppressConsoleLogs(async () => {
expect(() => render(Label)).toThrowError(
`You used a <Label /> component, but it is not inside a relevant parent.`
);
})
);
it("should be possible to use a LabelProvider without using a Label", async () => { it("should be possible to use a LabelProvider without using a Label", async () => {
let { container } = render(svelte` let { container } = render(svelte`
<LabelProvider let:labelledby> <LabelProvider let:labelledby>
@@ -31,36 +47,144 @@ it("should be possible to use a LabelProvider without using a Label", async () =
it("should be possible to use a LabelProvider and a single Label, and have them linked", async () => { it("should be possible to use a LabelProvider and a single Label, and have them linked", async () => {
let { container } = render(svelte` let { container } = render(svelte`
<LabelProvider let:labelledby> <LabelProvider let:labelledby>
<Label>I am a label</Label>
<div aria-labelledby={labelledby}> <div aria-labelledby={labelledby}>
<Label>I am a label</Label>
<span>Contents</span> <span>Contents</span>
</div> </div>
</LabelProvider> </LabelProvider>
`); `);
expect(container.firstChild?.firstChild).toMatchInlineSnapshot(` expect(container.firstChild?.firstChild).toMatchInlineSnapshot(`
<div
aria-labelledby="headlessui-label-1"
>
<label <label
id="headlessui-label-1" id="headlessui-label-1"
> >
I am a label I am a label
</label> </label>
<span>
Contents
</span>
</div>
`); `);
}); });
it("should be possible to use a LabelProvider and multiple Label components, and have them linked", async () => { it("should be possible to use a LabelProvider and multiple Label components, and have them linked", async () => {
let { container } = render(svelte` let { container } = render(svelte`
<LabelProvider let:labelledby> <LabelProvider let:labelledby>
<Label>I am a label</Label>
<div aria-labelledby={labelledby}> <div aria-labelledby={labelledby}>
<Label>I am a label</Label>
<span>Contents</span> <span>Contents</span>
</div>
<Label>I am also a label</Label> <Label>I am also a label</Label>
</div>
</LabelProvider> </LabelProvider>
`); `);
expect(container.firstChild?.firstChild).toMatchInlineSnapshot(` expect(container.firstChild?.firstChild).toMatchInlineSnapshot(`
<div
aria-labelledby="headlessui-label-1 headlessui-label-2"
>
<label <label
id="headlessui-label-1" id="headlessui-label-1"
> >
I am a label I am a label
</label> </label>
<span>
Contents
</span>
<label
id="headlessui-label-2"
>
I am also a label
</label>
</div>
`);
});
it("should be possible to render a Label with an `as` prop", async () => {
let { container } = render(svelte`
<LabelProvider let:labelledby>
<div aria-labelledby={labelledby}>
<Label as="p">I am a label</Label>
<span>Contents</span>
</div>
</LabelProvider>
`);
expect(container.firstChild?.firstChild).toMatchInlineSnapshot(`
<div
aria-labelledby="headlessui-label-1"
>
<p
id="headlessui-label-1"
>
I am a label
</p>
<span>
Contents
</span>
</div>
`);
});
it("should be possible to change the props of a Label", async () => {
let classStore: Writable<string | null> = writable(null);
let { container } = render(svelte`
<LabelProvider let:labelledby>
<div aria-labelledby={labelledby}>
<Label class={$classStore}>I am a label</Label>
<span>Contents</span>
</div>
</LabelProvider>
`);
expect(container.firstChild?.firstChild).toMatchInlineSnapshot(`
<div
aria-labelledby="headlessui-label-1"
>
<label
id="headlessui-label-1"
>
I am a label
</label>
<span>
Contents
</span>
</div>
`);
classStore.set("test-class");
await tick();
expect(container.firstChild?.firstChild).toMatchInlineSnapshot(`
<div
aria-labelledby="headlessui-label-1"
>
<label
class="test-class"
id="headlessui-label-1"
>
I am a label
</label>
<span>
Contents
</span>
</div>
`); `);
}); });