diff --git a/src/components/widgets/kubernetes/node.test.jsx b/src/components/widgets/kubernetes/node.test.jsx new file mode 100644 index 000000000..63c1c418e --- /dev/null +++ b/src/components/widgets/kubernetes/node.test.jsx @@ -0,0 +1,27 @@ +// @vitest-environment jsdom + +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import Node from "./node"; + +describe("components/widgets/kubernetes/node", () => { + it("renders cluster label when showLabel is enabled", () => { + const data = { cpu: { percent: 50 }, memory: { free: 123, percent: 10 } }; + + const { container } = render(); + + expect(screen.getByText("50")).toBeInTheDocument(); + expect(screen.getByText("123")).toBeInTheDocument(); + expect(screen.getByText("Cluster A")).toBeInTheDocument(); + expect(container.querySelectorAll('div[style*="width:"]').length).toBeGreaterThan(0); + }); + + it("renders node name when showLabel is enabled for node type", () => { + const data = { name: "node-1", ready: true, cpu: { percent: 1 }, memory: { free: 2, percent: 3 } }; + + render(); + + expect(screen.getByText("node-1")).toBeInTheDocument(); + }); +}); diff --git a/src/components/widgets/longhorn/node.test.jsx b/src/components/widgets/longhorn/node.test.jsx new file mode 100644 index 000000000..06033d27c --- /dev/null +++ b/src/components/widgets/longhorn/node.test.jsx @@ -0,0 +1,32 @@ +// @vitest-environment jsdom + +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +const { Resource } = vi.hoisted(() => ({ + Resource: vi.fn(({ children }) =>
{children}
), +})); + +vi.mock("../widget/resource", () => ({ + default: Resource, +})); + +vi.mock("../widget/widget_label", () => ({ + default: ({ label }) =>
{label}
, +})); + +import Node from "./node"; + +describe("components/widgets/longhorn/node", () => { + it("passes calculated percentage and renders label when enabled", () => { + const data = { node: { id: "n1", available: 25, maximum: 100 } }; + + render(); + + expect(Resource).toHaveBeenCalledTimes(1); + const callProps = Resource.mock.calls[0][0]; + expect(callProps.percentage).toBe(75); + expect(callProps.expanded).toBe(true); + expect(screen.getByTestId("lh-label")).toHaveTextContent("n1"); + }); +}); diff --git a/src/components/widgets/queue/queueEntry.test.jsx b/src/components/widgets/queue/queueEntry.test.jsx new file mode 100644 index 000000000..0cd2592a6 --- /dev/null +++ b/src/components/widgets/queue/queueEntry.test.jsx @@ -0,0 +1,20 @@ +// @vitest-environment jsdom + +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import QueueEntry from "./queueEntry"; + +describe("components/widgets/queue/queueEntry", () => { + it("renders title and progress width", () => { + const { container } = render( + , + ); + + expect(screen.getByText("Download")).toBeInTheDocument(); + expect(screen.getByText("1GB - Downloading - 1m")).toBeInTheDocument(); + + const bar = container.querySelector("div[style]"); + expect(bar.style.width).toBe("42%"); + }); +}); diff --git a/src/components/widgets/resources/usage-bar.test.jsx b/src/components/widgets/resources/usage-bar.test.jsx new file mode 100644 index 000000000..09f28bf26 --- /dev/null +++ b/src/components/widgets/resources/usage-bar.test.jsx @@ -0,0 +1,18 @@ +// @vitest-environment jsdom + +import { render } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import UsageBar from "./usage-bar"; + +describe("components/widgets/resources/usage-bar", () => { + it("normalizes percent to [0, 100] and applies width style", () => { + const { container: c0 } = render(); + const inner0 = c0.querySelector("div > div > div"); + expect(inner0.style.width).toBe("0%"); + + const { container: c1 } = render(); + const inner1 = c1.querySelector("div > div > div"); + expect(inner1.style.width).toBe("100%"); + }); +}); diff --git a/src/components/widgets/widget.test.jsx b/src/components/widgets/widget.test.jsx new file mode 100644 index 000000000..9be466598 --- /dev/null +++ b/src/components/widgets/widget.test.jsx @@ -0,0 +1,58 @@ +// @vitest-environment jsdom + +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +const { dynamic } = vi.hoisted(() => { + const dynamic = vi.fn((loader, opts) => { + const loaderStr = loader.toString(); + const ssr = opts?.ssr === false ? "false" : "true"; + + return function DynamicWidget({ options }) { + return ( +
+ ); + }; + }); + + return { dynamic }; +}); + +vi.mock("next/dynamic", () => ({ + default: dynamic, +})); + +vi.mock("components/errorboundry", () => ({ + default: ({ children }) =>
{children}
, +})); + +import Widget from "./widget"; + +describe("components/widgets/widget", () => { + it("renders the mapped widget component and forwards style into options", () => { + render( + , + ); + + const boundary = screen.getByTestId("error-boundary"); + expect(boundary).toBeInTheDocument(); + + const el = screen.getByTestId("dynamic-widget"); + expect(el.getAttribute("data-loader")).toContain("search/search"); + + const forwarded = JSON.parse(el.getAttribute("data-options")); + expect(forwarded.provider).toEqual(["google"]); + expect(forwarded.style).toEqual({ header: "boxedWidgets" }); + }); + + it("renders a missing message when widget type is unknown", () => { + render(); + expect(screen.getByText("Missing")).toBeInTheDocument(); + expect(screen.getByText("nope")).toBeInTheDocument(); + }); +}); diff --git a/src/components/widgets/widget/container.test.jsx b/src/components/widgets/widget/container.test.jsx new file mode 100644 index 000000000..db74bdf5d --- /dev/null +++ b/src/components/widgets/widget/container.test.jsx @@ -0,0 +1,76 @@ +// @vitest-environment jsdom + +import { screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import { renderWithProviders } from "test-utils/render-with-providers"; + +import Container, { getAllClasses } from "./container"; +import PrimaryText from "./primary_text"; +import Raw from "./raw"; +import SecondaryText from "./secondary_text"; +import WidgetIcon from "./widget_icon"; + +function FakeIcon(props) { + return ; +} + +describe("components/widgets/widget/container", () => { + it("getAllClasses supports boxedWidgets + cardBlur and right alignment", () => { + const boxed = getAllClasses({ style: { header: "boxedWidgets", cardBlur: "md" } }, "x"); + expect(boxed).toContain("backdrop-blur-md"); + expect(boxed).toContain("x"); + + const right = getAllClasses({ style: { isRightAligned: true } }, "y"); + expect(right).toContain("justify-center"); + expect(right).toContain("y"); + expect(right).not.toContain("max-w:full"); + }); + + it("renders an anchor when href is provided and prefers options.target over settings.target", () => { + renderWithProviders( + + + P + S + +
B
+
+
, + { settings: { target: "_blank" } }, + ); + + const link = screen.getByRole("link"); + expect(link.getAttribute("href")).toBe("http://example"); + expect(link.getAttribute("target")).toBe("_self"); + expect(screen.getByTestId("fake-icon")).toBeInTheDocument(); + expect(screen.getByText("P")).toBeInTheDocument(); + expect(screen.getByText("S")).toBeInTheDocument(); + expect(screen.getByTestId("bottom")).toBeInTheDocument(); + }); + + it("renders only bottom content when children are a single Raw element", () => { + const { container } = renderWithProviders( + + +
B
+
+
, + { settings: { target: "_self" } }, + ); + + expect(container.querySelector(".widget-inner")).toBeNull(); + expect(screen.getByTestId("only-bottom")).toBeInTheDocument(); + }); + + it("does not crash when clicked (href case is normal link)", () => { + renderWithProviders( + + +
Bottom
+
+
, + { settings: { target: "_self" } }, + ); + }); +}); diff --git a/src/components/widgets/widget/container_button.test.jsx b/src/components/widgets/widget/container_button.test.jsx new file mode 100644 index 000000000..a24d07064 --- /dev/null +++ b/src/components/widgets/widget/container_button.test.jsx @@ -0,0 +1,23 @@ +// @vitest-environment jsdom + +import { fireEvent, render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +import ContainerButton from "./container_button"; +import Raw from "./raw"; + +describe("components/widgets/widget/container_button", () => { + it("invokes callback on click", () => { + const cb = vi.fn(); + render( + + +
child
+
+
, + ); + + fireEvent.click(screen.getByRole("button")); + expect(cb).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/components/widgets/widget/container_form.test.jsx b/src/components/widgets/widget/container_form.test.jsx new file mode 100644 index 000000000..56b9d9a9a --- /dev/null +++ b/src/components/widgets/widget/container_form.test.jsx @@ -0,0 +1,23 @@ +// @vitest-environment jsdom + +import { fireEvent, render } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +import ContainerForm from "./container_form"; + +describe("components/widgets/widget/container_form", () => { + it("calls callback on submit", () => { + const cb = vi.fn((e) => e.preventDefault()); + + const { container } = render( + + {[
child
]} +
, + ); + + const form = container.querySelector("form"); + fireEvent.submit(form); + + expect(cb).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/components/widgets/widget/container_link.test.jsx b/src/components/widgets/widget/container_link.test.jsx new file mode 100644 index 000000000..5c4f15b34 --- /dev/null +++ b/src/components/widgets/widget/container_link.test.jsx @@ -0,0 +1,24 @@ +// @vitest-environment jsdom + +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import ContainerLink from "./container_link"; +import Raw from "./raw"; + +describe("components/widgets/widget/container_link", () => { + it("renders an anchor using href or url", () => { + const { rerender } = render(); + expect(screen.getByRole("link").getAttribute("href")).toBe("http://a"); + expect(screen.getByRole("link").getAttribute("target")).toBe("_self"); + + rerender( + + +
child
+
+
, + ); + expect(screen.getByRole("link").getAttribute("href")).toBe("http://b"); + }); +}); diff --git a/src/components/widgets/widget/error.test.jsx b/src/components/widgets/widget/error.test.jsx new file mode 100644 index 000000000..b82c587ae --- /dev/null +++ b/src/components/widgets/widget/error.test.jsx @@ -0,0 +1,15 @@ +// @vitest-environment jsdom + +import { screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import { renderWithProviders } from "test-utils/render-with-providers"; + +import Error from "./error"; + +describe("components/widgets/widget/error", () => { + it("renders the api_error message", () => { + renderWithProviders(, { settings: { target: "_self" } }); + expect(screen.getByText("widget.api_error")).toBeInTheDocument(); + }); +}); diff --git a/src/components/widgets/widget/primary_text.test.jsx b/src/components/widgets/widget/primary_text.test.jsx new file mode 100644 index 000000000..962e2bf4b --- /dev/null +++ b/src/components/widgets/widget/primary_text.test.jsx @@ -0,0 +1,13 @@ +// @vitest-environment jsdom + +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import PrimaryText from "./primary_text"; + +describe("components/widgets/widget/primary_text", () => { + it("renders children", () => { + render(hello); + expect(screen.getByText("hello")).toBeInTheDocument(); + }); +}); diff --git a/src/components/widgets/widget/raw.test.jsx b/src/components/widgets/widget/raw.test.jsx new file mode 100644 index 000000000..f5f175ee1 --- /dev/null +++ b/src/components/widgets/widget/raw.test.jsx @@ -0,0 +1,20 @@ +// @vitest-environment jsdom + +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import Raw from "./raw"; + +describe("components/widgets/widget/raw", () => { + it("renders nested Raw content", () => { + render( + + +
inner
+
+
, + ); + + expect(screen.getByText("inner")).toBeInTheDocument(); + }); +}); diff --git a/src/components/widgets/widget/resource.test.jsx b/src/components/widgets/widget/resource.test.jsx new file mode 100644 index 000000000..4afe066c5 --- /dev/null +++ b/src/components/widgets/widget/resource.test.jsx @@ -0,0 +1,38 @@ +// @vitest-environment jsdom + +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +const { UsageBar } = vi.hoisted(() => ({ + UsageBar: vi.fn(({ percent }) =>
), +})); + +vi.mock("../resources/usage-bar", () => ({ + default: UsageBar, +})); + +import Resource from "./resource"; + +function FakeIcon(props) { + return ; +} + +describe("components/widgets/widget/resource", () => { + it("renders icon/value/label and shows usage bar when percentage is set", () => { + render(); + + expect(screen.getByTestId("resource-icon")).toBeInTheDocument(); + expect(screen.getByText("v")).toBeInTheDocument(); + expect(screen.getByText("l")).toBeInTheDocument(); + expect(screen.getByTestId("usagebar").getAttribute("data-percent")).toBe("0"); + }); + + it("renders expanded values when expanded", () => { + render( + , + ); + + expect(screen.getByText("ev")).toBeInTheDocument(); + expect(screen.getByText("el")).toBeInTheDocument(); + }); +}); diff --git a/src/components/widgets/widget/resources.test.jsx b/src/components/widgets/widget/resources.test.jsx new file mode 100644 index 000000000..4b71f1598 --- /dev/null +++ b/src/components/widgets/widget/resources.test.jsx @@ -0,0 +1,31 @@ +// @vitest-environment jsdom + +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import Resource from "./resource"; +import Resources from "./resources"; +import WidgetLabel from "./widget_label"; + +function FakeIcon() { + return ; +} + +describe("components/widgets/widget/resources", () => { + it("filters children to Resource + WidgetLabel and wraps them in a link", () => { + render( + + {[ + , + , +
Other
, + ]} +
, + ); + + expect(screen.getByRole("link").getAttribute("href")).toBe("http://example"); + expect(screen.getByText("v")).toBeInTheDocument(); + expect(screen.getByText("Label")).toBeInTheDocument(); + expect(screen.queryByText("Other")).toBeNull(); + }); +}); diff --git a/src/components/widgets/widget/secondary_text.test.jsx b/src/components/widgets/widget/secondary_text.test.jsx new file mode 100644 index 000000000..4e80962d8 --- /dev/null +++ b/src/components/widgets/widget/secondary_text.test.jsx @@ -0,0 +1,13 @@ +// @vitest-environment jsdom + +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import SecondaryText from "./secondary_text"; + +describe("components/widgets/widget/secondary_text", () => { + it("renders children", () => { + render(world); + expect(screen.getByText("world")).toBeInTheDocument(); + }); +}); diff --git a/src/components/widgets/widget/widget_icon.test.jsx b/src/components/widgets/widget/widget_icon.test.jsx new file mode 100644 index 000000000..cd073ee47 --- /dev/null +++ b/src/components/widgets/widget/widget_icon.test.jsx @@ -0,0 +1,30 @@ +// @vitest-environment jsdom + +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import WidgetIcon from "./widget_icon"; + +function FakeIcon(props) { + return ; +} + +describe("components/widgets/widget/widget_icon", () => { + it("applies size classes and pulse animation", () => { + render( + <> + + + + + , + ); + + const icons = screen.getAllByTestId("icon"); + expect(icons[0].getAttribute("class")).toContain("w-5 h-5"); + expect(icons[1].getAttribute("class")).toContain("w-6 h-6"); + expect(icons[2].getAttribute("class")).toContain("w-8 h-8"); + expect(icons[2].getAttribute("class")).toContain("animate-pulse"); + expect(icons[3].getAttribute("class")).toContain("w-10 h-10"); + }); +}); diff --git a/src/components/widgets/widget/widget_label.test.jsx b/src/components/widgets/widget/widget_label.test.jsx new file mode 100644 index 000000000..920bd722b --- /dev/null +++ b/src/components/widgets/widget/widget_label.test.jsx @@ -0,0 +1,13 @@ +// @vitest-environment jsdom + +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import WidgetLabel from "./widget_label"; + +describe("components/widgets/widget/widget_label", () => { + it("renders label text", () => { + render(); + expect(screen.getByText("Label A")).toBeInTheDocument(); + }); +});