gradio-app--gradio
adf0d17497
publish / version_or_publish (push) Has been cancelled
storybook-build / changes (push) Has been cancelled
storybook-build / :storybook-build (push) Has been cancelled
Sync Gradio Skills to Hugging Face / sync-skills (push) Has been cancelled
functional / changes (push) Has been cancelled
functional / build-frontend (push) Has been cancelled
functional / functional-test-SSR=false (push) Has been cancelled
functional / functional-reload (push) Has been cancelled
js / changes (push) Has been cancelled
js / js-test (push) Has been cancelled
docs-build / changes (push) Has been cancelled
docs-build / docs-build (push) Has been cancelled
docs-build / website-build (push) Has been cancelled
functional / functional-test-SSR=true (push) Has been cancelled
hygiene / hygiene-test (push) Has been cancelled
python / changes (push) Has been cancelled
python / build (push) Has been cancelled
python / test-ubuntu-latest-flaky (push) Has been cancelled
python / test-ubuntu-latest-not-flaky (push) Has been cancelled
python / test-windows-latest-flaky (push) Has been cancelled
python / test-windows-latest-not-flaky (push) Has been cancelled
471 行
11 KiB
TypeScript
471 行
11 KiB
TypeScript
import { test, describe, afterEach, expect, vi } from "vitest";
|
|
import { cleanup, render, fireEvent } from "@self/tootils/render";
|
|
import { run_shared_prop_tests } from "@self/tootils/shared-prop-tests";
|
|
import { tick } from "svelte";
|
|
|
|
import Slider from "./Index.svelte";
|
|
|
|
const loading_status = {
|
|
status: "complete" as const,
|
|
eta: 0,
|
|
queue_position: 1,
|
|
queue_size: 1,
|
|
scroll_to_output: false,
|
|
visible: true,
|
|
fn_index: 0,
|
|
show_progress: "full" as const,
|
|
type: "input" as const,
|
|
stream_state: "closed" as const
|
|
};
|
|
|
|
const default_props = {
|
|
value: 5,
|
|
minimum: 0,
|
|
maximum: 10,
|
|
step: 1,
|
|
label: "Slider",
|
|
show_label: true,
|
|
interactive: true,
|
|
buttons: null as string[] | null,
|
|
info: undefined as string | undefined,
|
|
loading_status
|
|
};
|
|
|
|
run_shared_prop_tests({
|
|
component: Slider,
|
|
name: "Slider",
|
|
base_props: {
|
|
...default_props
|
|
},
|
|
has_label: true,
|
|
has_validation_error: true
|
|
});
|
|
|
|
describe("Slider", () => {
|
|
afterEach(() => cleanup());
|
|
|
|
test("renders with value displayed in number input", async () => {
|
|
const { getByTestId } = await render(Slider, {
|
|
...default_props,
|
|
value: 7
|
|
});
|
|
|
|
const number_input = getByTestId("number-input") as HTMLInputElement;
|
|
expect(number_input).toBeVisible();
|
|
expect(number_input.value).toBe("7");
|
|
});
|
|
|
|
test("renders range input with correct min, max, and step", async () => {
|
|
const { getByTestId } = await render(Slider, {
|
|
...default_props,
|
|
minimum: 2,
|
|
maximum: 20,
|
|
step: 0.5
|
|
});
|
|
|
|
const range_input = getByTestId("range-input") as HTMLInputElement;
|
|
expect(range_input).toBeVisible();
|
|
expect(range_input.min).toBe("2");
|
|
expect(range_input.max).toBe("20");
|
|
expect(range_input.step).toBe("0.5");
|
|
});
|
|
|
|
test("displays min and max values", async () => {
|
|
const { getByTestId } = await render(Slider, {
|
|
...default_props,
|
|
minimum: -10,
|
|
maximum: 100
|
|
});
|
|
|
|
expect(getByTestId("min-value")).toHaveTextContent("-10");
|
|
expect(getByTestId("max-value")).toHaveTextContent("100");
|
|
});
|
|
|
|
test("defaults label to 'Slider' when label is empty", async () => {
|
|
const { getByText } = await render(Slider, {
|
|
...default_props,
|
|
label: ""
|
|
});
|
|
|
|
expect(getByText("Slider")).toBeVisible();
|
|
});
|
|
});
|
|
|
|
describe("Props: interactive", () => {
|
|
afterEach(() => cleanup());
|
|
|
|
test("interactive=true enables both inputs", async () => {
|
|
const { getByTestId } = await render(Slider, {
|
|
...default_props,
|
|
interactive: true
|
|
});
|
|
|
|
const number_input = getByTestId("number-input") as HTMLInputElement;
|
|
const range_input = getByTestId("range-input") as HTMLInputElement;
|
|
|
|
expect(number_input).toBeEnabled();
|
|
expect(range_input).toBeEnabled();
|
|
});
|
|
|
|
test("interactive=false disables both inputs", async () => {
|
|
const { getByTestId } = await render(Slider, {
|
|
...default_props,
|
|
interactive: false
|
|
});
|
|
|
|
const number_input = getByTestId("number-input") as HTMLInputElement;
|
|
const range_input = getByTestId("range-input") as HTMLInputElement;
|
|
|
|
expect(number_input).toBeDisabled();
|
|
expect(range_input).toBeDisabled();
|
|
});
|
|
|
|
test("interactive=false disables the reset button", async () => {
|
|
const { getByTestId } = await render(Slider, {
|
|
...default_props,
|
|
interactive: false
|
|
});
|
|
|
|
expect(getByTestId("reset-button")).toBeDisabled();
|
|
});
|
|
});
|
|
|
|
describe("Props: buttons", () => {
|
|
afterEach(() => cleanup());
|
|
|
|
test("reset button is shown by default when buttons is null", async () => {
|
|
const { getByTestId } = await render(Slider, {
|
|
...default_props,
|
|
buttons: null
|
|
});
|
|
|
|
expect(getByTestId("reset-button")).toBeVisible();
|
|
});
|
|
|
|
test("reset button is shown when buttons includes 'reset'", async () => {
|
|
const { getByTestId } = await render(Slider, {
|
|
...default_props,
|
|
buttons: ["reset"]
|
|
});
|
|
|
|
expect(getByTestId("reset-button")).toBeVisible();
|
|
});
|
|
|
|
test("reset button is hidden when buttons is empty array", async () => {
|
|
const { queryByTestId } = await render(Slider, {
|
|
...default_props,
|
|
buttons: []
|
|
});
|
|
|
|
expect(queryByTestId("reset-button")).toBeNull();
|
|
});
|
|
|
|
test("reset button is hidden when buttons does not include 'reset'", async () => {
|
|
const { queryByTestId } = await render(Slider, {
|
|
...default_props,
|
|
buttons: ["other"]
|
|
});
|
|
|
|
expect(queryByTestId("reset-button")).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("Events: change", () => {
|
|
afterEach(() => cleanup());
|
|
|
|
test("setting value triggers change event", async () => {
|
|
const { listen, set_data } = await render(Slider, {
|
|
...default_props,
|
|
value: 5
|
|
});
|
|
|
|
const change = listen("change");
|
|
|
|
await set_data({ value: 8 });
|
|
|
|
expect(change).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test("change event is not triggered on mount with a default value", async () => {
|
|
const { listen } = await render(Slider, {
|
|
...default_props,
|
|
value: 5
|
|
});
|
|
|
|
const change = listen("change", { retrospective: true });
|
|
|
|
expect(change).not.toHaveBeenCalled();
|
|
await tick();
|
|
await tick();
|
|
|
|
expect(change).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test("changing value multiple times triggers change each time", async () => {
|
|
const { listen, set_data } = await render(Slider, {
|
|
...default_props,
|
|
value: 0
|
|
});
|
|
|
|
const change = listen("change");
|
|
const input = listen("input");
|
|
const release = listen("release");
|
|
|
|
await set_data({ value: 3 });
|
|
await set_data({ value: 7 });
|
|
await set_data({ value: 10 });
|
|
|
|
expect(change).toHaveBeenCalledTimes(3);
|
|
expect(input).not.toHaveBeenCalled();
|
|
expect(release).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test("setting same value does not trigger change", async () => {
|
|
const { listen, set_data } = await render(Slider, {
|
|
...default_props,
|
|
value: 5
|
|
});
|
|
|
|
const change = listen("change");
|
|
|
|
await set_data({ value: 5 });
|
|
|
|
expect(change).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe("Events: input", () => {
|
|
afterEach(() => cleanup());
|
|
|
|
test("typing in number input fires input event", async () => {
|
|
const { getByTestId, listen } = await render(Slider, {
|
|
...default_props,
|
|
value: 5
|
|
});
|
|
|
|
const input = listen("input");
|
|
const number_input = getByTestId("number-input") as HTMLInputElement;
|
|
|
|
await fireEvent.input(number_input, { target: { value: "8" } });
|
|
|
|
expect(input).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test("moving range slider fires input event", async () => {
|
|
const { getByTestId, listen } = await render(Slider, {
|
|
...default_props,
|
|
value: 5
|
|
});
|
|
|
|
const input = listen("input");
|
|
const range_input = getByTestId("range-input") as HTMLInputElement;
|
|
|
|
await fireEvent.input(range_input, { target: { value: "8" } });
|
|
|
|
expect(input).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe("Events: release", () => {
|
|
afterEach(() => cleanup());
|
|
|
|
test("pointerup on range input fires release event with value", async () => {
|
|
const { getByTestId, listen } = await render(Slider, {
|
|
...default_props,
|
|
value: 5
|
|
});
|
|
|
|
const release = listen("release");
|
|
const range_input = getByTestId("range-input") as HTMLInputElement;
|
|
|
|
await fireEvent.pointerUp(range_input);
|
|
|
|
expect(release).toHaveBeenCalledTimes(1);
|
|
expect(release).toHaveBeenCalledWith(5);
|
|
});
|
|
|
|
test("pointerup on number input fires release event", async () => {
|
|
const { getByTestId, listen } = await render(Slider, {
|
|
...default_props,
|
|
value: 3
|
|
});
|
|
|
|
const release = listen("release");
|
|
const number_input = getByTestId("number-input") as HTMLInputElement;
|
|
|
|
await fireEvent.pointerUp(number_input);
|
|
|
|
expect(release).toHaveBeenCalledTimes(1);
|
|
expect(release).toHaveBeenCalledWith(3);
|
|
});
|
|
|
|
test("blur on number input fires release event via clamp", async () => {
|
|
const { getByTestId, listen } = await render(Slider, {
|
|
...default_props,
|
|
value: 5
|
|
});
|
|
|
|
const release = listen("release");
|
|
const number_input = getByTestId("number-input") as HTMLInputElement;
|
|
|
|
await fireEvent.blur(number_input);
|
|
|
|
expect(release).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe("Value clamping", () => {
|
|
afterEach(() => cleanup());
|
|
|
|
test("value above maximum is clamped on number input blur", async () => {
|
|
const { getByTestId, get_data } = await render(Slider, {
|
|
...default_props,
|
|
value: 5,
|
|
minimum: 0,
|
|
maximum: 10
|
|
});
|
|
|
|
const number_input = getByTestId("number-input") as HTMLInputElement;
|
|
|
|
await fireEvent.input(number_input, { target: { value: "15" } });
|
|
await fireEvent.blur(number_input);
|
|
|
|
const data = await get_data();
|
|
expect(data.value).toBe(10);
|
|
});
|
|
|
|
test("value below minimum is clamped on number input blur", async () => {
|
|
const { getByTestId, get_data } = await render(Slider, {
|
|
...default_props,
|
|
value: 5,
|
|
minimum: 0,
|
|
maximum: 10
|
|
});
|
|
|
|
const number_input = getByTestId("number-input") as HTMLInputElement;
|
|
|
|
await fireEvent.input(number_input, { target: { value: "-5" } });
|
|
await fireEvent.blur(number_input);
|
|
|
|
const data = await get_data();
|
|
expect(data.value).toBe(0);
|
|
});
|
|
|
|
test("value within range is not clamped", async () => {
|
|
const { getByTestId, get_data } = await render(Slider, {
|
|
...default_props,
|
|
value: 5,
|
|
minimum: 0,
|
|
maximum: 10
|
|
});
|
|
|
|
const number_input = getByTestId("number-input") as HTMLInputElement;
|
|
|
|
await fireEvent.input(number_input, { target: { value: "7" } });
|
|
await fireEvent.blur(number_input);
|
|
|
|
const data = await get_data();
|
|
expect(data.value).toBe(7);
|
|
});
|
|
});
|
|
|
|
describe("Reset button", () => {
|
|
afterEach(() => cleanup());
|
|
|
|
test("clicking reset restores the initial value", async () => {
|
|
const { getByTestId, set_data, get_data } = await render(Slider, {
|
|
...default_props,
|
|
value: 5
|
|
});
|
|
|
|
await set_data({ value: 9 });
|
|
|
|
const reset_btn = getByTestId("reset-button");
|
|
await fireEvent.click(reset_btn);
|
|
|
|
const data = await get_data();
|
|
expect(data.value).toBe(5);
|
|
});
|
|
|
|
test("clicking reset dispatches change and release events", async () => {
|
|
const { getByTestId, listen, set_data } = await render(Slider, {
|
|
...default_props,
|
|
value: 5
|
|
});
|
|
|
|
await set_data({ value: 9 });
|
|
|
|
const change = listen("change");
|
|
const release = listen("release");
|
|
|
|
const reset_btn = getByTestId("reset-button");
|
|
await fireEvent.click(reset_btn);
|
|
|
|
expect(change).toHaveBeenCalled();
|
|
expect(release).toHaveBeenCalled();
|
|
expect(release).toHaveBeenCalledWith(5);
|
|
});
|
|
});
|
|
|
|
describe("Number input and range input sync", () => {
|
|
afterEach(() => cleanup());
|
|
|
|
test("number input reflects the current value", async () => {
|
|
const { getByTestId, set_data } = await render(Slider, {
|
|
...default_props,
|
|
value: 3
|
|
});
|
|
|
|
const number_input = getByTestId("number-input") as HTMLInputElement;
|
|
expect(number_input.value).toBe("3");
|
|
|
|
await set_data({ value: 8 });
|
|
|
|
expect(number_input.value).toBe("8");
|
|
});
|
|
|
|
test("range input reflects the current value", async () => {
|
|
const { getByTestId, set_data } = await render(Slider, {
|
|
...default_props,
|
|
value: 3
|
|
});
|
|
|
|
const range_input = getByTestId("range-input") as HTMLInputElement;
|
|
expect(range_input.value).toBe("3");
|
|
|
|
await set_data({ value: 8 });
|
|
|
|
expect(range_input.value).toBe("8");
|
|
});
|
|
});
|
|
|
|
describe("Accessibility", () => {
|
|
afterEach(() => cleanup());
|
|
|
|
test("number input has correct aria-label", async () => {
|
|
const { getByLabelText } = await render(Slider, {
|
|
...default_props,
|
|
label: "Range"
|
|
});
|
|
|
|
expect(() => getByLabelText("number input for Range")).not.toThrow();
|
|
});
|
|
|
|
test("range input has correct aria-label", async () => {
|
|
const { getByLabelText } = await render(Slider, {
|
|
...default_props,
|
|
label: "Range"
|
|
});
|
|
|
|
expect(() => getByLabelText("range slider for Range")).not.toThrow();
|
|
});
|
|
|
|
test("reset button has aria-label", async () => {
|
|
const { getByLabelText } = await render(Slider, {
|
|
...default_props
|
|
});
|
|
|
|
expect(() => getByLabelText("Reset to default value")).not.toThrow();
|
|
});
|
|
});
|