From a9f284548f10f95d3261390548430f5300d2463d Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Tue, 3 Feb 2026 11:42:32 -0800
Subject: [PATCH] Fix linkwarden widget stats + add component test
---
src/widgets/linkwarden/component.jsx | 36 ++++------
src/widgets/linkwarden/component.test.jsx | 81 +++++++++++++++++++++++
2 files changed, 93 insertions(+), 24 deletions(-)
create mode 100644 src/widgets/linkwarden/component.test.jsx
diff --git a/src/widgets/linkwarden/component.jsx b/src/widgets/linkwarden/component.jsx
index b2b0d91a8..d2bb40eb9 100644
--- a/src/widgets/linkwarden/component.jsx
+++ b/src/widgets/linkwarden/component.jsx
@@ -1,35 +1,23 @@
import Block from "components/services/widget/block";
import Container from "components/services/widget/container";
-import { useEffect, useState } from "react";
import useWidgetAPI from "utils/proxy/use-widget-api";
export default function Component({ service }) {
const { widget } = service;
- const [stats, setStats] = useState({
- totalLinks: null,
- collections: { total: null },
- tags: { total: null },
- });
-
const { data: collectionsStatsData, error: collectionsStatsError } = useWidgetAPI(widget, "collections");
const { data: tagsStatsData, error: tagsStatsError } = useWidgetAPI(widget, "tags");
- useEffect(() => {
- if (collectionsStatsData?.response && tagsStatsData?.response) {
- setStats({
- // eslint-disable-next-line no-underscore-dangle
- totalLinks: collectionsStatsData.response.reduce((sum, collection) => sum + (collection._count?.links || 0), 0),
- collections: {
- total: collectionsStatsData.response.length,
- },
- tags: {
- total: tagsStatsData.response.length,
- },
- });
- }
- }, [collectionsStatsData, tagsStatsData]);
+ // Some APIs return raw arrays, others wrap the payload (e.g. { response: [...] }).
+ const collections = collectionsStatsData?.response ?? collectionsStatsData;
+ const tags = tagsStatsData?.response ?? tagsStatsData;
+
+ const totalLinks = Array.isArray(collections)
+ ? collections.reduce((sum, collection) => sum + (collection._count?.links || 0), 0)
+ : null;
+ const collectionsTotal = Array.isArray(collections) ? collections.length : null;
+ const tagsTotal = Array.isArray(tags) ? tags.length : null;
if (collectionsStatsError || tagsStatsError) {
return ;
@@ -47,9 +35,9 @@ export default function Component({ service }) {
return (
-
-
-
+
+
+
);
}
diff --git a/src/widgets/linkwarden/component.test.jsx b/src/widgets/linkwarden/component.test.jsx
new file mode 100644
index 000000000..9b565a6ae
--- /dev/null
+++ b/src/widgets/linkwarden/component.test.jsx
@@ -0,0 +1,81 @@
+// @vitest-environment jsdom
+
+import { screen } from "@testing-library/react";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+import { renderWithProviders } from "test-utils/render-with-providers";
+import { findServiceBlockByLabel } from "test-utils/widget-assertions";
+
+const { useWidgetAPI } = vi.hoisted(() => ({ useWidgetAPI: vi.fn() }));
+vi.mock("utils/proxy/use-widget-api", () => ({ default: useWidgetAPI }));
+
+import Component from "./component";
+
+function expectBlockValue(container, label, value) {
+ const block = findServiceBlockByLabel(container, label);
+ expect(block, `missing block for ${label}`).toBeTruthy();
+ expect(block.textContent).toContain(String(value));
+}
+
+describe("widgets/linkwarden/component", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it("renders placeholders while loading", () => {
+ useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
+
+ const { container } = renderWithProviders(, {
+ settings: { hideErrors: false },
+ });
+
+ expect(container.querySelectorAll(".service-block")).toHaveLength(3);
+ expect(screen.getByText("linkwarden.links")).toBeInTheDocument();
+ expect(screen.getByText("linkwarden.collections")).toBeInTheDocument();
+ expect(screen.getByText("linkwarden.tags")).toBeInTheDocument();
+ });
+
+ it("renders error UI when either endpoint errors", () => {
+ useWidgetAPI.mockImplementation((_widget, endpoint) => {
+ if (endpoint === "tags") return { data: undefined, error: { message: "nope" } };
+ return { data: undefined, error: undefined };
+ });
+
+ renderWithProviders(, {
+ settings: { hideErrors: false },
+ });
+
+ expect(screen.getAllByText(/widget\.api_error/i).length).toBeGreaterThan(0);
+ expect(screen.getByText("nope")).toBeInTheDocument();
+ });
+
+ it("computes totals from collections + tags arrays", async () => {
+ useWidgetAPI.mockImplementation((_widget, endpoint) => {
+ if (endpoint === "collections") {
+ return {
+ data: [
+ // eslint-disable-next-line no-underscore-dangle
+ { _count: { links: 2 } },
+ // eslint-disable-next-line no-underscore-dangle
+ { _count: { links: 3 } },
+ ],
+ error: undefined,
+ };
+ }
+
+ if (endpoint === "tags") {
+ return { data: [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }], error: undefined };
+ }
+
+ return { data: undefined, error: undefined };
+ });
+
+ const { container } = renderWithProviders(, {
+ settings: { hideErrors: false },
+ });
+
+ expectBlockValue(container, "linkwarden.links", 5);
+ expectBlockValue(container, "linkwarden.collections", 2);
+ expectBlockValue(container, "linkwarden.tags", 4);
+ });
+});