diff --git a/src/widgets/glances/components/block.test.jsx b/src/widgets/glances/components/block.test.jsx
new file mode 100644
index 000000000..5a2835975
--- /dev/null
+++ b/src/widgets/glances/components/block.test.jsx
@@ -0,0 +1,21 @@
+// @vitest-environment jsdom
+
+import { render, screen } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+
+import Block from "./block";
+
+describe("widgets/glances/components/block", () => {
+ it("renders children with the given absolute position classes", () => {
+ render(
+
+ hi
+ ,
+ );
+
+ const el = screen.getByText("hi").parentElement;
+ expect(el).toHaveClass("absolute");
+ expect(el).toHaveClass("top-1");
+ expect(el).toHaveClass("left-2");
+ });
+});
diff --git a/src/widgets/glances/components/chart.test.jsx b/src/widgets/glances/components/chart.test.jsx
new file mode 100644
index 000000000..9d0d2ee40
--- /dev/null
+++ b/src/widgets/glances/components/chart.test.jsx
@@ -0,0 +1,31 @@
+// @vitest-environment jsdom
+
+import { render, screen } from "@testing-library/react";
+import React from "react";
+import { describe, expect, it, vi } from "vitest";
+
+vi.mock("recharts", () => ({
+ ResponsiveContainer: ({ children }) =>
{children}
,
+ AreaChart: ({ children }) => {
+ // Filter out raw SVG elements (defs/linearGradient/stop) so jsdom doesn't warn.
+ const kept = React.Children.toArray(children).filter((child) => typeof child?.type === "function");
+ return {kept}
;
+ },
+ Area: ({ name, dataKey }) => ,
+ Tooltip: ({ content }) => {content}
,
+}));
+
+import Chart from "./chart";
+
+describe("widgets/glances/components/chart", () => {
+ it("renders a single-series chart scaffold", () => {
+ render( String(v)} label={["Series"]} />);
+
+ expect(screen.getByTestId("ResponsiveContainer")).toBeInTheDocument();
+ expect(screen.getByTestId("AreaChart")).toBeInTheDocument();
+ const area = screen.getByTestId("Area");
+ expect(area).toHaveAttribute("data-name", "Series");
+ expect(area).toHaveAttribute("data-key", "value");
+ expect(screen.getByTestId("Tooltip")).toBeInTheDocument();
+ });
+});
diff --git a/src/widgets/glances/components/chart_dual.test.jsx b/src/widgets/glances/components/chart_dual.test.jsx
new file mode 100644
index 000000000..c914d67cb
--- /dev/null
+++ b/src/widgets/glances/components/chart_dual.test.jsx
@@ -0,0 +1,38 @@
+// @vitest-environment jsdom
+
+import { render, screen } from "@testing-library/react";
+import React from "react";
+import { describe, expect, it, vi } from "vitest";
+
+vi.mock("recharts", () => ({
+ ResponsiveContainer: ({ children }) => {children}
,
+ AreaChart: ({ children, stackOffset }) => (
+
+ {
+ // Filter out raw SVG elements (defs/linearGradient/stop) so jsdom doesn't warn.
+ React.Children.toArray(children).filter((child) => typeof child?.type === "function")
+ }
+
+ ),
+ Area: ({ name, dataKey }) => ,
+ Tooltip: ({ content }) => {content}
,
+}));
+
+import ChartDual from "./chart_dual";
+
+describe("widgets/glances/components/chart_dual", () => {
+ it("renders a dual-series chart scaffold", () => {
+ render(
+ String(v)} label={["A", "B"]} stackOffset="expand" />,
+ );
+
+ expect(screen.getByTestId("ResponsiveContainer")).toBeInTheDocument();
+ const chart = screen.getByTestId("AreaChart");
+ expect(chart).toHaveAttribute("data-stackoffset", "expand");
+
+ const areas = screen.getAllByTestId("Area");
+ expect(areas).toHaveLength(2);
+ expect(areas[0]).toHaveAttribute("data-key", "a");
+ expect(areas[1]).toHaveAttribute("data-key", "b");
+ });
+});
diff --git a/src/widgets/glances/components/container.test.jsx b/src/widgets/glances/components/container.test.jsx
new file mode 100644
index 000000000..f3cf5209b
--- /dev/null
+++ b/src/widgets/glances/components/container.test.jsx
@@ -0,0 +1,37 @@
+// @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 from "./container";
+
+describe("widgets/glances/components/container", () => {
+ it("renders children and chart spacing when not in error state", () => {
+ renderWithProviders(
+
+ child
+ ,
+ { settings: { hideErrors: false } },
+ );
+
+ expect(screen.getByText("child")).toBeInTheDocument();
+ expect(document.querySelector(".service-container")).toBeTruthy();
+ expect(document.querySelector(".h-\\[68px\\]")).toBeTruthy();
+ });
+
+ it("renders nothing when error is present and errors are hidden", () => {
+ const { container } = renderWithProviders(, {
+ settings: { hideErrors: true },
+ });
+ expect(container.firstChild).toBeNull();
+ });
+
+ it("renders the error message when error is present and errors are not hidden", () => {
+ renderWithProviders(, {
+ settings: { hideErrors: false },
+ });
+ expect(screen.getByText("widget.api_error")).toBeInTheDocument();
+ });
+});
diff --git a/src/widgets/glances/components/custom_tooltip.test.jsx b/src/widgets/glances/components/custom_tooltip.test.jsx
new file mode 100644
index 000000000..ea1144d79
--- /dev/null
+++ b/src/widgets/glances/components/custom_tooltip.test.jsx
@@ -0,0 +1,29 @@
+// @vitest-environment jsdom
+
+import { render, screen } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+
+import CustomTooltip from "./custom_tooltip";
+
+describe("widgets/glances/components/custom_tooltip", () => {
+ it("returns null when inactive", () => {
+ const { container } = render( String(v)} />);
+ expect(container.firstChild).toBeNull();
+ });
+
+ it("renders formatted values and series names when active", () => {
+ render(
+ `v=${v}`}
+ payload={[
+ { value: 1, name: "A" },
+ { value: 2, name: "B" },
+ ]}
+ />,
+ );
+
+ expect(screen.getByText("v=1 A")).toBeInTheDocument();
+ expect(screen.getByText("v=2 B")).toBeInTheDocument();
+ });
+});
diff --git a/src/widgets/glances/components/error.test.jsx b/src/widgets/glances/components/error.test.jsx
new file mode 100644
index 000000000..7cdd4817a
--- /dev/null
+++ b/src/widgets/glances/components/error.test.jsx
@@ -0,0 +1,13 @@
+// @vitest-environment jsdom
+
+import { render, screen } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+
+import Error from "./error";
+
+describe("widgets/glances/components/error", () => {
+ it("renders the standard widget api error message", () => {
+ render();
+ expect(screen.getByText("widget.api_error")).toBeInTheDocument();
+ });
+});
diff --git a/src/widgets/glances/metrics/containers.test.jsx b/src/widgets/glances/metrics/containers.test.jsx
new file mode 100644
index 000000000..b3a8dbeb4
--- /dev/null
+++ b/src/widgets/glances/metrics/containers.test.jsx
@@ -0,0 +1,24 @@
+// @vitest-environment jsdom
+
+import { screen } from "@testing-library/react";
+import { describe, expect, it, vi } from "vitest";
+
+import { renderWithProviders } from "test-utils/render-with-providers";
+
+const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() }));
+vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI }));
+
+// Avoid pulling Next/Image + ThemeContext requirements into these unit tests.
+vi.mock("components/resolvedicon", () => ({ default: () => }));
+
+import Component from "./containers";
+
+describe("widgets/glances/metrics/containers", () => {
+ it("renders a placeholder while loading", () => {
+ useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
+ renderWithProviders(, {
+ settings: { hideErrors: false },
+ });
+ expect(screen.getByText("-")).toBeInTheDocument();
+ });
+});
diff --git a/src/widgets/glances/metrics/cpu.test.jsx b/src/widgets/glances/metrics/cpu.test.jsx
new file mode 100644
index 000000000..4ef48a7b8
--- /dev/null
+++ b/src/widgets/glances/metrics/cpu.test.jsx
@@ -0,0 +1,22 @@
+// @vitest-environment jsdom
+
+import { screen } from "@testing-library/react";
+import { describe, expect, it, vi } from "vitest";
+
+import { renderWithProviders } from "test-utils/render-with-providers";
+
+const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() }));
+vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI }));
+vi.mock("next/dynamic", () => ({ default: () => () => null }));
+
+import Component from "./cpu";
+
+describe("widgets/glances/metrics/cpu", () => {
+ it("renders a placeholder while loading", () => {
+ useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
+ renderWithProviders(, {
+ settings: { hideErrors: false },
+ });
+ expect(screen.getByText("-")).toBeInTheDocument();
+ });
+});
diff --git a/src/widgets/glances/metrics/disk.test.jsx b/src/widgets/glances/metrics/disk.test.jsx
new file mode 100644
index 000000000..f240af912
--- /dev/null
+++ b/src/widgets/glances/metrics/disk.test.jsx
@@ -0,0 +1,25 @@
+// @vitest-environment jsdom
+
+import { screen } from "@testing-library/react";
+import { describe, expect, it, vi } from "vitest";
+
+import { renderWithProviders } from "test-utils/render-with-providers";
+
+const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() }));
+vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI }));
+vi.mock("next/dynamic", () => ({ default: () => () => null }));
+
+import Component from "./disk";
+
+describe("widgets/glances/metrics/disk", () => {
+ it("renders a placeholder while loading", () => {
+ useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
+ renderWithProviders(
+ ,
+ {
+ settings: { hideErrors: false },
+ },
+ );
+ expect(screen.getByText("-")).toBeInTheDocument();
+ });
+});
diff --git a/src/widgets/glances/metrics/fs.test.jsx b/src/widgets/glances/metrics/fs.test.jsx
new file mode 100644
index 000000000..661349e04
--- /dev/null
+++ b/src/widgets/glances/metrics/fs.test.jsx
@@ -0,0 +1,25 @@
+// @vitest-environment jsdom
+
+import { screen } from "@testing-library/react";
+import { describe, expect, it, vi } from "vitest";
+
+import { renderWithProviders } from "test-utils/render-with-providers";
+
+const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() }));
+vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI }));
+vi.mock("next/dynamic", () => ({ default: () => () => null }));
+
+import Component from "./fs";
+
+describe("widgets/glances/metrics/fs", () => {
+ it("renders a placeholder while loading", () => {
+ useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
+ renderWithProviders(
+ ,
+ {
+ settings: { hideErrors: false },
+ },
+ );
+ expect(screen.getByText("-")).toBeInTheDocument();
+ });
+});
diff --git a/src/widgets/glances/metrics/gpu.test.jsx b/src/widgets/glances/metrics/gpu.test.jsx
new file mode 100644
index 000000000..8cb2d8cc4
--- /dev/null
+++ b/src/widgets/glances/metrics/gpu.test.jsx
@@ -0,0 +1,25 @@
+// @vitest-environment jsdom
+
+import { screen } from "@testing-library/react";
+import { describe, expect, it, vi } from "vitest";
+
+import { renderWithProviders } from "test-utils/render-with-providers";
+
+const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() }));
+vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI }));
+vi.mock("next/dynamic", () => ({ default: () => () => null }));
+
+import Component from "./gpu";
+
+describe("widgets/glances/metrics/gpu", () => {
+ it("renders a placeholder while loading", () => {
+ useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
+ renderWithProviders(
+ ,
+ {
+ settings: { hideErrors: false },
+ },
+ );
+ expect(screen.getByText("-")).toBeInTheDocument();
+ });
+});
diff --git a/src/widgets/glances/metrics/info.test.jsx b/src/widgets/glances/metrics/info.test.jsx
new file mode 100644
index 000000000..51a33f8e5
--- /dev/null
+++ b/src/widgets/glances/metrics/info.test.jsx
@@ -0,0 +1,20 @@
+// @vitest-environment jsdom
+
+import { describe, expect, it, vi } from "vitest";
+
+import { renderWithProviders } from "test-utils/render-with-providers";
+
+const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() }));
+vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI }));
+
+import Component from "./info";
+
+describe("widgets/glances/metrics/info", () => {
+ it("renders a placeholder while loading", () => {
+ useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
+ renderWithProviders(, {
+ settings: { hideErrors: false },
+ });
+ expect(document.querySelector(".service-container")).toBeTruthy();
+ });
+});
diff --git a/src/widgets/glances/metrics/memory.test.jsx b/src/widgets/glances/metrics/memory.test.jsx
new file mode 100644
index 000000000..9b94cbaad
--- /dev/null
+++ b/src/widgets/glances/metrics/memory.test.jsx
@@ -0,0 +1,22 @@
+// @vitest-environment jsdom
+
+import { screen } from "@testing-library/react";
+import { describe, expect, it, vi } from "vitest";
+
+import { renderWithProviders } from "test-utils/render-with-providers";
+
+const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() }));
+vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI }));
+vi.mock("next/dynamic", () => ({ default: () => () => null }));
+
+import Component from "./memory";
+
+describe("widgets/glances/metrics/memory", () => {
+ it("renders a placeholder while loading", () => {
+ useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
+ renderWithProviders(, {
+ settings: { hideErrors: false },
+ });
+ expect(screen.getByText("-")).toBeInTheDocument();
+ });
+});
diff --git a/src/widgets/glances/metrics/net.test.jsx b/src/widgets/glances/metrics/net.test.jsx
new file mode 100644
index 000000000..9ed25586d
--- /dev/null
+++ b/src/widgets/glances/metrics/net.test.jsx
@@ -0,0 +1,23 @@
+// @vitest-environment jsdom
+
+import { screen } from "@testing-library/react";
+import { describe, expect, it, vi } from "vitest";
+
+import { renderWithProviders } from "test-utils/render-with-providers";
+
+const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() }));
+vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI }));
+vi.mock("next/dynamic", () => ({ default: () => () => null }));
+
+import Component from "./net";
+
+describe("widgets/glances/metrics/net", () => {
+ it("renders a placeholder while loading", () => {
+ useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
+ renderWithProviders(
+ ,
+ { settings: { hideErrors: false } },
+ );
+ expect(screen.getByText("-")).toBeInTheDocument();
+ });
+});
diff --git a/src/widgets/glances/metrics/process.test.jsx b/src/widgets/glances/metrics/process.test.jsx
new file mode 100644
index 000000000..9a3ded419
--- /dev/null
+++ b/src/widgets/glances/metrics/process.test.jsx
@@ -0,0 +1,22 @@
+// @vitest-environment jsdom
+
+import { screen } from "@testing-library/react";
+import { describe, expect, it, vi } from "vitest";
+
+import { renderWithProviders } from "test-utils/render-with-providers";
+
+const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() }));
+vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI }));
+vi.mock("components/resolvedicon", () => ({ default: () => }));
+
+import Component from "./process";
+
+describe("widgets/glances/metrics/process", () => {
+ it("renders a placeholder while loading", () => {
+ useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
+ renderWithProviders(, {
+ settings: { hideErrors: false },
+ });
+ expect(screen.getByText("-")).toBeInTheDocument();
+ });
+});
diff --git a/src/widgets/glances/metrics/sensor.test.jsx b/src/widgets/glances/metrics/sensor.test.jsx
new file mode 100644
index 000000000..4dee7a9cb
--- /dev/null
+++ b/src/widgets/glances/metrics/sensor.test.jsx
@@ -0,0 +1,25 @@
+// @vitest-environment jsdom
+
+import { screen } from "@testing-library/react";
+import { describe, expect, it, vi } from "vitest";
+
+import { renderWithProviders } from "test-utils/render-with-providers";
+
+const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() }));
+vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI }));
+vi.mock("next/dynamic", () => ({ default: () => () => null }));
+
+import Component from "./sensor";
+
+describe("widgets/glances/metrics/sensor", () => {
+ it("renders a placeholder while loading", () => {
+ useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
+ renderWithProviders(
+ ,
+ {
+ settings: { hideErrors: false },
+ },
+ );
+ expect(screen.getByText("-")).toBeInTheDocument();
+ });
+});