mirror of
https://github.com/gethomepage/homepage.git
synced 2026-02-08 00:40:52 +08:00
test: cover quicklaunch + service widget helpers
This commit is contained in:
@@ -23,7 +23,7 @@ vi.mock("@headlessui/react", async () => {
|
||||
|
||||
const DisclosurePanel = React.forwardRef(function DisclosurePanel(props, ref) {
|
||||
// HeadlessUI uses a boolean `static` prop; avoid forwarding it to the DOM.
|
||||
const { static: isStatic, ...rest } = props; // eslint-disable-line no-unused-vars
|
||||
const { static: _static, ...rest } = props;
|
||||
return <div ref={ref} data-testid="disclosure-panel" {...rest} />;
|
||||
});
|
||||
|
||||
|
||||
84
src/components/quicklaunch.test.jsx
Normal file
84
src/components/quicklaunch.test.jsx
Normal file
@@ -0,0 +1,84 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { act, fireEvent, screen, waitFor } from "@testing-library/react";
|
||||
import { useState } from "react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { renderWithProviders } from "test-utils/render-with-providers";
|
||||
|
||||
const { useSWR } = vi.hoisted(() => ({ useSWR: vi.fn() }));
|
||||
|
||||
vi.mock("swr", () => ({
|
||||
default: useSWR,
|
||||
}));
|
||||
|
||||
vi.mock("./resolvedicon", () => ({
|
||||
default: function ResolvedIconMock() {
|
||||
return <div data-testid="resolved-icon" />;
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("./widgets/search/search", () => ({
|
||||
getStoredProvider: () => null,
|
||||
searchProviders: {
|
||||
duckduckgo: {
|
||||
name: "DuckDuckGo",
|
||||
url: "https://duckduckgo.example/?q=",
|
||||
suggestionUrl: "https://duckduckgo.example/ac/?q=",
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
import QuickLaunch from "./quicklaunch";
|
||||
|
||||
function Wrapper({ servicesAndBookmarks = [] } = {}) {
|
||||
const [searchString, setSearchString] = useState("");
|
||||
const [isOpen, setSearching] = useState(true);
|
||||
|
||||
return (
|
||||
<QuickLaunch
|
||||
servicesAndBookmarks={servicesAndBookmarks}
|
||||
searchString={searchString}
|
||||
setSearchString={setSearchString}
|
||||
isOpen={isOpen}
|
||||
setSearching={setSearching}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
describe("components/quicklaunch", () => {
|
||||
it("renders results for urls and opens the selected result on Enter", async () => {
|
||||
const openSpy = vi.spyOn(window, "open").mockImplementation(() => null);
|
||||
|
||||
useSWR.mockReturnValue({ data: {}, error: undefined });
|
||||
|
||||
renderWithProviders(<Wrapper />, {
|
||||
settings: {
|
||||
target: "_self",
|
||||
quicklaunch: {
|
||||
provider: "duckduckgo",
|
||||
showSearchSuggestions: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const input = screen.getByPlaceholderText("Search");
|
||||
await waitFor(() => expect(input).toHaveFocus());
|
||||
|
||||
fireEvent.change(input, { target: { value: "example.com" } });
|
||||
|
||||
expect(await screen.findByText("quicklaunch.visit URL")).toBeInTheDocument();
|
||||
expect(screen.getByText("DuckDuckGo quicklaunch.search")).toBeInTheDocument();
|
||||
|
||||
fireEvent.keyDown(input, { key: "Enter" });
|
||||
|
||||
await act(async () => {
|
||||
// Close/reset schedules timeouts (200ms + 300ms); flush them to avoid state updates after cleanup.
|
||||
await new Promise((r) => setTimeout(r, 350));
|
||||
});
|
||||
|
||||
expect(openSpy).toHaveBeenCalledWith("https://example.com/", "_self", "noreferrer");
|
||||
|
||||
openSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
@@ -8,8 +8,7 @@ export default function Widget({ widget, service }) {
|
||||
|
||||
const ServiceWidget = components[widget.type];
|
||||
|
||||
const fullService = Object.apply({}, service);
|
||||
fullService.widget = widget;
|
||||
const fullService = { ...service, widget };
|
||||
if (ServiceWidget) {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
|
||||
38
src/components/services/widget.test.jsx
Normal file
38
src/components/services/widget.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";
|
||||
|
||||
vi.mock("components/errorboundry", () => ({
|
||||
default: function ErrorBoundaryMock({ children }) {
|
||||
return <>{children}</>;
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("widgets/components", () => ({
|
||||
default: {
|
||||
mock: function MockWidget({ service }) {
|
||||
return (
|
||||
<div data-testid="mock-service-widget">
|
||||
{service.name}:{service.widget?.type}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
import Widget from "./widget";
|
||||
|
||||
describe("components/services/widget", () => {
|
||||
it("renders the mapped widget component and passes merged service.widget", () => {
|
||||
render(<Widget widget={{ type: "mock" }} service={{ name: "Svc" }} />);
|
||||
|
||||
expect(screen.getByTestId("mock-service-widget")).toHaveTextContent("Svc:mock");
|
||||
});
|
||||
|
||||
it("renders a missing widget message when the type is unknown", () => {
|
||||
render(<Widget widget={{ type: "nope" }} service={{ name: "Svc" }} />);
|
||||
|
||||
expect(screen.getByText("widget.missing_type")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
45
src/components/services/widget/error.test.jsx
Normal file
45
src/components/services/widget/error.test.jsx
Normal file
@@ -0,0 +1,45 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import Error from "./error";
|
||||
|
||||
describe("components/services/widget/error", () => {
|
||||
it("normalizes string errors to an object with a message", () => {
|
||||
render(<Error error="boom" />);
|
||||
|
||||
expect(screen.getByText((_, el) => el?.textContent === "widget.api_error:")).toBeInTheDocument();
|
||||
expect(screen.getByText(/boom/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("normalizes numeric errors to an object with a message", () => {
|
||||
render(<Error error={500} />);
|
||||
|
||||
expect(screen.getByText(/Error 500/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("unwraps nested response errors and renders raw/data sections", () => {
|
||||
render(
|
||||
<Error
|
||||
error={{
|
||||
message: "outer",
|
||||
data: {
|
||||
error: {
|
||||
message: "inner",
|
||||
url: "https://example.com",
|
||||
rawError: ["oops", { code: 1 }],
|
||||
data: { type: "Buffer", data: [97, 98] },
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText(/inner/)).toBeInTheDocument();
|
||||
expect(screen.getByText("https://example.com")).toBeInTheDocument();
|
||||
expect(screen.getByText(/\"code\": 1/)).toBeInTheDocument();
|
||||
// Buffer.from({type:"Buffer",data:[97,98]}).toString() === "ab"
|
||||
expect(screen.getByText(/ab/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
29
src/components/services/widget/highlight-context.test.jsx
Normal file
29
src/components/services/widget/highlight-context.test.jsx
Normal file
@@ -0,0 +1,29 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { useContext } from "react";
|
||||
|
||||
import { BlockHighlightContext } from "./highlight-context";
|
||||
|
||||
function Reader() {
|
||||
const value = useContext(BlockHighlightContext);
|
||||
return <div data-testid="value">{value === null ? "null" : value}</div>;
|
||||
}
|
||||
|
||||
describe("components/services/widget/highlight-context", () => {
|
||||
it("defaults to null", () => {
|
||||
render(<Reader />);
|
||||
expect(screen.getByTestId("value")).toHaveTextContent("null");
|
||||
});
|
||||
|
||||
it("provides a value to consumers", () => {
|
||||
render(
|
||||
<BlockHighlightContext.Provider value="on">
|
||||
<Reader />
|
||||
</BlockHighlightContext.Provider>,
|
||||
);
|
||||
expect(screen.getByTestId("value")).toHaveTextContent("on");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user