adf0d17497
publish / version_or_publish (push) Waiting to run
storybook-build / changes (push) Waiting to run
storybook-build / :storybook-build (push) Blocked by required conditions
Sync Gradio Skills to Hugging Face / sync-skills (push) Waiting to run
functional / changes (push) Waiting to run
functional / build-frontend (push) Blocked by required conditions
functional / functional-test-SSR=false (push) Blocked by required conditions
functional / functional-reload (push) Blocked by required conditions
functional / functional-test-SSR=true (push) Blocked by required conditions
hygiene / hygiene-test (push) Waiting to run
python / changes (push) Waiting to run
python / build (push) Blocked by required conditions
python / test-ubuntu-latest-flaky (push) Blocked by required conditions
python / test-ubuntu-latest-not-flaky (push) Blocked by required conditions
python / test-windows-latest-flaky (push) Blocked by required conditions
python / test-windows-latest-not-flaky (push) Blocked by required conditions
js / changes (push) Waiting to run
js / js-test (push) Blocked by required conditions
docs-build / changes (push) Waiting to run
docs-build / docs-build (push) Blocked by required conditions
docs-build / website-build (push) Blocked by required conditions
328 lines
8.1 KiB
TypeScript
328 lines
8.1 KiB
TypeScript
import { test, describe, afterEach, expect } from "vitest";
|
|
import { cleanup, render, fireEvent, waitFor } from "@self/tootils/render";
|
|
import { run_shared_prop_tests } from "@self/tootils/shared-prop-tests";
|
|
|
|
import Plot from "./Index.svelte";
|
|
|
|
const matplotlib_value = {
|
|
type: "matplotlib",
|
|
plot: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+M8AAAMBAQDJ/pLvAAAAAElFTkSuQmCC",
|
|
chart: "bar"
|
|
};
|
|
|
|
// layout has no height on purpose: this is the autosize path.
|
|
const plotly_autosize_value = {
|
|
type: "plotly",
|
|
plot: JSON.stringify({
|
|
data: [{ type: "scatter", x: [1, 2, 3], y: [4, 5, 6] }],
|
|
layout: {}
|
|
})
|
|
};
|
|
|
|
const default_props = {
|
|
value: null as null | Record<string, any>,
|
|
label: "Plot",
|
|
show_label: true,
|
|
caption: "",
|
|
theme_mode: "light" as const,
|
|
bokeh_version: null,
|
|
show_actions_button: false,
|
|
_selectable: false,
|
|
x_lim: null,
|
|
show_fullscreen_button: false,
|
|
buttons: null,
|
|
container: true
|
|
};
|
|
|
|
run_shared_prop_tests({
|
|
component: Plot,
|
|
name: "Plot",
|
|
base_props: { ...default_props },
|
|
has_label: false
|
|
});
|
|
|
|
describe("Props: label", () => {
|
|
afterEach(() => cleanup());
|
|
|
|
test("renders the label text", async () => {
|
|
const { getByTestId } = await render(Plot, {
|
|
...default_props,
|
|
label: "My Chart"
|
|
});
|
|
|
|
expect(getByTestId("block-label")).toHaveTextContent("My Chart");
|
|
});
|
|
|
|
test("show_label: true makes the label visible", async () => {
|
|
const { getByTestId } = await render(Plot, {
|
|
...default_props,
|
|
show_label: true
|
|
});
|
|
|
|
expect(getByTestId("block-label")).toBeVisible();
|
|
});
|
|
|
|
test("show_label: false hides the label", async () => {
|
|
const { getByTestId } = await render(Plot, {
|
|
...default_props,
|
|
show_label: false
|
|
});
|
|
|
|
expect(getByTestId("block-label")).not.toBeVisible();
|
|
});
|
|
});
|
|
|
|
describe("Props: value", () => {
|
|
afterEach(() => cleanup());
|
|
|
|
test("renders the empty state when value is null", async () => {
|
|
const { getByLabelText } = await render(Plot, {
|
|
...default_props,
|
|
value: null
|
|
});
|
|
|
|
expect(getByLabelText("Empty value")).toBeInTheDocument();
|
|
});
|
|
|
|
test("renders a plot when value is provided", async () => {
|
|
const { getByTestId, queryByLabelText } = await render(Plot, {
|
|
...default_props,
|
|
value: matplotlib_value
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(getByTestId("matplotlib")).toBeInTheDocument();
|
|
});
|
|
expect(queryByLabelText("Empty value")).toBeNull();
|
|
});
|
|
|
|
test("the rendered plot uses the value's image source", async () => {
|
|
const { getByTestId } = await render(Plot, {
|
|
...default_props,
|
|
value: matplotlib_value
|
|
});
|
|
|
|
await waitFor(() => {
|
|
const img = getByTestId("matplotlib").querySelector("img");
|
|
expect(img).toHaveAttribute("src", matplotlib_value.plot);
|
|
});
|
|
});
|
|
|
|
test("set_data with a value replaces the empty state with a plot", async () => {
|
|
const { set_data, getByTestId, queryByLabelText } = await render(Plot, {
|
|
...default_props,
|
|
value: null
|
|
});
|
|
|
|
expect(queryByLabelText("Empty value")).toBeInTheDocument();
|
|
|
|
await set_data({ value: matplotlib_value });
|
|
|
|
await waitFor(() => {
|
|
expect(getByTestId("matplotlib")).toBeInTheDocument();
|
|
});
|
|
expect(queryByLabelText("Empty value")).toBeNull();
|
|
});
|
|
|
|
test("set_data with null restores the empty state", async () => {
|
|
const { set_data, getByLabelText } = await render(Plot, {
|
|
...default_props,
|
|
value: matplotlib_value
|
|
});
|
|
|
|
await set_data({ value: null });
|
|
|
|
expect(getByLabelText("Empty value")).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe("Props: show_fullscreen_button", () => {
|
|
afterEach(() => cleanup());
|
|
|
|
test("show_fullscreen_button: true renders the fullscreen button", async () => {
|
|
const { getByLabelText } = await render(Plot, {
|
|
...default_props,
|
|
show_fullscreen_button: true
|
|
});
|
|
|
|
expect(getByLabelText("Fullscreen")).toBeVisible();
|
|
});
|
|
|
|
test("show_fullscreen_button: false hides the fullscreen button", async () => {
|
|
const { queryByLabelText } = await render(Plot, {
|
|
...default_props,
|
|
show_fullscreen_button: false
|
|
});
|
|
|
|
expect(queryByLabelText("Fullscreen")).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("Props: buttons", () => {
|
|
afterEach(() => cleanup());
|
|
|
|
test("a custom button is rendered with its label", async () => {
|
|
const { getByLabelText } = await render(Plot, {
|
|
...default_props,
|
|
buttons: [{ value: "Export", id: 1, icon: null }]
|
|
});
|
|
|
|
expect(getByLabelText("Export")).toBeVisible();
|
|
});
|
|
|
|
test("no toolbar buttons render when buttons is null and fullscreen is off", async () => {
|
|
const { queryByLabelText } = await render(Plot, {
|
|
...default_props,
|
|
buttons: null,
|
|
show_fullscreen_button: false
|
|
});
|
|
|
|
expect(queryByLabelText("Export")).toBeNull();
|
|
expect(queryByLabelText("Fullscreen")).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("Events: change", () => {
|
|
afterEach(() => cleanup());
|
|
|
|
test("fires when value changes via set_data", async () => {
|
|
const { listen, set_data } = await render(Plot, {
|
|
...default_props,
|
|
value: null
|
|
});
|
|
|
|
const change = listen("change");
|
|
|
|
await set_data({ value: matplotlib_value });
|
|
|
|
expect(change).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test("does not fire when an unrelated prop changes", async () => {
|
|
const { listen, set_data } = await render(Plot, {
|
|
...default_props,
|
|
value: matplotlib_value
|
|
});
|
|
|
|
const change = listen("change");
|
|
|
|
await set_data({ caption: "A new caption" });
|
|
|
|
expect(change).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe("Events: custom_button_click", () => {
|
|
afterEach(() => cleanup());
|
|
|
|
test("fires with the button id when a custom button is clicked", async () => {
|
|
const { listen, getByLabelText } = await render(Plot, {
|
|
...default_props,
|
|
buttons: [{ value: "Export", id: 42, icon: null }]
|
|
});
|
|
|
|
const custom = listen("custom_button_click");
|
|
|
|
await fireEvent.click(getByLabelText("Export"));
|
|
|
|
expect(custom).toHaveBeenCalledTimes(1);
|
|
expect(custom).toHaveBeenCalledWith({ id: 42 });
|
|
});
|
|
|
|
test("each custom button dispatches its own id", async () => {
|
|
const { listen, getByLabelText } = await render(Plot, {
|
|
...default_props,
|
|
buttons: [
|
|
{ value: "First", id: 1, icon: null },
|
|
{ value: "Second", id: 2, icon: null }
|
|
]
|
|
});
|
|
|
|
const custom = listen("custom_button_click");
|
|
|
|
await fireEvent.click(getByLabelText("First"));
|
|
await fireEvent.click(getByLabelText("Second"));
|
|
|
|
expect(custom).toHaveBeenNthCalledWith(1, { id: 1 });
|
|
expect(custom).toHaveBeenNthCalledWith(2, { id: 2 });
|
|
});
|
|
});
|
|
|
|
describe("get_data / set_data", () => {
|
|
afterEach(() => cleanup());
|
|
|
|
test("get_data returns the initial value", async () => {
|
|
const { get_data } = await render(Plot, {
|
|
...default_props,
|
|
value: matplotlib_value
|
|
});
|
|
|
|
const data = await get_data();
|
|
expect(data.value).toEqual(matplotlib_value);
|
|
});
|
|
|
|
test("get_data returns null when no value is set", async () => {
|
|
const { get_data } = await render(Plot, {
|
|
...default_props,
|
|
value: null
|
|
});
|
|
|
|
const data = await get_data();
|
|
expect(data.value).toBeNull();
|
|
});
|
|
|
|
test("set_data then get_data round-trips correctly", async () => {
|
|
const { set_data, get_data } = await render(Plot, {
|
|
...default_props,
|
|
value: null
|
|
});
|
|
|
|
await set_data({ value: matplotlib_value });
|
|
|
|
const data = await get_data();
|
|
expect(data.value).toEqual(matplotlib_value);
|
|
});
|
|
});
|
|
|
|
describe("Plotly", () => {
|
|
afterEach(() => cleanup());
|
|
|
|
// Regression: this collapsed to zero height after the Svelte 5 migration.
|
|
test("an autosize figure renders with a non-zero height", async () => {
|
|
const { getByTestId } = await render(Plot, {
|
|
...default_props,
|
|
value: plotly_autosize_value
|
|
});
|
|
|
|
await waitFor(() => {
|
|
const plot = getByTestId("plotly");
|
|
expect(plot.querySelector(".main-svg")).toBeTruthy();
|
|
expect(plot.offsetHeight).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("Edge cases", () => {
|
|
afterEach(() => cleanup());
|
|
|
|
test("renders without crashing when value is null", async () => {
|
|
const { container } = await render(Plot, {
|
|
...default_props,
|
|
value: null
|
|
});
|
|
|
|
expect(container).toBeInTheDocument();
|
|
});
|
|
|
|
test("change fires on initial mount", async () => {
|
|
const { listen } = await render(Plot, {
|
|
...default_props,
|
|
value: matplotlib_value
|
|
});
|
|
|
|
const change = listen("change", { retrospective: true });
|
|
|
|
expect(change).toHaveBeenCalled();
|
|
});
|
|
});
|