From 12f2abb996fec357ca4f81b69752c2bbccc57c9e Mon Sep 17 00:00:00 2001 From: pitoi Date: Fri, 3 Jul 2026 15:38:40 +0000 Subject: [PATCH] Generated with Hive: Pass ui_skin param to search API when activeSkin is legal --- src/components/search/search-bar.tsx | 7 ++- src/lib/__tests__/graph-api-search.test.ts | 70 ++++++++++++++++++++++ src/lib/__tests__/search-bar.test.tsx | 50 +++++++++++++++- src/lib/graph-api.ts | 3 +- 4 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 src/lib/__tests__/graph-api-search.test.ts diff --git a/src/components/search/search-bar.tsx b/src/components/search/search-bar.tsx index 2fd1cb5..75abf01 100644 --- a/src/components/search/search-bar.tsx +++ b/src/components/search/search-bar.tsx @@ -14,6 +14,7 @@ import { isMocksEnabled, MOCK_NODES, MOCK_EDGES } from "@/lib/mock-data" export function SearchBar() { const setSearchTerm = useAppStore((s) => s.setSearchTerm) const closeAllPanels = useAppStore((s) => s.closeAllPanels) + const activeSkin = useAppStore((s) => s.activeSkin) const { setGraphData, setLoading, clearSelection } = useGraphStore() const refreshBalance = useUserStore((s) => s.refreshBalance) const openModal = useModalStore((s) => s.open) @@ -60,7 +61,7 @@ export function SearchBar() { }) setGraphData(filtered, MOCK_EDGES) } else { - const result = await searchNodes(trimmed, { limit: 100 }, controller.signal) + const result = await searchNodes(trimmed, { limit: 100, ui_skin: activeSkin === "legal" ? "legal" : undefined }, controller.signal) setGraphData(result.nodes ?? [], result.edges ?? []) refreshBalance() } @@ -72,7 +73,7 @@ export function SearchBar() { try { await payL402(() => {}) // Retry search after payment - const result = await searchNodes(trimmed, { limit: 100 }, controller.signal) + const result = await searchNodes(trimmed, { limit: 100, ui_skin: activeSkin === "legal" ? "legal" : undefined }, controller.signal) setGraphData(result.nodes ?? [], result.edges ?? []) refreshBalance() } catch { @@ -87,7 +88,7 @@ export function SearchBar() { setLoading(false) } }, - [value, setSearchTerm, closeAllPanels, clearSelection, setGraphData, setLoading, refreshBalance, openModal] + [value, setSearchTerm, closeAllPanels, clearSelection, setGraphData, setLoading, refreshBalance, openModal, activeSkin] ) const handleClear = useCallback(async () => { diff --git a/src/lib/__tests__/graph-api-search.test.ts b/src/lib/__tests__/graph-api-search.test.ts new file mode 100644 index 0000000..6f21500 --- /dev/null +++ b/src/lib/__tests__/graph-api-search.test.ts @@ -0,0 +1,70 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest" + +// Mock sphinx helpers so api.ts can be imported without side-effects +const { getL402Mock, getSignedMessageMock } = vi.hoisted(() => ({ + getL402Mock: vi.fn(), + getSignedMessageMock: vi.fn(), +})) + +vi.mock("@/lib/sphinx", () => ({ + getL402: getL402Mock, + getSignedMessage: getSignedMessageMock, +})) + +vi.mock("@/lib/mock-data", () => ({ + isMocksEnabled: () => false, + MOCK_REVIEWS: [], + MOCK_WORKFLOW_MARKETPLACE: [], +})) + +import { searchNodes } from "@/lib/graph-api" + +const originalFetch = global.fetch + +beforeEach(() => { + getSignedMessageMock.mockResolvedValue({ signature: "", message: "" }) + getL402Mock.mockResolvedValue(null) +}) + +afterEach(() => { + global.fetch = originalFetch + vi.clearAllMocks() +}) + +describe("searchNodes — ui_skin param", () => { + it("appends ui_skin=legal when opt is provided", async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ nodes: [], edges: [] }), + }) as unknown as typeof fetch + + await searchNodes("foo", { ui_skin: "legal" }) + + const [[url]] = (global.fetch as ReturnType).mock.calls + expect(url).toContain("ui_skin=legal") + }) + + it("omits ui_skin when opt is not provided", async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ nodes: [], edges: [] }), + }) as unknown as typeof fetch + + await searchNodes("foo") + + const [[url]] = (global.fetch as ReturnType).mock.calls + expect(url).not.toContain("ui_skin") + }) + + it("omits ui_skin when opt is undefined", async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ nodes: [], edges: [] }), + }) as unknown as typeof fetch + + await searchNodes("foo", { ui_skin: undefined }) + + const [[url]] = (global.fetch as ReturnType).mock.calls + expect(url).not.toContain("ui_skin") + }) +}) diff --git a/src/lib/__tests__/search-bar.test.tsx b/src/lib/__tests__/search-bar.test.tsx index 4bad18a..afc7053 100644 --- a/src/lib/__tests__/search-bar.test.tsx +++ b/src/lib/__tests__/search-bar.test.tsx @@ -25,10 +25,11 @@ vi.mock("@/stores/graph-store", () => ({ const setSearchTerm = vi.fn() const closeAllPanels = vi.fn() +let mockActiveSkin: string | null = null vi.mock("@/stores/app-store", () => ({ useAppStore: (sel?: (s: unknown) => unknown) => { - const state = { setSearchTerm, closeAllPanels } + const state = { setSearchTerm, closeAllPanels, activeSkin: mockActiveSkin } return sel ? sel(state) : state }, })) @@ -48,8 +49,9 @@ vi.mock("@/stores/modal-store", () => ({ })) const mockGetLatestNodes = vi.fn() +const mockSearchNodes = vi.fn() vi.mock("@/lib/graph-api", () => ({ - searchNodes: vi.fn(), + searchNodes: (...args: unknown[]) => mockSearchNodes(...args), getLatestNodes: (...args: unknown[]) => mockGetLatestNodes(...args), })) @@ -82,7 +84,9 @@ describe("SearchBar handleClear", () => { beforeEach(() => { vi.clearAllMocks() mocksEnabled = false + mockActiveSkin = null mockGetLatestNodes.mockResolvedValue({ nodes: [{ ref_id: "n1" }], edges: [{ id: "e1" }] }) + mockSearchNodes.mockResolvedValue({ nodes: [], edges: [] }) }) function renderWithValue(inputValue: string) { @@ -141,3 +145,45 @@ describe("SearchBar handleClear", () => { expect(setGraphData).toHaveBeenCalledWith([], []) }) }) + +describe("SearchBar handleSubmit — ui_skin forwarding", () => { + beforeEach(() => { + vi.clearAllMocks() + mocksEnabled = false + mockActiveSkin = null + mockGetLatestNodes.mockResolvedValue({ nodes: [], edges: [] }) + mockSearchNodes.mockResolvedValue({ nodes: [], edges: [] }) + }) + + async function submitSearch(query: string) { + render() + const input = screen.getByPlaceholderText("Search the graph...") + fireEvent.change(input, { target: { value: query } }) + fireEvent.submit(input.closest("form")!) + await waitFor(() => expect(mockSearchNodes).toHaveBeenCalled()) + } + + it("forwards ui_skin=legal when activeSkin is 'legal'", async () => { + mockActiveSkin = "legal" + await submitSearch("contract") + expect(mockSearchNodes).toHaveBeenCalledWith( + "contract", + expect.objectContaining({ ui_skin: "legal" }), + expect.anything() + ) + }) + + it("omits ui_skin when activeSkin is 'default'", async () => { + mockActiveSkin = "default" + await submitSearch("contract") + const opts = mockSearchNodes.mock.calls[0][1] + expect(opts?.ui_skin).toBeUndefined() + }) + + it("omits ui_skin when activeSkin is null", async () => { + mockActiveSkin = null + await submitSearch("contract") + const opts = mockSearchNodes.mock.calls[0][1] + expect(opts?.ui_skin).toBeUndefined() + }) +}) diff --git a/src/lib/graph-api.ts b/src/lib/graph-api.ts index 59f2811..d28653b 100644 --- a/src/lib/graph-api.ts +++ b/src/lib/graph-api.ts @@ -52,7 +52,7 @@ export const DEFAULT_SEARCH_DOMAINS: readonly string[] = [] // Search nodes via v2 endpoint export async function searchNodes( query: string, - opts?: { limit?: number; skip?: number; node_type?: string; domains?: string[] }, + opts?: { limit?: number; skip?: number; node_type?: string; domains?: string[]; ui_skin?: string }, signal?: AbortSignal ): Promise { const params = new URLSearchParams({ @@ -61,6 +61,7 @@ export async function searchNodes( skip: String(opts?.skip ?? 0), }) if (opts?.node_type) params.set("node_type", opts.node_type) + if (opts?.ui_skin) params.set("ui_skin", opts.ui_skin) const domains = opts?.domains ?? DEFAULT_SEARCH_DOMAINS if (domains.length > 0) {