docs: added tests for components in api-reference project (#14428)

* add tests (WIP)

* added test for h2

* finished adding tests

* fixes

* fixes

* fixes
This commit is contained in:
Shahed Nasser
2026-01-05 10:56:56 +02:00
committed by GitHub
parent fb772f0f6a
commit 4d632e7a5d
102 changed files with 8278 additions and 127 deletions
@@ -0,0 +1,182 @@
import React from "react"
import { beforeEach, describe, expect, test, vi } from "vitest"
import { cleanup, fireEvent, render } from "@testing-library/react"
import { usePathname } from "next/navigation"
import AreaProvider, { useArea } from "../area"
import { OpenAPI } from "types"
// Mock functions
const mockSetActivePath = vi.fn()
const mockUseSidebar = vi.fn(() => ({
setActivePath: mockSetActivePath,
}))
const mockUsePathname = vi.fn(() => "/store/test")
const mockCapitalize = vi.fn((str: string) => str.charAt(0).toUpperCase() + str.slice(1))
// Track previous values for usePrevious mock
// usePrevious returns the value from the previous render
let previousValue: unknown = undefined
const mockUsePrevious = vi.fn((value: unknown) => {
const result = previousValue
previousValue = value
return result
})
// Test component that uses the hook
const TestComponent = () => {
const { area, prevArea, displayedArea, setArea } = useArea()
return (
<div>
<div data-testid="area">{area}</div>
<div data-testid="prev-area">{prevArea || "undefined"}</div>
<div data-testid="displayed-area">{displayedArea}</div>
<button
data-testid="set-area-store"
onClick={() => setArea("store" as OpenAPI.Area)}
>
Set Store
</button>
<button
data-testid="set-area-admin"
onClick={() => setArea("admin" as OpenAPI.Area)}
>
Set Admin
</button>
</div>
)
}
vi.mock("docs-ui", () => ({
capitalize: (str: string) => mockCapitalize(str),
usePrevious: (value: unknown) => mockUsePrevious(value),
useSidebar: () => mockUseSidebar(),
}))
vi.mock("next/navigation", () => ({
usePathname: () => mockUsePathname(),
}))
describe("AreaProvider", () => {
beforeEach(() => {
vi.clearAllMocks()
cleanup()
previousValue = undefined
mockUsePathname.mockReturnValue("/store/test")
mockCapitalize.mockImplementation((str: string) => str.charAt(0).toUpperCase() + str.slice(1))
})
describe("rendering", () => {
test("renders children", () => {
const { getByText } = render(
<AreaProvider area="store">
<div>Test Content</div>
</AreaProvider>
)
expect(getByText("Test Content")).toBeInTheDocument()
})
})
describe("initial state", () => {
test("initializes with passed area", () => {
const { getByTestId } = render(
<AreaProvider area="store">
<TestComponent />
</AreaProvider>
)
expect(getByTestId("area")).toHaveTextContent("store")
})
test("displays capitalized area", () => {
const { getByTestId } = render(
<AreaProvider area="store">
<TestComponent />
</AreaProvider>
)
expect(getByTestId("displayed-area")).toHaveTextContent("Store")
expect(mockCapitalize).toHaveBeenCalledWith("store")
})
test("prevArea is undefined initially", () => {
const { getByTestId } = render(
<AreaProvider area="store">
<TestComponent />
</AreaProvider>
)
expect(getByTestId("prev-area")).toHaveTextContent("undefined")
})
})
describe("setArea", () => {
test("updates area when setArea is called", () => {
const { getByTestId } = render(
<AreaProvider area="store">
<TestComponent />
</AreaProvider>
)
const setAdminButton = getByTestId("set-area-admin")
const areaElement = getByTestId("area")
expect(areaElement).toHaveTextContent("store")
fireEvent.click(setAdminButton)
expect(areaElement).toHaveTextContent("admin")
})
test("updates displayedArea when area changes", () => {
const { getByTestId } = render(
<AreaProvider area="store">
<TestComponent />
</AreaProvider>
)
const setAdminButton = getByTestId("set-area-admin")
const displayedAreaElement = getByTestId("displayed-area")
expect(displayedAreaElement).toHaveTextContent("Store")
fireEvent.click(setAdminButton)
expect(displayedAreaElement).toHaveTextContent("Admin")
})
})
describe("useEffect behavior", () => {
test("calls setActivePath(null) when pathname changes", () => {
const { rerender } = render(
<AreaProvider area="store">
<TestComponent />
</AreaProvider>
)
mockUsePathname.mockReturnValue("/admin/test")
rerender(
<AreaProvider area="store">
<TestComponent />
</AreaProvider>
)
expect(mockSetActivePath).toHaveBeenCalledWith(null)
})
})
describe("useArea hook", () => {
test("throws error when used outside provider", () => {
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {})
expect(() => {
render(<TestComponent />)
}).toThrow("useAreaProvider must be used inside an AreaProvider")
consoleSpy.mockRestore()
})
test("returns area, prevArea, displayedArea, and setArea", () => {
const { getByTestId } = render(
<AreaProvider area="store">
<TestComponent />
</AreaProvider>
)
expect(getByTestId("area")).toBeInTheDocument()
expect(getByTestId("prev-area")).toBeInTheDocument()
expect(getByTestId("displayed-area")).toBeInTheDocument()
expect(getByTestId("set-area-store")).toBeInTheDocument()
expect(getByTestId("set-area-admin")).toBeInTheDocument()
})
})
})
@@ -0,0 +1,363 @@
import React from "react"
import { beforeEach, describe, expect, test, vi } from "vitest"
import { cleanup, render, waitFor } from "@testing-library/react"
import { OpenAPI, Sidebar } from "types"
// mock data
const mockShownSidebar: Sidebar.Sidebar = {
sidebar_id: "test-sidebar",
title: "Test Sidebar",
items: [],
}
const mockBaseSpecs: OpenAPI.ExpandedDocument = {
openapi: "3.0.0",
info: {
title: "Test API",
version: "1.0.0",
},
tags: [
{
name: "TestTag",
},
],
expandedTags: {
"testtag": {
"get": {
summary: "Test Operation",
description: "Test Operation Description",
parameters: [],
},
},
},
paths: {
"/test-path": {
get: {
operationId: "test-operation",
summary: "Test Operation",
description: "Test Operation Description",
"x-authenticated": false,
"x-codeSamples": [],
parameters: [],
responses: {
"200": {
description: "OK",
content: {}
}
},
requestBody: {
content: {}
}
},
},
},
components: {
securitySchemes: {
"test-security": {
type: "http",
scheme: "bearer",
},
},
},
}
// Mock functions
const mockPush = vi.fn()
const mockReplace = vi.fn()
const mockUseRouter = vi.fn(() => ({
push: mockPush,
replace: mockReplace,
}))
const mockSetActivePath = vi.fn()
const mockResetItems = vi.fn()
const mockUpdateItems = vi.fn()
const mockUseSidebar = vi.fn(() => ({
activePath: "testtag",
setActivePath: mockSetActivePath,
resetItems: mockResetItems,
updateItems: mockUpdateItems,
shownSidebar: mockShownSidebar as Sidebar.Sidebar | Sidebar.SidebarItemSidebar | undefined,
}))
const mockGetSectionId = vi.fn((parts: string[]) => parts.join("-").toLowerCase())
const mockGetTagChildSidebarItems = vi.fn(() => [] as Sidebar.SidebarItem[])
vi.mock("next/navigation", () => ({
useRouter: () => mockUseRouter(),
}))
vi.mock("docs-ui", () => ({
useSidebar: () => mockUseSidebar(),
}))
vi.mock("docs-utils", () => ({
getSectionId: (parts: string[]) => mockGetSectionId(parts),
}))
vi.mock("@/utils/get-tag-child-sidebar-items", () => ({
default: () => mockGetTagChildSidebarItems(),
}))
import BaseSpecsProvider, { useBaseSpecs } from "../base-specs"
// Test component that uses the hook
const TestComponent = () => {
const { baseSpecs, getSecuritySchema } = useBaseSpecs()
return (
<div>
<div data-testid="base-specs">{baseSpecs ? "present" : "null"}</div>
<div data-testid="security-schema">
{getSecuritySchema("test-security") ? "found" : "null"}
</div>
</div>
)
}
describe("BaseSpecsProvider", () => {
beforeEach(() => {
vi.clearAllMocks()
cleanup()
window.location.hash = ""
mockGetSectionId.mockImplementation((parts: string[]) => parts.join("-").toLowerCase())
mockGetTagChildSidebarItems.mockReturnValue([])
})
describe("rendering", () => {
test("renders children", () => {
const { getByText } = render(
<BaseSpecsProvider baseSpecs={mockBaseSpecs}>
<div>Test Content</div>
</BaseSpecsProvider>
)
expect(getByText("Test Content")).toBeInTheDocument()
})
})
describe("useBaseSpecs hook", () => {
test("throws error when used outside provider", () => {
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {})
expect(() => {
render(<TestComponent />)
}).toThrow("useBaseSpecs must be used inside a BaseSpecsProvider")
consoleSpy.mockRestore()
})
test("returns baseSpecs and getSecuritySchema", () => {
const { getByTestId } = render(
<BaseSpecsProvider baseSpecs={mockBaseSpecs}>
<TestComponent />
</BaseSpecsProvider>
)
expect(getByTestId("base-specs")).toHaveTextContent("present")
expect(getByTestId("security-schema")).toBeInTheDocument()
})
})
describe("getSecuritySchema", () => {
test("returns security schema when it exists", () => {
const { getByTestId } = render(
<BaseSpecsProvider baseSpecs={mockBaseSpecs}>
<TestComponent />
</BaseSpecsProvider>
)
const securitySchema = getByTestId("security-schema")
expect(securitySchema).toHaveTextContent("found")
})
test("returns null when security schema does not exist", () => {
const { getByTestId } = render(
<BaseSpecsProvider baseSpecs={mockBaseSpecs}>
<TestComponent />
</BaseSpecsProvider>
)
const securitySchema = getByTestId("security-schema")
// Change to a non-existent security name
const TestComponent2 = () => {
const { getSecuritySchema } = useBaseSpecs()
return (
<div data-testid="security-schema-2">
{getSecuritySchema("non-existent") ? "found" : "null"}
</div>
)
}
const { getByTestId: getByTestId2 } = render(
<BaseSpecsProvider baseSpecs={mockBaseSpecs}>
<TestComponent2 />
</BaseSpecsProvider>
)
expect(getByTestId2("security-schema-2")).toHaveTextContent("null")
})
test("returns null when security schema is a ref", () => {
const baseSpecsWithRef: OpenAPI.ExpandedDocument = {
...mockBaseSpecs,
components: {
securitySchemes: {
"test-security": {
$ref: "#/components/securitySchemes/OtherSecurity",
},
},
},
} as OpenAPI.ExpandedDocument
const TestComponent3 = () => {
const { getSecuritySchema } = useBaseSpecs()
return (
<div data-testid="security-schema-3">
{getSecuritySchema("test-security") ? "found" : "null"}
</div>
)
}
const { getByTestId } = render(
<BaseSpecsProvider baseSpecs={baseSpecsWithRef}>
<TestComponent3 />
</BaseSpecsProvider>
)
expect(getByTestId("security-schema-3")).toHaveTextContent("null")
})
})
describe("itemsToUpdate", () => {
test("generates itemsToUpdate from baseSpecs tags", async () => {
const mockSidebar: Sidebar.Sidebar = {
sidebar_id: "test-sidebar",
title: "Test Sidebar",
items: [],
}
mockUseSidebar.mockReturnValue({
activePath: "testtag",
setActivePath: mockSetActivePath,
resetItems: mockResetItems,
updateItems: mockUpdateItems,
shownSidebar: mockSidebar,
})
mockGetTagChildSidebarItems.mockReturnValue([
{
type: "link",
path: "/test-path",
title: "Test Link",
},
])
render(
<BaseSpecsProvider baseSpecs={mockBaseSpecs}>
<div>Test</div>
</BaseSpecsProvider>
)
await waitFor(() => {
expect(mockUpdateItems).toHaveBeenCalled()
})
const updateCall = mockUpdateItems.mock.calls[0][0]
expect(updateCall.sidebar_id).toBe("test-sidebar")
expect(updateCall.items).toBeDefined()
expect(Array.isArray(updateCall.items)).toBe(true)
expect(updateCall.items.length).toBeGreaterThan(0)
})
test("does not update items when baseSpecs is undefined", () => {
render(
<BaseSpecsProvider baseSpecs={undefined}>
<div>Test</div>
</BaseSpecsProvider>
)
expect(mockUpdateItems).not.toHaveBeenCalled()
})
test("does not update items when shownSidebar is null", () => {
mockUseSidebar.mockReturnValue({
activePath: "testtag",
setActivePath: mockSetActivePath,
resetItems: mockResetItems,
updateItems: mockUpdateItems,
shownSidebar: undefined,
})
render(
<BaseSpecsProvider baseSpecs={mockBaseSpecs}>
<div>Test</div>
</BaseSpecsProvider>
)
expect(mockUpdateItems).not.toHaveBeenCalled()
})
test("includes onOpen handler that updates hash and activePath", async () => {
mockUseSidebar.mockReturnValue({
activePath: "something-else",
setActivePath: mockSetActivePath,
resetItems: mockResetItems,
updateItems: mockUpdateItems,
shownSidebar: mockShownSidebar,
})
window.location.hash = ""
render(
<BaseSpecsProvider baseSpecs={mockBaseSpecs}>
<div>Test</div>
</BaseSpecsProvider>
)
await waitFor(() => {
expect(mockUpdateItems).toHaveBeenCalled()
})
const updateCall = mockUpdateItems.mock.calls[0][0]
const item = updateCall.items[0]
expect(item.newItem.onOpen).toBeDefined()
// Call onOpen
item.newItem.onOpen()
expect(mockPush).toHaveBeenCalledWith("#testtag", { scroll: false })
expect(mockSetActivePath).toHaveBeenCalledWith("testtag")
})
test("onOpen does not update hash when it already matches", async () => {
mockUseSidebar.mockReturnValue({
activePath: "testtag",
setActivePath: mockSetActivePath,
resetItems: mockResetItems,
updateItems: mockUpdateItems,
shownSidebar: mockShownSidebar,
})
window.location.hash = "#testtag"
render(
<BaseSpecsProvider baseSpecs={mockBaseSpecs}>
<div>Test</div>
</BaseSpecsProvider>
)
await waitFor(() => {
expect(mockUpdateItems).toHaveBeenCalled()
})
const updateCall = mockUpdateItems.mock.calls[0][0]
const item = updateCall.items[0]
mockPush.mockClear()
mockSetActivePath.mockClear()
// Call onOpen
item.newItem.onOpen()
expect(mockPush).not.toHaveBeenCalled()
expect(mockSetActivePath).not.toHaveBeenCalled()
})
})
describe("cleanup", () => {
test("calls resetItems on unmount", () => {
const { unmount } = render(
<BaseSpecsProvider baseSpecs={mockBaseSpecs}>
<div>Test</div>
</BaseSpecsProvider>
)
unmount()
expect(mockResetItems).toHaveBeenCalled()
})
})
})
@@ -0,0 +1,95 @@
import React from "react"
import { beforeEach, describe, expect, test, vi } from "vitest"
import { cleanup, fireEvent, render } from "@testing-library/react"
import LoadingProvider, { useLoading } from "../loading"
// Test component that uses the hook
const TestComponent = () => {
const { loading, removeLoading } = useLoading()
return (
<div>
<div data-testid="loading-state">{loading.toString()}</div>
<button data-testid="remove-loading" onClick={removeLoading}>
Remove Loading
</button>
</div>
)
}
describe("LoadingProvider", () => {
beforeEach(() => {
vi.clearAllMocks()
cleanup()
})
describe("rendering", () => {
test("renders children", () => {
const { getByText } = render(
<LoadingProvider>
<div>Test Content</div>
</LoadingProvider>
)
expect(getByText("Test Content")).toBeInTheDocument()
})
})
describe("initial loading state", () => {
test("initializes with loading false by default", () => {
const { getByTestId } = render(
<LoadingProvider>
<TestComponent />
</LoadingProvider>
)
expect(getByTestId("loading-state")).toHaveTextContent("false")
})
test("initializes with loading true when initialLoading is true", () => {
const { getByTestId } = render(
<LoadingProvider initialLoading={true}>
<TestComponent />
</LoadingProvider>
)
expect(getByTestId("loading-state")).toHaveTextContent("true")
})
})
describe("removeLoading", () => {
test("sets loading to false when removeLoading is called", () => {
const { getByTestId } = render(
<LoadingProvider initialLoading={true}>
<TestComponent />
</LoadingProvider>
)
const removeLoadingButton = getByTestId("remove-loading")
const loadingState = getByTestId("loading-state")
expect(loadingState).toHaveTextContent("true")
fireEvent.click(removeLoadingButton)
expect(loadingState).toHaveTextContent("false")
})
})
describe("useLoading hook", () => {
test("throws error when used outside provider", () => {
// Suppress console.error for this test
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {})
expect(() => {
render(<TestComponent />)
}).toThrow("useLoading must be used inside a LoadingProvider")
consoleSpy.mockRestore()
})
test("returns loading state and removeLoading function", () => {
const { getByTestId } = render(
<LoadingProvider>
<TestComponent />
</LoadingProvider>
)
expect(getByTestId("loading-state")).toBeInTheDocument()
expect(getByTestId("remove-loading")).toBeInTheDocument()
})
})
})
@@ -0,0 +1,80 @@
import React from "react"
import { beforeEach, describe, expect, test, vi } from "vitest"
import { cleanup, render } from "@testing-library/react"
import { MainNavProvider } from "../main-nav"
// Mock functions
const mockGetNavDropdownItems = vi.fn((options: unknown) => [
{
title: "Test Item",
path: "/test",
},
])
vi.mock("docs-ui", () => ({
getNavDropdownItems: (options: unknown) => mockGetNavDropdownItems(options),
MainNavProvider: ({ children, navItems }: { children: React.ReactNode; navItems: unknown[] }) => (
<div data-testid="ui-main-nav-provider" data-nav-items={JSON.stringify(navItems)}>
{children}
</div>
),
}))
vi.mock("@/config", () => ({
config: {
baseUrl: "https://test.com",
},
}))
describe("MainNavProvider", () => {
beforeEach(() => {
vi.clearAllMocks()
cleanup()
})
describe("rendering", () => {
test("renders children", () => {
const { getByText } = render(
<MainNavProvider>
<div>Test Content</div>
</MainNavProvider>
)
expect(getByText("Test Content")).toBeInTheDocument()
})
test("renders UiMainNavProvider with navItems", () => {
const { getByTestId } = render(
<MainNavProvider>
<div>Test</div>
</MainNavProvider>
)
const uiProvider = getByTestId("ui-main-nav-provider")
expect(uiProvider).toBeInTheDocument()
expect(mockGetNavDropdownItems).toHaveBeenCalledWith({
basePath: "https://test.com",
})
})
})
describe("navigationDropdownItems", () => {
test("memoizes navigationDropdownItems", () => {
const { rerender } = render(
<MainNavProvider>
<div>Test</div>
</MainNavProvider>
)
const callCount = mockGetNavDropdownItems.mock.calls.length
rerender(
<MainNavProvider>
<div>Test</div>
</MainNavProvider>
)
// Should not call getNavDropdownItems again due to memoization
expect(mockGetNavDropdownItems.mock.calls.length).toBe(callCount)
})
})
})
@@ -0,0 +1,206 @@
import React from "react"
import { beforeEach, describe, expect, test, vi } from "vitest"
import { cleanup, render } from "@testing-library/react"
import PageTitleProvider from "../page-title"
import { Sidebar } from "types"
// Mock functions
const mockUseSidebar = vi.fn(() => ({
activePath: "test-path" as string | null,
activeItem: {
type: "link",
path: "test-path",
title: "Test Item",
},
}))
const mockUseArea = vi.fn(() => ({
displayedArea: "Store",
}))
vi.mock("docs-ui", () => ({
useSidebar: () => mockUseSidebar(),
}))
vi.mock("@/providers/area", () => ({
useArea: () => mockUseArea(),
}))
describe("PageTitleProvider", () => {
beforeEach(() => {
vi.clearAllMocks()
cleanup()
document.title = ""
mockUseSidebar.mockReturnValue({
activePath: "test-path",
activeItem: {
type: "link",
path: "test-path",
title: "Test Item",
},
})
mockUseArea.mockReturnValue({
displayedArea: "Store",
})
})
describe("rendering", () => {
test("renders children", () => {
const { getByText } = render(
<PageTitleProvider>
<div>Test Content</div>
</PageTitleProvider>
)
expect(getByText("Test Content")).toBeInTheDocument()
})
})
describe("document title", () => {
test("sets title to suffix when activePath is null", () => {
mockUseSidebar.mockReturnValue({
activePath: null,
activeItem: {
type: "link",
path: "test-path",
title: "Test Item",
},
})
render(
<PageTitleProvider>
<div>Test</div>
</PageTitleProvider>
)
expect(document.title).toBe("Medusa Store API Reference")
})
test("sets title to suffix when activePath is empty string", () => {
mockUseSidebar.mockReturnValue({
activePath: "",
activeItem: {
type: "link",
path: "test-path",
title: "Test Item",
},
})
render(
<PageTitleProvider>
<div>Test</div>
</PageTitleProvider>
)
expect(document.title).toBe("Medusa Store API Reference")
})
test("sets title with activeItem title when activeItem.path matches activePath", () => {
const mockItem: Sidebar.SidebarItemLink = {
type: "link",
path: "/test-path",
title: "Test Item",
}
mockUseSidebar.mockReturnValue({
activePath: "/test-path",
activeItem: mockItem,
})
render(
<PageTitleProvider>
<div>Test</div>
</PageTitleProvider>
)
expect(document.title).toBe("Test Item - Medusa Store API Reference")
})
test("sets title with child item title when activeItem has matching child", () => {
const mockChildItem: Sidebar.SidebarItemLink = {
type: "link",
path: "/child-path",
title: "Child Item",
}
const mockItem: Sidebar.SidebarItemLink = {
type: "link",
path: "/parent-path",
title: "Parent Item",
children: [mockChildItem],
}
mockUseSidebar.mockReturnValue({
activePath: "/child-path",
activeItem: mockItem,
})
render(
<PageTitleProvider>
<div>Test</div>
</PageTitleProvider>
)
expect(document.title).toBe("Child Item - Medusa Store API Reference")
})
test("sets title to suffix when activeItem has no matching child", () => {
const mockItem: Sidebar.SidebarItemLink = {
type: "link",
path: "/parent-path",
title: "Parent Item",
children: [
{
type: "link",
path: "/other-path",
title: "Other Item",
},
],
}
mockUseSidebar.mockReturnValue({
activePath: "/child-path",
activeItem: mockItem,
})
render(
<PageTitleProvider>
<div>Test</div>
</PageTitleProvider>
)
expect(document.title).toBe("Medusa Store API Reference")
})
test("updates title when displayedArea changes", () => {
mockUseArea.mockReturnValue({
displayedArea: "Admin",
})
render(
<PageTitleProvider>
<div>Test</div>
</PageTitleProvider>
)
expect(document.title).toBe("Test Item - Medusa Admin API Reference")
})
test("updates title when activePath changes", () => {
const { rerender } = render(
<PageTitleProvider>
<div>Test</div>
</PageTitleProvider>
)
expect(document.title).toBe("Test Item - Medusa Store API Reference")
const mockItem: Sidebar.SidebarItemLink = {
type: "link",
path: "/new-path",
title: "New Item",
}
mockUseSidebar.mockReturnValue({
activePath: "/new-path",
activeItem: mockItem,
})
rerender(
<PageTitleProvider>
<div>Test</div>
</PageTitleProvider>
)
expect(document.title).toBe("New Item - Medusa Store API Reference")
})
})
})
@@ -0,0 +1,162 @@
import React from "react"
import { beforeEach, describe, expect, test, vi } from "vitest"
import { cleanup, render } from "@testing-library/react"
import SearchProvider from "../search"
// Mock functions
const mockIsLoading = vi.fn(() => false)
const mockUsePageLoading = vi.fn(() => ({
isLoading: mockIsLoading(),
}))
const mockBasePathUrl = vi.fn((url: string) => url)
vi.mock("docs-ui", () => ({
usePageLoading: () => mockUsePageLoading(),
SearchProvider: ({
children,
algolia,
indices,
defaultIndex,
searchProps,
}: {
children: React.ReactNode
algolia: unknown
indices: unknown[]
defaultIndex: string
searchProps: unknown
}) => (
<div
data-testid="ui-search-provider"
data-algolia={JSON.stringify(algolia)}
data-indices={JSON.stringify(indices)}
data-default-index={defaultIndex}
data-search-props={JSON.stringify(searchProps)}
>
{children}
</div>
),
}))
vi.mock("@/config", () => ({
config: {
baseUrl: "https://test.com",
},
}))
vi.mock("@/utils/base-path-url", () => ({
default: (url: string) => mockBasePathUrl(url),
}))
describe("SearchProvider", () => {
beforeEach(() => {
vi.clearAllMocks()
cleanup()
// Set environment variables
process.env.NEXT_PUBLIC_ALGOLIA_APP_ID = "test-app-id"
process.env.NEXT_PUBLIC_ALGOLIA_API_KEY = "test-api-key"
process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME = "test-api-index"
process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME = "test-docs-index"
mockIsLoading.mockReturnValue(false)
})
describe("rendering", () => {
test("renders children", () => {
const { getByText } = render(
<SearchProvider>
<div>Test Content</div>
</SearchProvider>
)
expect(getByText("Test Content")).toBeInTheDocument()
})
test("renders UiSearchProvider with correct props", () => {
const { getByTestId } = render(
<SearchProvider>
<div>Test</div>
</SearchProvider>
)
const uiProvider = getByTestId("ui-search-provider")
expect(uiProvider).toBeInTheDocument()
const algolia = JSON.parse(uiProvider.getAttribute("data-algolia") || "{}")
expect(algolia).toEqual({
appId: "test-app-id",
apiKey: "test-api-key",
mainIndexName: "test-api-index",
})
const indices = JSON.parse(uiProvider.getAttribute("data-indices") || "[]")
expect(indices).toEqual([
{
value: "test-docs-index",
title: "Docs",
},
{
value: "test-api-index",
title: "Store & Admin API",
},
])
expect(uiProvider.getAttribute("data-default-index")).toBe("test-api-index")
})
})
describe("environment variables", () => {
test("uses default values when env vars are not set", () => {
delete process.env.NEXT_PUBLIC_ALGOLIA_APP_ID
delete process.env.NEXT_PUBLIC_ALGOLIA_API_KEY
delete process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME
delete process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME
const { getByTestId } = render(
<SearchProvider>
<div>Test</div>
</SearchProvider>
)
const uiProvider = getByTestId("ui-search-provider")
const algolia = JSON.parse(uiProvider.getAttribute("data-algolia") || "{}")
expect(algolia.appId).toBe("temp")
expect(algolia.apiKey).toBe("temp")
expect(algolia.mainIndexName).toBe("temp")
})
})
describe("searchProps", () => {
test("passes isLoading from usePageLoading", () => {
mockIsLoading.mockReturnValue(true)
const { getByTestId } = render(
<SearchProvider>
<div>Test</div>
</SearchProvider>
)
const uiProvider = getByTestId("ui-search-provider")
const searchProps = JSON.parse(uiProvider.getAttribute("data-search-props") || "{}")
expect(searchProps.isLoading).toBe(true)
})
test("includes suggestions", () => {
const { getByTestId } = render(
<SearchProvider>
<div>Test</div>
</SearchProvider>
)
const uiProvider = getByTestId("ui-search-provider")
const searchProps = JSON.parse(uiProvider.getAttribute("data-search-props") || "{}")
expect(searchProps.suggestions).toBeDefined()
expect(Array.isArray(searchProps.suggestions)).toBe(true)
expect(searchProps.suggestions.length).toBeGreaterThan(0)
})
test("includes checkInternalPattern regex", () => {
const { getByTestId } = render(
<SearchProvider>
<div>Test</div>
</SearchProvider>
)
const uiProvider = getByTestId("ui-search-provider")
const searchProps = JSON.parse(uiProvider.getAttribute("data-search-props") || "{}")
expect(searchProps.checkInternalPattern).toBeDefined()
})
})
})
@@ -0,0 +1,237 @@
import React from "react"
import { beforeEach, describe, expect, test, vi } from "vitest"
import { cleanup, render, waitFor } from "@testing-library/react"
import SidebarProvider from "../sidebar"
import { Sidebar } from "types"
// Mock functions
const mockIsLoading = vi.fn(() => false)
const mockSetIsLoading = vi.fn()
const mockUsePageLoading = vi.fn(() => ({
isLoading: mockIsLoading(),
setIsLoading: mockSetIsLoading,
}))
const mockScrollableElement = vi.fn(() => null as HTMLElement | null)
const mockUseScrollController = vi.fn(() => ({
scrollableElement: mockScrollableElement(),
}))
const mockPathname = vi.fn(() => "/store/test")
const mockUsePathname = vi.fn(() => mockPathname())
const mockStoreSidebar: Sidebar.Sidebar = {
sidebar_id: "store-sidebar",
title: "Store Sidebar",
items: [],
}
const mockAdminSidebar: Sidebar.Sidebar = {
sidebar_id: "admin-sidebar",
title: "Admin Sidebar",
items: [],
}
vi.mock("docs-ui", () => ({
SidebarProvider: ({
children,
isLoading,
setIsLoading,
shouldHandleHashChange,
shouldHandlePathChange,
scrollableElement,
sidebars,
persistCategoryState,
disableActiveTransition,
isSidebarStatic,
}: {
children: React.ReactNode
isLoading: boolean
setIsLoading: (value: boolean) => void
shouldHandleHashChange: boolean
shouldHandlePathChange: boolean
scrollableElement: HTMLElement | null
sidebars: Sidebar.Sidebar[]
persistCategoryState: boolean
disableActiveTransition: boolean
isSidebarStatic: boolean
}) => (
<div
data-testid="ui-sidebar-provider"
data-is-loading={isLoading.toString()}
data-should-handle-hash-change={shouldHandleHashChange.toString()}
data-should-handle-path-change={shouldHandlePathChange.toString()}
data-scrollable-element={scrollableElement ? "present" : "null"}
data-sidebars={JSON.stringify(sidebars)}
data-persist-category-state={persistCategoryState.toString()}
data-disable-active-transition={disableActiveTransition.toString()}
data-is-sidebar-static={isSidebarStatic.toString()}
>
{children}
</div>
),
usePageLoading: () => mockUsePageLoading(),
useScrollController: () => mockUseScrollController(),
}))
vi.mock("next/navigation", () => ({
usePathname: () => mockUsePathname(),
}))
vi.mock("@/config", () => ({
config: {
sidebars: [{
sidebar_id: "store-sidebar",
title: "Store Sidebar",
items: [],
}],
},
}))
// Mock dynamic imports
vi.mock("@/generated/generated-store-sidebar.mjs", () => ({
default: mockStoreSidebar,
}))
vi.mock("@/generated/generated-admin-sidebar.mjs", () => ({
default: mockAdminSidebar,
}))
describe("SidebarProvider", () => {
beforeEach(() => {
vi.clearAllMocks()
cleanup()
mockIsLoading.mockReturnValue(false)
mockScrollableElement.mockReturnValue(null)
mockPathname.mockReturnValue("/store/test")
})
describe("rendering", () => {
test("renders children", () => {
const { getByText } = render(
<SidebarProvider>
<div>Test Content</div>
</SidebarProvider>
)
expect(getByText("Test Content")).toBeInTheDocument()
})
test("renders UiSidebarProvider with correct props", async () => {
const { getByTestId } = render(
<SidebarProvider>
<div>Test</div>
</SidebarProvider>
)
await waitFor(() => {
const uiProvider = getByTestId("ui-sidebar-provider")
expect(uiProvider).toBeInTheDocument()
expect(uiProvider.getAttribute("data-is-loading")).toBe("false")
expect(uiProvider.getAttribute("data-should-handle-hash-change")).toBe("true")
expect(uiProvider.getAttribute("data-should-handle-path-change")).toBe("false")
expect(uiProvider.getAttribute("data-persist-category-state")).toBe("false")
expect(uiProvider.getAttribute("data-disable-active-transition")).toBe("false")
expect(uiProvider.getAttribute("data-is-sidebar-static")).toBe("false")
})
})
})
describe("sidebar loading", () => {
test("loads store sidebar when path starts with /store", async () => {
mockPathname.mockReturnValue("/store/test")
const { getByTestId } = render(
<SidebarProvider>
<div>Test</div>
</SidebarProvider>
)
await waitFor(() => {
const uiProvider = getByTestId("ui-sidebar-provider")
const sidebars = JSON.parse(uiProvider.getAttribute("data-sidebars") || "[]")
expect(sidebars).toEqual([mockStoreSidebar])
})
})
test("loads admin sidebar when path does not start with /store", async () => {
mockPathname.mockReturnValue("/admin/test")
const { getByTestId } = render(
<SidebarProvider>
<div>Test</div>
</SidebarProvider>
)
await waitFor(() => {
const uiProvider = getByTestId("ui-sidebar-provider")
const sidebars = JSON.parse(uiProvider.getAttribute("data-sidebars") || "[]")
expect(sidebars).toEqual([mockAdminSidebar])
})
})
test("uses config sidebars as fallback when sidebar is not loaded", () => {
const { getByTestId } = render(
<SidebarProvider>
<div>Test</div>
</SidebarProvider>
)
// Initially should use config sidebars
const uiProvider = getByTestId("ui-sidebar-provider")
const sidebars = JSON.parse(uiProvider.getAttribute("data-sidebars") || "[]")
expect(sidebars).toEqual([mockStoreSidebar])
})
})
describe("props passing", () => {
test("passes isLoading and setIsLoading to UiSidebarProvider", async () => {
mockIsLoading.mockReturnValue(true)
const { getByTestId } = render(
<SidebarProvider>
<div>Test</div>
</SidebarProvider>
)
await waitFor(() => {
const uiProvider = getByTestId("ui-sidebar-provider")
expect(uiProvider.getAttribute("data-is-loading")).toBe("true")
})
})
test("passes scrollableElement to UiSidebarProvider", async () => {
const mockElement = document.createElement("div")
mockScrollableElement.mockReturnValue(mockElement)
const { getByTestId } = render(
<SidebarProvider>
<div>Test</div>
</SidebarProvider>
)
await waitFor(() => {
const uiProvider = getByTestId("ui-sidebar-provider")
expect(uiProvider.getAttribute("data-scrollable-element")).toBe("present")
})
})
})
describe("error handling", () => {
test("handles sidebar loading errors gracefully", async () => {
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {})
// Mock a failing import
vi.doMock("../generated/generated-store-sidebar.mjs", () => {
throw new Error("Failed to load sidebar")
})
const { getByTestId } = render(
<SidebarProvider>
<div>Test</div>
</SidebarProvider>
)
await waitFor(() => {
const uiProvider = getByTestId("ui-sidebar-provider")
expect(uiProvider).toBeInTheDocument()
})
consoleSpy.mockRestore()
})
})
})