mirror of
https://github.com/gethomepage/homepage.git
synced 2026-02-08 00:40:52 +08:00
test: add widget shared component coverage
This commit is contained in:
27
src/components/widgets/kubernetes/node.test.jsx
Normal file
27
src/components/widgets/kubernetes/node.test.jsx
Normal file
@@ -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(<Node type="cluster" options={{ showLabel: true, label: "Cluster A" }} data={data} />);
|
||||
|
||||
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(<Node type="node" options={{ showLabel: true }} data={data} />);
|
||||
|
||||
expect(screen.getByText("node-1")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
32
src/components/widgets/longhorn/node.test.jsx
Normal file
32
src/components/widgets/longhorn/node.test.jsx
Normal file
@@ -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 }) => <div data-testid="lh-resource">{children}</div>),
|
||||
}));
|
||||
|
||||
vi.mock("../widget/resource", () => ({
|
||||
default: Resource,
|
||||
}));
|
||||
|
||||
vi.mock("../widget/widget_label", () => ({
|
||||
default: ({ label }) => <div data-testid="lh-label">{label}</div>,
|
||||
}));
|
||||
|
||||
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(<Node data={{ node: data.node }} expanded labels />);
|
||||
|
||||
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");
|
||||
});
|
||||
});
|
||||
20
src/components/widgets/queue/queueEntry.test.jsx
Normal file
20
src/components/widgets/queue/queueEntry.test.jsx
Normal file
@@ -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(
|
||||
<QueueEntry title="Download" activity="Downloading" timeLeft="1m" progress={42} size="1GB" />,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Download")).toBeInTheDocument();
|
||||
expect(screen.getByText("1GB - Downloading - 1m")).toBeInTheDocument();
|
||||
|
||||
const bar = container.querySelector("div[style]");
|
||||
expect(bar.style.width).toBe("42%");
|
||||
});
|
||||
});
|
||||
18
src/components/widgets/resources/usage-bar.test.jsx
Normal file
18
src/components/widgets/resources/usage-bar.test.jsx
Normal file
@@ -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(<UsageBar percent={-5} />);
|
||||
const inner0 = c0.querySelector("div > div > div");
|
||||
expect(inner0.style.width).toBe("0%");
|
||||
|
||||
const { container: c1 } = render(<UsageBar percent={150} />);
|
||||
const inner1 = c1.querySelector("div > div > div");
|
||||
expect(inner1.style.width).toBe("100%");
|
||||
});
|
||||
});
|
||||
58
src/components/widgets/widget.test.jsx
Normal file
58
src/components/widgets/widget.test.jsx
Normal file
@@ -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 (
|
||||
<div
|
||||
data-testid="dynamic-widget"
|
||||
data-loader={loaderStr}
|
||||
data-ssr={ssr}
|
||||
data-options={JSON.stringify(options)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
return { dynamic };
|
||||
});
|
||||
|
||||
vi.mock("next/dynamic", () => ({
|
||||
default: dynamic,
|
||||
}));
|
||||
|
||||
vi.mock("components/errorboundry", () => ({
|
||||
default: ({ children }) => <div data-testid="error-boundary">{children}</div>,
|
||||
}));
|
||||
|
||||
import Widget from "./widget";
|
||||
|
||||
describe("components/widgets/widget", () => {
|
||||
it("renders the mapped widget component and forwards style into options", () => {
|
||||
render(
|
||||
<Widget widget={{ type: "search", options: { provider: ["google"] } }} style={{ header: "boxedWidgets" }} />,
|
||||
);
|
||||
|
||||
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(<Widget widget={{ type: "nope", options: {} }} style={{}} />);
|
||||
expect(screen.getByText("Missing")).toBeInTheDocument();
|
||||
expect(screen.getByText("nope")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
76
src/components/widgets/widget/container.test.jsx
Normal file
76
src/components/widgets/widget/container.test.jsx
Normal file
@@ -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 <svg data-testid="fake-icon" {...props} />;
|
||||
}
|
||||
|
||||
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(
|
||||
<Container options={{ href: "http://example", target: "_self" }}>
|
||||
<WidgetIcon icon={FakeIcon} />
|
||||
<PrimaryText>P</PrimaryText>
|
||||
<SecondaryText>S</SecondaryText>
|
||||
<Raw>
|
||||
<div data-testid="bottom">B</div>
|
||||
</Raw>
|
||||
</Container>,
|
||||
{ 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(
|
||||
<Container options={{}}>
|
||||
<Raw>
|
||||
<div data-testid="only-bottom">B</div>
|
||||
</Raw>
|
||||
</Container>,
|
||||
{ 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(
|
||||
<Container options={{ href: "http://example" }}>
|
||||
<Raw>
|
||||
<div>Bottom</div>
|
||||
</Raw>
|
||||
</Container>,
|
||||
{ settings: { target: "_self" } },
|
||||
);
|
||||
});
|
||||
});
|
||||
23
src/components/widgets/widget/container_button.test.jsx
Normal file
23
src/components/widgets/widget/container_button.test.jsx
Normal file
@@ -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(
|
||||
<ContainerButton options={{}} callback={cb}>
|
||||
<Raw>
|
||||
<div>child</div>
|
||||
</Raw>
|
||||
</ContainerButton>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button"));
|
||||
expect(cb).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
23
src/components/widgets/widget/container_form.test.jsx
Normal file
23
src/components/widgets/widget/container_form.test.jsx
Normal file
@@ -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(
|
||||
<ContainerForm options={{}} callback={cb}>
|
||||
{[<div key="c">child</div>]}
|
||||
</ContainerForm>,
|
||||
);
|
||||
|
||||
const form = container.querySelector("form");
|
||||
fireEvent.submit(form);
|
||||
|
||||
expect(cb).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
24
src/components/widgets/widget/container_link.test.jsx
Normal file
24
src/components/widgets/widget/container_link.test.jsx
Normal file
@@ -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(<ContainerLink options={{ href: "http://a" }} target="_self" />);
|
||||
expect(screen.getByRole("link").getAttribute("href")).toBe("http://a");
|
||||
expect(screen.getByRole("link").getAttribute("target")).toBe("_self");
|
||||
|
||||
rerender(
|
||||
<ContainerLink options={{ url: "http://b" }} target="_blank">
|
||||
<Raw>
|
||||
<div>child</div>
|
||||
</Raw>
|
||||
</ContainerLink>,
|
||||
);
|
||||
expect(screen.getByRole("link").getAttribute("href")).toBe("http://b");
|
||||
});
|
||||
});
|
||||
15
src/components/widgets/widget/error.test.jsx
Normal file
15
src/components/widgets/widget/error.test.jsx
Normal file
@@ -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(<Error options={{}} />, { settings: { target: "_self" } });
|
||||
expect(screen.getByText("widget.api_error")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
13
src/components/widgets/widget/primary_text.test.jsx
Normal file
13
src/components/widgets/widget/primary_text.test.jsx
Normal file
@@ -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(<PrimaryText>hello</PrimaryText>);
|
||||
expect(screen.getByText("hello")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
20
src/components/widgets/widget/raw.test.jsx
Normal file
20
src/components/widgets/widget/raw.test.jsx
Normal file
@@ -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(
|
||||
<Raw>
|
||||
<Raw>
|
||||
<div>inner</div>
|
||||
</Raw>
|
||||
</Raw>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("inner")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
38
src/components/widgets/widget/resource.test.jsx
Normal file
38
src/components/widgets/widget/resource.test.jsx
Normal file
@@ -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 }) => <div data-testid="usagebar" data-percent={String(percent)} />),
|
||||
}));
|
||||
|
||||
vi.mock("../resources/usage-bar", () => ({
|
||||
default: UsageBar,
|
||||
}));
|
||||
|
||||
import Resource from "./resource";
|
||||
|
||||
function FakeIcon(props) {
|
||||
return <svg data-testid="resource-icon" {...props} />;
|
||||
}
|
||||
|
||||
describe("components/widgets/widget/resource", () => {
|
||||
it("renders icon/value/label and shows usage bar when percentage is set", () => {
|
||||
render(<Resource icon={FakeIcon} value="v" label="l" percentage={0} />);
|
||||
|
||||
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(
|
||||
<Resource icon={FakeIcon} value="v" label="l" expanded expandedValue="ev" expandedLabel="el" percentage={10} />,
|
||||
);
|
||||
|
||||
expect(screen.getByText("ev")).toBeInTheDocument();
|
||||
expect(screen.getByText("el")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
31
src/components/widgets/widget/resources.test.jsx
Normal file
31
src/components/widgets/widget/resources.test.jsx
Normal file
@@ -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 <svg />;
|
||||
}
|
||||
|
||||
describe("components/widgets/widget/resources", () => {
|
||||
it("filters children to Resource + WidgetLabel and wraps them in a link", () => {
|
||||
render(
|
||||
<Resources options={{ href: "http://example" }} target="_self" additionalClassNames="x">
|
||||
{[
|
||||
<Resource key="r" icon={FakeIcon} value="v" label="l" />,
|
||||
<WidgetLabel key="w" label="Label" />,
|
||||
<div key="o">Other</div>,
|
||||
]}
|
||||
</Resources>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole("link").getAttribute("href")).toBe("http://example");
|
||||
expect(screen.getByText("v")).toBeInTheDocument();
|
||||
expect(screen.getByText("Label")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Other")).toBeNull();
|
||||
});
|
||||
});
|
||||
13
src/components/widgets/widget/secondary_text.test.jsx
Normal file
13
src/components/widgets/widget/secondary_text.test.jsx
Normal file
@@ -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(<SecondaryText>world</SecondaryText>);
|
||||
expect(screen.getByText("world")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
30
src/components/widgets/widget/widget_icon.test.jsx
Normal file
30
src/components/widgets/widget/widget_icon.test.jsx
Normal file
@@ -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 <svg data-testid="icon" {...props} />;
|
||||
}
|
||||
|
||||
describe("components/widgets/widget/widget_icon", () => {
|
||||
it("applies size classes and pulse animation", () => {
|
||||
render(
|
||||
<>
|
||||
<WidgetIcon icon={FakeIcon} size="s" />
|
||||
<WidgetIcon icon={FakeIcon} size="m" />
|
||||
<WidgetIcon icon={FakeIcon} size="l" pulse />
|
||||
<WidgetIcon icon={FakeIcon} size="xl" />
|
||||
</>,
|
||||
);
|
||||
|
||||
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");
|
||||
});
|
||||
});
|
||||
13
src/components/widgets/widget/widget_label.test.jsx
Normal file
13
src/components/widgets/widget/widget_label.test.jsx
Normal file
@@ -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(<WidgetLabel label="Label A" />);
|
||||
expect(screen.getByText("Label A")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user