Skip to content
Merged
Changes from 3 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
333 changes: 259 additions & 74 deletions js/radio/Radio.test.ts
Original file line number Diff line number Diff line change
@@ -1,96 +1,228 @@
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("renders provided value", async () => {
const { getAllByRole, getByTestId } = await render(Radio, {
choices: choices,
value: "cat",
label: "Radio"
test("null value renders no radio checked", async () => {
const { getAllByRole } = await render(Radio, {
...default_props,
value: null
});

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

expect(cat_radio).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();
});
});

const radioButtons: HTMLOptionElement[] = getAllByRole(
"radio"
) as HTMLOptionElement[];
assert.equal(radioButtons.length, 3);
test("numeric values are supported in choices", async () => {
const numeric_choices: [string, number][] = [
["one", 1],
["two", 2],
["three", 3]
];

radioButtons.forEach((radioButton: HTMLOptionElement, index) => {
assert.equal(radioButton.value === choices[index][1], true);
const { getAllByRole } = await render(Radio, {
...default_props,
choices: numeric_choices,
value: 2
});

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

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

expect(getByText("dog")).toBeTruthy();
Comment thread
hannahblair marked this conversation as resolved.
Outdated
expect(getByText("cat")).toBeTruthy();
expect(getByText("turtle")).toBeTruthy();
});

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("display value and internal value can differ", async () => {
const custom_choices: [string, string][] = [
["dog label", "dog_val"],
["cat label", "cat_val"]
];

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

const dog_radio = getAllByRole("radio")[0];
expect(getByText("dog label")).toBeTruthy();
expect(getByText("cat label")).toBeTruthy();
Comment thread
hannahblair marked this conversation as resolved.
Outdated

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

expect(dog_radio).toBeChecked();
test("empty choices renders no radios", async () => {
const { queryAllByRole } = await render(Radio, {
...default_props,
choices: [],
value: null
});

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

expect(cat_radio).not.toBeChecked();
describe("Props: interactive", () => {
test("interactive=false disables all radios", async () => {
const { getAllByRole } = await render(Radio, {
...default_props,
interactive: false
});

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

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

expect(cat_radio).toBeChecked();
expect(getByText("Pick your favorite animal")).toBeTruthy();
Comment thread
hannahblair marked this conversation as resolved.
Outdated
});

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 info does not render info text", async () => {
const { queryByText } = await render(Radio, {
...default_props,
info: undefined
});

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(queryByText("Pick your favorite animal")).toBeNull();
});
});

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 }]
});

test("when multiple radios are on the screen, they should not conflict", async () => {
getByLabelText("Shuffle");
});

test("no buttons rendered when null", async () => {
const { queryByRole } = await render(Radio, {
...default_props,
buttons: null
});

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();

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 +231,81 @@ describe("Radio", () => {
await event.click(items[0]);

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

describe("Events", () => {
Comment thread
hannahblair marked this conversation as resolved.
test("change emitted when value changes via set_data", async () => {
const { listen, set_data } = await render(Radio, default_props);

const change = listen("change");
await set_data({ value: "dog" });
Comment thread
hannahblair marked this conversation as resolved.

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

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

const change = listen("change");
Comment thread
hannahblair marked this conversation as resolved.
Outdated

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"
expect(change).not.toHaveBeenCalled();
});

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);
});

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 }]
});

const custom = listen("custom_button_click");
const btn = getByLabelText("Shuffle");
await fireEvent.click(btn);

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

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

expect(select_mock.callCount).toBe(0);
expect(input_mock.callCount).toBe(0);
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