Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
338 changes: 264 additions & 74 deletions js/radio/Radio.test.ts
Original file line number Diff line number Diff line change
@@ -1,96 +1,231 @@
import { test, describe, assert, afterEach, expect } from "vitest";

import { cleanup, render } from "@self/tootils/render";
import { tick } from "svelte";
import { test, describe, expect, afterEach } from "vitest";
import { cleanup, render, fireEvent, waitFor } from "@self/tootils/render";
import { run_shared_prop_tests } from "@self/tootils/shared-prop-tests";
import event from "@testing-library/user-event";

import Radio from "./Index.svelte";

describe("Radio", () => {
afterEach(() => cleanup());
const choices = [
["dog", "dog"],
["cat", "cat"],
["turtle", "turtle"]
] as [string, string][];
afterEach(cleanup);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need this for tests to pass despite it being in run_shared_prop_tests


const choices: [string, string][] = [
["dog", "dog"],
["cat", "cat"],
["turtle", "turtle"]
];

const default_props = {
show_label: true,
choices,
value: "cat",
label: "Radio",
interactive: true
};

run_shared_prop_tests({
component: Radio,
name: "Radio",
base_props: {
choices,
value: "dog",
interactive: true
},
has_validation_error: false
});

describe("Props: value", () => {
test("renders provided value as checked", async () => {
const { getAllByRole } = await render(Radio, default_props);

const radios = getAllByRole("radio") as HTMLInputElement[];
expect(radios).toHaveLength(3);
expect(radios[0]).not.toBeChecked();
expect(radios[1]).toBeChecked();
expect(radios[2]).not.toBeChecked();
});

test("null value renders no radio checked", async () => {
const { getAllByRole } = await render(Radio, {
...default_props,
value: null
});

const radios = getAllByRole("radio") as HTMLInputElement[];
radios.forEach((radio) => {
expect(radio).not.toBeChecked();
});
});

test("undefined value renders no radio checked", async () => {
const { getAllByRole } = await render(Radio, {
...default_props,
value: undefined
});

const radios = getAllByRole("radio") as HTMLInputElement[];
radios.forEach((radio) => {
expect(radio).not.toBeChecked();
});
});

test("value not in choices renders no radio checked", async () => {
const { getAllByRole } = await render(Radio, {
...default_props,
value: "fish"
});

const radios = getAllByRole("radio") as HTMLInputElement[];
radios.forEach((radio) => {
expect(radio).not.toBeChecked();
});
});

test("numeric values are supported in choices", async () => {
const numeric_choices: [string, number][] = [
["one", 1],
["two", 2],
["three", 3]
];

test("renders provided value", async () => {
const { getAllByRole, getByTestId } = await render(Radio, {
choices: choices,
value: "cat",
label: "Radio"
const { getAllByRole } = await render(Radio, {
...default_props,
choices: numeric_choices,
value: 2
});

const cat_radio = getAllByRole("radio")[1];
const radios = getAllByRole("radio") as HTMLInputElement[];
expect(radios[1]).toBeChecked();
});
});

expect(cat_radio).toBeChecked();
describe("Props: choices", () => {
test("renders display values as labels", async () => {
const { getByText } = await render(Radio, default_props);

const radioButtons: HTMLOptionElement[] = getAllByRole(
"radio"
) as HTMLOptionElement[];
assert.equal(radioButtons.length, 3);
expect(getByText("dog")).toBeVisible();
expect(getByText("cat")).toBeVisible();
expect(getByText("turtle")).toBeVisible();
});

radioButtons.forEach((radioButton: HTMLOptionElement, index) => {
assert.equal(radioButton.value === choices[index][1], true);
test("display value and internal value can differ", async () => {
const custom_choices: [string, string][] = [
["dog label", "dog_val"],
["cat label", "cat_val"]
];

const { getByText, getAllByRole, get_data } = await render(Radio, {
...default_props,
choices: custom_choices,
value: "cat_val"
});

expect(getByText("dog label")).toBeVisible();
expect(getByText("cat label")).toBeVisible();

const radios = getAllByRole("radio") as HTMLInputElement[];
expect(radios[1]).toBeChecked();

const data = await get_data();
expect(data.value).toBe("cat_val");
});

test("should update the value when a radio is clicked", async () => {
const { getByDisplayValue, getAllByRole } = await render(Radio, {
choices: choices,
value: "cat",
label: "Radio",
interactive: true
test("empty choices renders no radios", async () => {
const { queryAllByRole } = await render(Radio, {
...default_props,
choices: [],
value: null
});

const dog_radio = getAllByRole("radio")[0];
expect(queryAllByRole("radio")).toHaveLength(0);
});
});

await event.click(dog_radio);
describe("Props: interactive", () => {
test("interactive=false disables all radios", async () => {
const { getAllByRole } = await render(Radio, {
...default_props,
interactive: false
});

const radios = getAllByRole("radio") as HTMLInputElement[];
radios.forEach((radio) => {
expect(radio).toBeDisabled();
});
});
});

expect(dog_radio).toBeChecked();
describe("Props: info", () => {
test("info renders descriptive text", async () => {
const { getByText } = await render(Radio, {
...default_props,
info: "Pick your favorite animal"
});

const cat_radio = getAllByRole("radio")[1];
expect(getByText("Pick your favorite animal")).toBeVisible();
});

expect(cat_radio).not.toBeChecked();
test("no info does not render info text", async () => {
const { queryByText } = await render(Radio, {
...default_props,
info: undefined
});

await event.click(getByDisplayValue("turtle"));
expect(queryByText("Pick your favorite animal")).toBeNull();
});
});

await event.click(cat_radio);
describe("Props: buttons", () => {
test("custom buttons are rendered when provided", async () => {
const { getByLabelText } = await render(Radio, {
...default_props,
buttons: [{ value: "Shuffle", id: 1, icon: null }]
});

expect(cat_radio).toBeChecked();
getByLabelText("Shuffle");
});

test.skip("should dispatch the select event when clicks", async () => {
const { listen, getAllByTestId } = await render(Radio, {
choices: choices,
value: "cat",
label: "Radio",
interactive: true
test("no buttons rendered when null", async () => {
const { queryByRole } = await render(Radio, {
...default_props,
buttons: null
});

const mock = listen("select");
await event.click(getAllByTestId("dog-radio-label")[0]);
expect(mock.callCount).toBe(1);
expect(mock.calls[0][0].detail.data.value).toEqual("dog");
expect(queryByRole("button")).toBeNull();
});
});

describe("Interactive behavior", () => {
test("clicking through options selects and deselects correctly", async () => {
const { getAllByRole } = await render(Radio, default_props);

const radios = getAllByRole("radio") as HTMLInputElement[];
expect(radios[1]).toBeChecked();

await event.click(radios[0]);
expect(radios[0]).toBeChecked();
expect(radios[1]).not.toBeChecked();

test("when multiple radios are on the screen, they should not conflict", async () => {
await event.click(radios[2]);
expect(radios[2]).toBeChecked();
expect(radios[0]).not.toBeChecked();

await event.click(radios[1]);
expect(radios[1]).toBeChecked();
expect(radios[2]).not.toBeChecked();
});

test("multiple radio instances on the same page do not conflict", async () => {
const { container } = await render(Radio, {
choices: choices,
value: "cat",
label: "Radio",
interactive: true
...default_props,
value: "cat"
});

const { getAllByLabelText } = await render(
Radio,
{
choices: choices,
value: "dog",
label: "Radio",
interactive: true
...default_props,
value: "dog"
},
container
{ container: container as HTMLElement }
);

const items = getAllByLabelText("dog") as HTMLInputElement[];
Expand All @@ -99,28 +234,83 @@ describe("Radio", () => {
await event.click(items[0]);

expect([items[0].checked, items[1].checked]).toEqual([true, true]);
cleanup();
});
});

describe("Events", () => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would test the select and input events

test("change emitted when value changes via set_data", async () => {
const { listen, set_data } = await render(Radio, default_props);

const change = listen("change");
const input = listen("input");
await set_data({ value: "dog" });

expect(change).toHaveBeenCalledTimes(1);
expect(input).not.toHaveBeenCalled();
});

test("change event does not fire on mount", async () => {
const { listen } = await render(Radio, default_props);

const change = listen("change", { retrospective: true });

expect(change).not.toHaveBeenCalled();
});

test.skip("dispatches change and should not dispatch select/input on programmatic value update", async () => {
const { unmount, listen } = await render(Radio, {
choices: choices,
value: "cat",
label: "Radio"
test("change deduplication: same value does not re-fire", async () => {
const { listen, set_data } = await render(Radio, {
...default_props,
value: "dog"
});

const select_mock = listen("select" as never);
const input_mock = listen("input" as never);
const change = listen("change");
await set_data({ value: "cat" });
await set_data({ value: "cat" });

expect(change).toHaveBeenCalledTimes(1);
});

unmount();
await render(Radio, {
choices: choices,
value: "dog",
label: "Radio"
test("custom_button_click emitted when custom button is clicked", async () => {
const { listen, getByLabelText } = await render(Radio, {
...default_props,
buttons: [{ value: "Shuffle", id: 5, icon: null }]
});
await tick();

expect(select_mock.callCount).toBe(0);
expect(input_mock.callCount).toBe(0);
const custom = listen("custom_button_click");
const btn = getByLabelText("Shuffle");
await fireEvent.click(btn);

expect(custom).toHaveBeenCalledTimes(1);
expect(custom).toHaveBeenCalledWith({ id: 5 });
});
});

describe("get_data / set_data", () => {
test("get_data returns the current value", async () => {
const { get_data } = await render(Radio, {
...default_props,
value: "turtle"
});

const data = await get_data();
expect(data.value).toBe("turtle");
});

test("set_data updates the value", async () => {
const { set_data, get_data } = await render(Radio, default_props);

await set_data({ value: "dog" });

const data = await get_data();
expect(data.value).toBe("dog");
});

test("set_data to null clears the value", async () => {
const { set_data, get_data } = await render(Radio, default_props);

await set_data({ value: null });

const data = await get_data();
expect(data.value).toBeNull();
});
});
Loading