Skip to content

Commit

Permalink
toolbar tests
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte committed May 31, 2024
1 parent 511ac38 commit 2877761
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 89 deletions.
111 changes: 60 additions & 51 deletions packages/bits-ui/src/lib/bits/toolbar/toolbar.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ type ToolbarRootStateProps = ReadableBoxedValues<{
}>;

class ToolbarRootState {
#id = undefined as unknown as ToolbarRootStateProps["id"];
orientation = undefined as unknown as ToolbarRootStateProps["orientation"];
#loop = undefined as unknown as ToolbarRootStateProps["loop"];
#id: ToolbarRootStateProps["id"];
orientation: ToolbarRootStateProps["orientation"];
#loop: ToolbarRootStateProps["loop"];
#node = box<HTMLElement | null>(null);
rovingFocusGroup = undefined as unknown as UseRovingFocusReturn;
rovingFocusGroup: UseRovingFocusReturn;

constructor(props: ToolbarRootStateProps) {
this.#id = props.id;
Expand Down Expand Up @@ -64,12 +64,15 @@ class ToolbarRootState {
return new ToolbarButtonState(props, this);
}

props = $derived({
id: this.#id.value,
role: "toolbar",
"data-orientation": this.orientation.value,
[ROOT_ATTR]: "",
} as const);
props = $derived.by(
() =>
({
id: this.#id.value,
role: "toolbar",
"data-orientation": this.orientation.value,
[ROOT_ATTR]: "",
}) as const
);
}

type ToolbarGroupBaseStateProps = ReadableBoxedValues<{
Expand All @@ -78,10 +81,10 @@ type ToolbarGroupBaseStateProps = ReadableBoxedValues<{
}>;

class ToolbarGroupBaseState {
id = undefined as unknown as ToolbarGroupBaseStateProps["id"];
id: ToolbarGroupBaseStateProps["id"];
node = box<HTMLElement | null>(null);
disabled = undefined as unknown as ToolbarGroupBaseStateProps["disabled"];
root = undefined as unknown as ToolbarRootState;
disabled: ToolbarGroupBaseStateProps["disabled"];
root: ToolbarRootState;

constructor(props: ToolbarGroupBaseStateProps, root: ToolbarRootState) {
this.id = props.id;
Expand All @@ -90,13 +93,16 @@ class ToolbarGroupBaseState {
this.root = root;
}

props = $derived({
id: this.id.value,
[GROUP_ATTR]: "",
role: "group",
"data-orientation": getDataOrientation(this.root.orientation.value),
"data-disabled": getDataDisabled(this.disabled.value),
} as const);
props = $derived.by(
() =>
({
id: this.id.value,
[GROUP_ATTR]: "",
role: "group",
"data-orientation": getDataOrientation(this.root.orientation.value),
"data-disabled": getDataDisabled(this.disabled.value),
}) as const
);
}

//
Expand All @@ -109,9 +115,9 @@ type ToolbarGroupSingleStateProps = ToolbarGroupBaseStateProps &
}>;

class ToolbarGroupSingleState extends ToolbarGroupBaseState {
#value = undefined as unknown as ToolbarGroupSingleStateProps["value"];
#value: ToolbarGroupSingleStateProps["value"];
isMulti = false;
anyPressed = $derived(this.#value.value !== "");
anyPressed = $derived.by(() => this.#value.value !== "");

constructor(props: ToolbarGroupSingleStateProps, root: ToolbarRootState) {
super(props, root);
Expand Down Expand Up @@ -145,9 +151,9 @@ type ToolbarGroupMultipleStateProps = ToolbarGroupBaseStateProps &
}>;

class ToolbarGroupMultipleState extends ToolbarGroupBaseState {
#value = undefined as unknown as ToolbarGroupMultipleStateProps["value"];
#value: ToolbarGroupMultipleStateProps["value"];
isMulti = true;
anyPressed = $derived(this.#value.value.length > 0);
anyPressed = $derived.by(() => this.#value.value.length > 0);

constructor(props: ToolbarGroupMultipleStateProps, root: ToolbarRootState) {
super(props, root);
Expand Down Expand Up @@ -184,13 +190,13 @@ type ToolbarGroupItemStateProps = ReadableBoxedValues<{
}>;

class ToolbarGroupItemState {
#id = undefined as unknown as ToolbarGroupItemStateProps["id"];
#group = undefined as unknown as ToolbarGroupState;
#root = undefined as unknown as ToolbarRootState;
#value = undefined as unknown as ToolbarGroupItemStateProps["value"];
#id: ToolbarGroupItemStateProps["id"];
#group: ToolbarGroupState;
#root: ToolbarRootState;
#value: ToolbarGroupItemStateProps["value"];
#node = box<HTMLElement | null>(null);
#disabled = undefined as unknown as ToolbarGroupItemStateProps["disabled"];
#isDisabled = $derived(this.#disabled.value || this.#group.disabled.value);
#disabled: ToolbarGroupItemStateProps["disabled"];
#isDisabled = $derived.by(() => this.#disabled.value || this.#group.disabled.value);

constructor(
props: ToolbarGroupItemStateProps,
Expand Down Expand Up @@ -225,35 +231,38 @@ class ToolbarGroupItemState {
this.#root.rovingFocusGroup.handleKeydown(this.#node.value, e);
};

#isPressed = $derived(this.#group.includesItem(this.#value.value));
#isPressed = $derived.by(() => this.#group.includesItem(this.#value.value));

#ariaChecked = $derived.by(() => {
return this.#group.isMulti ? undefined : getAriaChecked(this.#isPressed);
});

#ariaPressed = $derived.by(() => {
return this.#group.isMulti ? undefined : getAriaPressed(this.#isPressed);
return this.#group.isMulti ? getAriaPressed(this.#isPressed) : undefined;
});

#tabIndex = $derived(this.#root.rovingFocusGroup.getTabIndex(this.#node.value).value);

props = $derived({
id: this.#id.value,
role: this.#group.isMulti ? undefined : "radio",
tabindex: this.#tabIndex,
"data-orientation": getDataOrientation(this.#root.orientation.value),
"data-disabled": getDataDisabled(this.#isDisabled),
"data-state": getToggleItemDataState(this.#isPressed),
"data-value": this.#value.value,
"aria-pressed": this.#ariaPressed,
"aria-checked": this.#ariaChecked,
[ITEM_ATTR]: "",
[GROUP_ITEM_ATTR]: "",
disabled: getDisabledAttr(this.#isDisabled),
//
onclick: this.#onclick,
onkeydown: this.#onkeydown,
});
#tabIndex = $derived.by(() => this.#root.rovingFocusGroup.getTabIndex(this.#node.value).value);

props = $derived.by(
() =>
({
id: this.#id.value,
role: this.#group.isMulti ? undefined : "radio",
tabindex: this.#tabIndex,
"data-orientation": getDataOrientation(this.#root.orientation.value),
"data-disabled": getDataDisabled(this.#isDisabled),
"data-state": getToggleItemDataState(this.#isPressed),
"data-value": this.#value.value,
"aria-pressed": this.#ariaPressed,
"aria-checked": this.#ariaChecked,
[ITEM_ATTR]: "",
[GROUP_ITEM_ATTR]: "",
disabled: getDisabledAttr(this.#isDisabled),
//
onclick: this.#onclick,
onkeydown: this.#onkeydown,
}) as const
);
}

type ToolbarLinkStateProps = ReadableBoxedValues<{
Expand Down
7 changes: 4 additions & 3 deletions packages/bits-ui/src/lib/bits/toolbar/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
PrimitiveButtonAttributes,
PrimitiveDivAttributes,
WithAsChild,
Without,
} from "$lib/internal/types.js";
import type { EventCallback } from "$lib/internal/events.js";

Expand All @@ -25,7 +26,7 @@ export type ToolbarGroupPropsWithoutHTML = Omit<
>;

export type ToolbarGroupProps = ToolbarGroupPropsWithoutHTML &
Omit<PrimitiveDivAttributes, "value" | "disabled">;
Without<PrimitiveDivAttributes, ToolbarGroupPropsWithoutHTML>;

export type ToolbarGroupItemPropsWithoutHTML = ToggleGroupItemPropsWithoutHTML;

Expand All @@ -37,11 +38,11 @@ export type ToolbarButtonPropsWithoutHTML = WithAsChild<{
}>;

export type ToolbarButtonProps = ToolbarButtonPropsWithoutHTML &
Omit<PrimitiveButtonAttributes, "onkeydown" | "disabled">;
Without<PrimitiveButtonAttributes, ToolbarButtonPropsWithoutHTML>;

export type ToolbarLinkPropsWithoutHTML = WithAsChild<{
onkeydown?: EventCallback<KeyboardEvent>;
}>;

export type ToolbarLinkProps = ToolbarLinkPropsWithoutHTML &
Omit<PrimitiveAnchorAttributes, "onkeydown">;
Without<PrimitiveAnchorAttributes, ToolbarLinkPropsWithoutHTML>;
30 changes: 13 additions & 17 deletions packages/bits-ui/src/tests/toolbar/Toolbar.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import { render } from "@testing-library/svelte";
import { render } from "@testing-library/svelte/svelte5";
import { userEvent } from "@testing-library/user-event";
import { axe } from "jest-axe";
import { describe, it } from "vitest";
import { getTestKbd } from "../utils.js";
import ToolbarTest from "./ToolbarTest.svelte";
import type { Toolbar } from "$lib/index.js";
import type { ToolbarTestProps } from "./ToolbarTest.svelte";

const kbd = getTestKbd();

function setup(
props: Toolbar.Props & {
multipleProps?: Toolbar.GroupProps<"multiple">;
singleProps?: Toolbar.GroupProps<"single">;
} = {}
) {
function setup(props: Partial<ToolbarTestProps> = {}) {
const user = userEvent.setup();
const returned = render(ToolbarTest, { ...props });
const root = returned.getByTestId("root");
Expand Down Expand Up @@ -114,7 +109,7 @@ describe("toolbar", () => {
expect(groupMultipleItemBold).toHaveFocus();
});

it("respects the loop prop", async () => {
it("respects the loop: false prop", async () => {
const { user, groupMultipleItemBold, button } = setup({
loop: false,
});
Expand All @@ -130,7 +125,7 @@ describe("toolbar", () => {

it("toolbar toogle group, type `'single'`, toggles when clicked", async () => {
const { user, groupSingleItemLeft, groupSingleItemCenter, alignBinding } = setup();
expect(alignBinding).toHaveTextContent("undefined");
expect(alignBinding).toHaveTextContent("");
await user.click(groupSingleItemLeft);
expect(alignBinding).toHaveTextContent("left");
await user.click(groupSingleItemCenter);
Expand All @@ -141,7 +136,7 @@ describe("toolbar", () => {
"toolbar toogle group, type `'single'`, toggles when the %s key is pressed",
async (key) => {
const { user, groupSingleItemLeft, groupSingleItemCenter, alignBinding } = setup();
expect(alignBinding).toHaveTextContent("undefined");
expect(alignBinding).toHaveTextContent("");
groupSingleItemLeft.focus();
await user.keyboard(key);
expect(alignBinding).toHaveTextContent("left");
Expand Down Expand Up @@ -180,7 +175,10 @@ describe("toolbar", () => {
groupSingleItemLeft,
groupSingleItemCenter,
groupSingleItemRight,
} = setup({ multipleProps: { disabled: true }, singleProps: { disabled: true } });
} = setup({
multipleProps: { disabled: true },
singleProps: { disabled: true },
});
expect(groupMultipleItemBold).toBeDisabled();
expect(groupMultipleItemItalic).toBeDisabled();
expect(groupMultipleItemStrikethrough).toBeDisabled();
Expand Down Expand Up @@ -216,7 +214,7 @@ describe("toolbar", () => {
const { user, groupMultipleItemItalic, groupSingleItemCenter, styleBinding, alignBinding } =
setup();
expect(styleBinding).toHaveTextContent("bold");
expect(alignBinding).toHaveTextContent("undefined");
expect(alignBinding).toHaveTextContent("");
expect(groupMultipleItemItalic).toHaveAttribute("data-state", "off");
expect(groupMultipleItemItalic).toHaveAttribute("aria-pressed", "false");
expect(groupSingleItemCenter).toHaveAttribute("data-state", "off");
Expand All @@ -236,7 +234,7 @@ describe("toolbar", () => {
it.each(["link", "button"])("toolbar %s forwards click event", async (kind) => {
const { user, clickedBinding, [kind as keyof ReturnType<typeof setup>]: el } = setup();

expect(clickedBinding).toHaveTextContent("undefined");
expect(clickedBinding).toHaveTextContent("");
await user.click(el as Element);
expect(clickedBinding).toHaveTextContent(kind);
});
Expand All @@ -247,11 +245,9 @@ describe("toolbar", () => {
const { user, button, clickedBinding } = setup();

button.focus();
expect(clickedBinding).toHaveTextContent("undefined");
expect(clickedBinding).toHaveTextContent("");
await user.keyboard(key);
expect(clickedBinding).toHaveTextContent("button");
}
);

it.todo("`asChild` behavior");
});
36 changes: 18 additions & 18 deletions packages/bits-ui/src/tests/toolbar/ToolbarTest.svelte
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
<script lang="ts">
import { Toolbar } from "$lib/index.js";
type $$Props = Toolbar.Props & {
multipleProps?: Toolbar.GroupProps<"multiple">;
singleProps?: Toolbar.GroupProps<"single">;
<script lang="ts" context="module">
import { Toolbar, type WithoutChildren } from "$lib/index.js";
export type ToolbarTestProps = WithoutChildren<Toolbar.RootProps> & {
multipleProps?: Partial<Toolbar.GroupProps>;
singleProps?: Partial<Toolbar.GroupProps>;
};
</script>

export let multipleProps: $$Props["multipleProps"] = undefined;
export let singleProps: $$Props["singleProps"] = undefined;
<script lang="ts">
let { multipleProps, singleProps, ...restProps }: ToolbarTestProps = $props();
let style: string[] | undefined = ["bold"];
let align: string | undefined;
let style: string[] = $state(["bold"]);
let align: string = $state("");
let clicked: string | undefined;
let clicked: string | undefined = $state();
</script>

<main>
<button data-testid="style-binding" on:click={() => (style = ["italic"])}>
<button aria-label="style" data-testid="style-binding" onclick={() => (style = ["italic"])}>
{style}
</button>

<button data-testid="align-binding" on:click={() => (align = "center")}>
<button aria-label="align" data-testid="align-binding" onclick={() => (align = "center")}>
{align}
</button>

<button data-testid="clicked-binding">
<span data-testid="clicked-binding">
{clicked}
</button>
</span>

<Toolbar.Root data-testid="root" {...$$restProps}>
<Toolbar.Root data-testid="root" {...restProps}>
<Toolbar.Group
data-testid="group-multiple"
bind:value={style}
Expand Down Expand Up @@ -78,11 +78,11 @@
</Toolbar.GroupItem>
</Toolbar.Group>

<Toolbar.Link data-testid="link" on:click={() => (clicked = "link")}
<Toolbar.Link data-testid="link" onclick={() => (clicked = "link")}
>Edited 2 hours ago</Toolbar.Link
>

<Toolbar.Button data-testid="button" on:click={() => (clicked = "button")}
<Toolbar.Button data-testid="button" onclick={() => (clicked = "button")}
>Save</Toolbar.Button
>
</Toolbar.Root>
Expand Down

0 comments on commit 2877761

Please sign in to comment.