Skip to content

Commit

Permalink
Ported common test scenarios to Svelte, and added a Textarea component (
Browse files Browse the repository at this point in the history
  • Loading branch information
sureshjoshi authored Dec 10, 2024
1 parent 07731a5 commit 9e7dfca
Show file tree
Hide file tree
Showing 11 changed files with 574 additions and 428 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ The target compatibility for the components below is based on [HeadlessUI React

### Forms

- [/] Button
- [ ] Button
- [ ] Checkbox
- [ ] Combobox
- [ ] Description
Expand Down
56 changes: 14 additions & 42 deletions src/lib/checkbox/checkbox.dom.test.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,26 @@
import { render, screen } from "@testing-library/svelte";
import Checkbox from "./Checkbox.svelte";
import { getCheckbox } from "../../test-utils/accessibility-assertions";
import { click } from "../../test-utils/interactions";
import {
commonControlScenarios,
commonFormScenarios,
commonRenderingScenarios,
} from "../../test-utils/scenarios.dom";
import { render, screen } from "@testing-library/svelte";
import type { SvelteComponent } from "svelte";

function sveltify(input: string): Promise<typeof SvelteComponent> {
throw new Error("TODO");
}

// TODO: Manually unrolled test-utils/scenarios.ts commonRenderingScenarios

describe("Rendering", () => {
it("should render a checkbox", async () => {
const component = await sveltify(`
<script>
import Checkbox from "$lib/checkbox/Checkbox.svelte";
</script>
<Checkbox />
`);
render(component);

expect(getCheckbox()).toBeInTheDocument();
});

it("should have an `id` attached", async () => {
const component = await sveltify(`
<script>
import Checkbox from "$lib/checkbox/Checkbox.svelte";
</script>
<Checkbox />
`);
render(component);

expect(getCheckbox()).toHaveAttribute("id");
});

it("should be possible to override the `id`", async () => {
const component = await sveltify(`
<script>
import Checkbox from "$lib/checkbox/Checkbox.svelte";
</script>
<Checkbox id="foo" />
`);
render(component);

expect(getCheckbox()).toHaveAttribute("id", "foo");
});
commonRenderingScenarios(Checkbox, { getElement: getCheckbox });
commonControlScenarios(Checkbox);
commonFormScenarios(Checkbox, {
async performUserInteraction(control) {
await click(control);
},
});

describe.skip("commonControlScenarios", () => {});
describe.skip("commonFormScenarios", () => {});

// describe.each([
// [
// 'Uncontrolled',
Expand Down
1 change: 1 addition & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export { default as Input } from "./input/Input.svelte";
export { default as Label } from "./label/Label.svelte";
export { default as Legend } from "./legend/Legend.svelte";
export { default as Switch } from "./switch/Switch.svelte";
export { default as Textarea } from "./textarea/Textarea.svelte";
63 changes: 15 additions & 48 deletions src/lib/input/input.dom.test.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,17 @@
import { render, screen } from "@testing-library/svelte";
import Input from "./Input.svelte";
import { getInput } from "../../test-utils/accessibility-assertions";
import type { SvelteComponent } from "svelte";

function sveltify(input: string): Promise<typeof SvelteComponent> {
throw new Error("TODO");
}

// TODO: Manually unrolled test-utils/scenarios.ts commonRenderingScenarios

describe("Rendering", () => {
it("should render an input", async () => {
const component = await sveltify(`
<script>
import Input from "$lib/input/Input.svelte";
</script>
<Input />
`);
render(component);

expect(getInput()).toBeInTheDocument();
});

it("should have an `id` attached", async () => {
const component = await sveltify(`
<script>
import Input from "$lib/input/Input.svelte";
</script>
<Input />
`);
render(component);

expect(getInput()).toHaveAttribute("id");
});

it("should be possible to override the `id`", async () => {
const component = await sveltify(`
<script>
import Input from "$lib/input/Input.svelte";
</script>
<Input id="foo" />
`);
render(component);

expect(getInput()).toHaveAttribute("id", "foo");
});
import { focus, type, word } from "../../test-utils/interactions";
import {
commonControlScenarios,
commonFormScenarios,
commonRenderingScenarios,
} from "../../test-utils/scenarios.dom";

commonRenderingScenarios(Input, { getElement: getInput });
commonControlScenarios(Input);
commonFormScenarios(Input, {
async performUserInteraction(input) {
await focus(input);
await type(word("alice"));
},
});

describe.skip("commonControlScenarios", () => {});
describe.skip("commonFormScenarios", () => {});
80 changes: 80 additions & 0 deletions src/lib/textarea/Textarea.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<script lang="ts">
import { type Component, type Snippet } from "svelte";
import { useId } from "$lib/internal/hooks/use-id";
import { useLabelledBy } from "$lib/label/LabelProvider.svelte";
import { useDisabled } from "$lib/internal/DisabledProvider.svelte";
type Props = {
/** The element or component the input should render as. */
as?: string | Component;
/** Whether or not the input should receive focus when first rendered. */
autofocus?: boolean;
/** Whether or not the input is disabled. */
disabled?: boolean;
/** Whether or not the input is invalid. */
invalid?: boolean;
children?: Snippet<[SnippetProps]>;
};
type SnippetProps = {
/** Whether or not the autofocus prop was set to true. */
autofocus?: boolean;
/** Whether or not the input is invalid. */
invalid?: boolean;
/** Whether or not the input is disabled. */
disabled?: boolean;
/** Whether or not the input is focused. */
focus?: boolean;
/** Whether or not the input is hovered. */
hover?: boolean;
};
let {
id = `headlessui-textarea-${useId()}`,
as = "textarea",
autofocus = false,
disabled = useDisabled() || false,
invalid = false,
children,
...theirProps
}: Props & Record<string, any> = $props();
let labelledBy = $derived(useLabelledBy());
let ourProps = $derived({
id,
autofocus,
disabled,
"aria-invalid": invalid, // ? "" : undefined,
"aria-labelledby": labelledBy?.[0],
// "aria-describedby": describedBy,
});
let snippetProps: SnippetProps = $derived({
autofocus,
disabled,
focus: false,
hover: false,
invalid,
});
// TODO: Utility function to create this
let dataAttributes: DataAttributes<SnippetProps> = $derived({
"data-autofocus": autofocus || undefined,
"data-disabled": disabled || undefined,
"data-focus": invalid || undefined,
"data-hover": invalid || undefined,
"data-invalid": invalid || undefined,
});
</script>

{#if typeof as === "string"}
<svelte:element this={as} {...theirProps} {...ourProps} {...dataAttributes}>
{@render children?.(snippetProps)}
</svelte:element>
{:else}
{@const AsComponent = as}
<AsComponent {...theirProps} {...ourProps} {...dataAttributes}>
{@render children?.(snippetProps)}
</AsComponent>
{/if}
17 changes: 17 additions & 0 deletions src/lib/textarea/textarea.dom.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Textarea from "./Textarea.svelte";
import { getTextarea } from "../../test-utils/accessibility-assertions";
import { focus, type, word } from "../../test-utils/interactions";
import {
commonControlScenarios,
commonFormScenarios,
commonRenderingScenarios,
} from "../../test-utils/scenarios.dom";

commonRenderingScenarios(Textarea, { getElement: getTextarea });
commonControlScenarios(Textarea);
commonFormScenarios(Textarea, {
async performUserInteraction(control) {
await focus(control);
await type(word("alice"));
},
});
3 changes: 1 addition & 2 deletions src/routes/(main)/nav.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script lang="ts">
import { page } from "$app/stores";
const components = ["button", "checkbox", "fieldset", "input", "switch"];
const components = ["button", "checkbox", "fieldset", "input", "switch", "textarea"];
</script>

<nav>
Expand Down
16 changes: 16 additions & 0 deletions src/routes/examples/textarea/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script lang="ts">
import Field from "$lib/field/Field.svelte";
import Label from "$lib/label/Label.svelte";
import Textarea from "$lib/textarea/Textarea.svelte";
</script>

<div class="w-full max-w-md px-4">
<Field>
<Label class="text-sm/6 font-medium text-white">Description</Label>
<!-- <Description class="text-sm/6 text-white/50">This will be shown under the product title.</Description> -->
<Textarea
class="mt-3 block w-full resize-none rounded-lg border-none bg-white/5 py-1.5 px-3 text-sm/6 text-white focus:outline-none data-[focus]:outline-2 data-[focus]:-outline-offset-2 data-[focus]:outline-white/25"
rows={3}
/>
</Field>
</div>
Loading

0 comments on commit 9e7dfca

Please sign in to comment.