/* Copyright 2026 Marimo. All rights reserved. */
import { render } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { initialModeAtom } from "@/core/mode";
import { store } from "@/core/state/jotai";
import { DatePickerPlugin } from "../DatePickerPlugin";
import { DateRangePickerPlugin } from "../DateRangePlugin";
import { DateTimePickerPlugin } from "../DateTimePickerPlugin";
const sharedProps = () => ({
host: document.createElement("div"),
setValue: vi.fn(),
functions: {},
});
const widgets = [
{
name: "date",
role: "group",
fallback: "date picker",
render: (label: string | null, disabled = false) =>
new DatePickerPlugin().render({
...sharedProps(),
value: "2026-10-02",
data: {
label,
disabled,
start: "2026-01-01",
stop: "2026-12-31",
fullWidth: false,
},
}),
},
{
name: "datetime",
role: "group",
fallback: "date time picker",
render: (label: string | null, disabled = false) =>
new DateTimePickerPlugin().render({
...sharedProps(),
value: "2026-10-02T12:00:00",
data: {
label,
disabled,
start: "2026-01-01T00:00:00",
stop: "2026-12-31T23:59:59",
precision: "minute",
fullWidth: false,
},
}),
},
{
name: "date range",
role: "group",
fallback: "date range picker",
render: (label: string | null, disabled = false) =>
new DateRangePickerPlugin().render({
...sharedProps(),
value: ["2026-10-02", "2026-10-03"],
data: {
label,
disabled,
start: "2026-01-01",
stop: "2026-12-31",
fullWidth: false,
},
}),
},
];
beforeEach(() => store.set(initialModeAtom, "edit"));
describe.each(widgets)("$name accessible labels", (widget) => {
it("uses rendered formatting and decoded entities, and follows label updates", () => {
const { getByRole, rerender } = render(
widget.render("Visit date & time"),
);
expect(getByRole(widget.role, { name: "Visit date & time" })).toBeTruthy();
rerender(widget.render("Updated label"));
expect(getByRole(widget.role, { name: "Updated label" })).toBeTruthy();
rerender(widget.render(null));
expect(getByRole(widget.role, { name: widget.fallback })).toBeTruthy();
});
it.each([null, ""])("uses the fallback when label is %s", (label) => {
const { getByRole } = render(widget.render(label));
expect(getByRole(widget.role, { name: widget.fallback })).toBeTruthy();
});
it("names the calendar button and every editable segment", () => {
const { getByRole, getAllByRole } = render(
widget.render("Visit & time"),
);
expect(getByRole("button", { name: "Calendar Visit & time" })).toBeTruthy();
for (const segment of getAllByRole("spinbutton")) {
expect(segment).toHaveAccessibleName(/Visit & time/);
}
});
it("preserves disabled controls with a formatted label", () => {
const { getByRole, getAllByRole } = render(
widget.render("Visit", true),
);
expect(getByRole("button", { name: "Calendar Visit" })).toBeDisabled();
for (const segment of getAllByRole("spinbutton")) {
expect(segment).toHaveAttribute("aria-disabled", "true");
}
});
it("keeps labels distinct for multiple widgets", () => {
const { getByRole } = render(
<>
{widget.render("First")}
{widget.render("Second")}
>,
);
expect(getByRole(widget.role, { name: "First" })).not.toBe(
getByRole(widget.role, { name: "Second" }),
);
});
});