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:
@@ -1,5 +1,6 @@
|
||||
"use server"
|
||||
|
||||
import React from "react"
|
||||
import type { OpenAPI } from "types"
|
||||
import Section from "../Section"
|
||||
import MDXContentServer from "../MDXContent/Server"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import DividedLayout from "@/layouts/Divided"
|
||||
import React from "react"
|
||||
import { Loading } from "docs-ui"
|
||||
|
||||
type DividedLoadingProps = {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render } from "@testing-library/react"
|
||||
|
||||
// mock hooks
|
||||
const mockUseArea = vi.fn(() => ({
|
||||
area: "store",
|
||||
}))
|
||||
|
||||
// mock components
|
||||
vi.mock("@/providers/area", () => ({
|
||||
useArea: () => mockUseArea(),
|
||||
}))
|
||||
|
||||
import DownloadFull from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
cleanup()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("render", () => {
|
||||
test("render download link for store area", () => {
|
||||
const { getByTestId } = render(<DownloadFull />)
|
||||
const link = getByTestId("download-full-link")
|
||||
expect(link).toBeInTheDocument()
|
||||
expect(link).toHaveAttribute("href", "/download/store")
|
||||
expect(link).toHaveAttribute("download")
|
||||
expect(link).toHaveAttribute("target", "_blank")
|
||||
})
|
||||
|
||||
test("render download link for admin area", () => {
|
||||
mockUseArea.mockReturnValue({ area: "admin" })
|
||||
const { getByTestId } = render(<DownloadFull />)
|
||||
const link = getByTestId("download-full-link")
|
||||
expect(link).toBeInTheDocument()
|
||||
expect(link).toHaveAttribute("href", "/download/admin")
|
||||
expect(link).toHaveAttribute("download")
|
||||
expect(link).toHaveAttribute("target", "_blank")
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { Button } from "docs-ui"
|
||||
import { useArea } from "../../providers/area"
|
||||
import { useArea } from "@/providers/area"
|
||||
import Link from "next/link"
|
||||
|
||||
const DownloadFull = () => {
|
||||
@@ -9,7 +10,12 @@ const DownloadFull = () => {
|
||||
|
||||
return (
|
||||
<Button variant="secondary">
|
||||
<Link href={`/download/${area}`} download target="_blank">
|
||||
<Link
|
||||
href={`/download/${area}`}
|
||||
download
|
||||
target="_blank"
|
||||
data-testid="download-full-link"
|
||||
>
|
||||
Download OpenApi Specs Collection
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render } from "@testing-library/react"
|
||||
|
||||
// mock hooks
|
||||
const mockUseArea = vi.fn(() => ({
|
||||
area: "store",
|
||||
}))
|
||||
|
||||
// mock components
|
||||
vi.mock("@/providers/area", () => ({
|
||||
useArea: () => mockUseArea(),
|
||||
}))
|
||||
vi.mock("docs-ui", async () => {
|
||||
const actual = await vi.importActual<typeof React>("docs-ui")
|
||||
return {
|
||||
...actual,
|
||||
Feedback: vi.fn(({ extraData }: { extraData: { area: string } }) => (
|
||||
<div data-testid="feedback" data-area={extraData.area}>Feedback</div>
|
||||
)),
|
||||
}
|
||||
})
|
||||
|
||||
import {Feedback} from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
cleanup()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("render", () => {
|
||||
test("render feedback component for store area", () => {
|
||||
const { getByTestId } = render(<Feedback />)
|
||||
const feedback = getByTestId("feedback")
|
||||
expect(feedback).toBeInTheDocument()
|
||||
expect(feedback).toHaveAttribute("data-area", "store")
|
||||
})
|
||||
|
||||
test("render feedback component for admin area", () => {
|
||||
mockUseArea.mockReturnValue({ area: "admin" })
|
||||
const { getByTestId } = render(<Feedback />)
|
||||
const feedback = getByTestId("feedback")
|
||||
expect(feedback).toBeInTheDocument()
|
||||
expect(feedback).toHaveAttribute("data-area", "admin")
|
||||
})
|
||||
})
|
||||
@@ -1,11 +1,12 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import {
|
||||
Feedback as UiFeedback,
|
||||
FeedbackProps,
|
||||
DocsTrackingEvents,
|
||||
} from "docs-ui"
|
||||
import { useArea } from "../../providers/area"
|
||||
import { useArea } from "@/providers/area"
|
||||
|
||||
export const Feedback = (props: Partial<FeedbackProps>) => {
|
||||
const { area } = useArea()
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render } from "@testing-library/react"
|
||||
|
||||
// mock functions
|
||||
const mockScrollToElement = vi.fn()
|
||||
const mockUseScrollController = vi.fn(() => ({
|
||||
scrollableElement: null as HTMLElement | null,
|
||||
scrollToElement: mockScrollToElement,
|
||||
}))
|
||||
const mockUseSidebar = vi.fn(() => ({
|
||||
activePath: null as string | null,
|
||||
}))
|
||||
|
||||
// mock components
|
||||
vi.mock("docs-ui", () => ({
|
||||
useScrollController: () => mockUseScrollController(),
|
||||
useSidebar: () => mockUseSidebar(),
|
||||
H2: ({
|
||||
passRef,
|
||||
...props
|
||||
}: {
|
||||
passRef: React.RefObject<HTMLHeadingElement>
|
||||
} & React.HTMLAttributes<HTMLHeadingElement>) => (
|
||||
<h2 {...props} ref={passRef} />
|
||||
),
|
||||
}))
|
||||
vi.mock("docs-utils", () => ({
|
||||
getSectionId: vi.fn(() => "section-id"),
|
||||
}))
|
||||
|
||||
import H2 from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("render", () => {
|
||||
test("renders children", () => {
|
||||
const { container } = render(<H2>Test</H2>)
|
||||
const h2 = container.querySelector("h2")
|
||||
expect(h2).toBeInTheDocument()
|
||||
expect(h2).toHaveTextContent("Test")
|
||||
})
|
||||
|
||||
test("renders with custom props", () => {
|
||||
const { container } = render(<H2 className="test-class">Test</H2>)
|
||||
const h2 = container.querySelector("h2")
|
||||
expect(h2).toBeInTheDocument()
|
||||
expect(h2).toHaveClass("test-class")
|
||||
})
|
||||
})
|
||||
|
||||
describe("section id", () => {
|
||||
test("uses generated id", () => {
|
||||
const { container } = render(<H2>Test</H2>)
|
||||
const h2 = container.querySelector("h2")
|
||||
expect(h2).toBeInTheDocument()
|
||||
expect(h2).toHaveAttribute("id", "section-id")
|
||||
})
|
||||
})
|
||||
|
||||
describe("scroll", () => {
|
||||
test("scrolls to element when active path matches id", () => {
|
||||
mockUseScrollController.mockReturnValueOnce({
|
||||
scrollableElement: document.body,
|
||||
scrollToElement: mockScrollToElement,
|
||||
})
|
||||
mockUseSidebar.mockReturnValueOnce({
|
||||
activePath: "section-id",
|
||||
})
|
||||
render(<H2>Test</H2>)
|
||||
const heading = document.querySelector("h2")
|
||||
expect(heading).toBeInTheDocument()
|
||||
expect(mockScrollToElement).toHaveBeenCalledWith(heading)
|
||||
})
|
||||
|
||||
test("does not scroll to element when active path does not match id", () => {
|
||||
mockUseScrollController.mockReturnValueOnce({
|
||||
scrollableElement: document.body,
|
||||
scrollToElement: mockScrollToElement,
|
||||
})
|
||||
mockUseSidebar.mockReturnValueOnce({
|
||||
activePath: "other-section-id",
|
||||
})
|
||||
render(<H2>Test</H2>)
|
||||
const heading = document.querySelector("h2")
|
||||
expect(heading).toBeInTheDocument()
|
||||
expect(mockScrollToElement).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react"
|
||||
import { useScrollController, useSidebar, H2 as UiH2 } from "docs-ui"
|
||||
import { getSectionId } from "docs-utils"
|
||||
import { useEffect, useMemo, useRef, useState } from "react"
|
||||
|
||||
type H2Props = React.HTMLAttributes<HTMLHeadingElement>
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, getByTestId, render, waitFor } from "@testing-library/react"
|
||||
import { OpenAPI } from "types"
|
||||
|
||||
// mock data
|
||||
const mockHttpSecuritySchema: OpenAPI.SecuritySchemeObject = {
|
||||
type: "http",
|
||||
scheme: "bearer",
|
||||
description: "Authentication using Bearer token",
|
||||
"x-displayName": "Bearer Token",
|
||||
}
|
||||
const mockApiKeySecuritySchema: OpenAPI.SecuritySchemeObject = {
|
||||
type: "apiKey",
|
||||
name: "Authorization",
|
||||
description: "Authentication using API key",
|
||||
"x-displayName": "API Key",
|
||||
in: "header",
|
||||
}
|
||||
|
||||
// mock components
|
||||
vi.mock("@/components/MDXContent/Client", () => ({
|
||||
default: ({ content }: { content: string }) => (
|
||||
<div data-testid="mdx-content-client">{content}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/MDXContent/Server", () => ({
|
||||
default: ({ content }: { content: string }) => (
|
||||
<div data-testid="mdx-content-server">{content}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/utils/get-security-schema-type-name", () => ({
|
||||
default: vi.fn(() => "security-schema-type"),
|
||||
}))
|
||||
vi.mock("docs-ui", () => ({
|
||||
Loading: () => <div>Loading</div>,
|
||||
}))
|
||||
|
||||
import SecurityDescription from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders security information for http security scheme", () => {
|
||||
const { getByTestId } = render(
|
||||
<SecurityDescription securitySchema={mockHttpSecuritySchema} />
|
||||
)
|
||||
const titleElement = getByTestId("title")
|
||||
expect(titleElement).toBeInTheDocument()
|
||||
expect(titleElement).toHaveTextContent(mockHttpSecuritySchema["x-displayName"] as string)
|
||||
const securitySchemeTypeElement = getByTestId("security-scheme-type")
|
||||
expect(securitySchemeTypeElement).toBeInTheDocument()
|
||||
expect(securitySchemeTypeElement).toHaveTextContent("security-schema-type")
|
||||
const securitySchemeTypeDetailsElement = getByTestId("security-scheme-type-details")
|
||||
expect(securitySchemeTypeDetailsElement).toBeInTheDocument()
|
||||
expect(securitySchemeTypeDetailsElement).toHaveTextContent("HTTP Authorization Scheme")
|
||||
const securitySchemeTypeDetailsValueElement = getByTestId("security-scheme-type-details-value")
|
||||
expect(securitySchemeTypeDetailsValueElement).toBeInTheDocument()
|
||||
expect(securitySchemeTypeDetailsValueElement).toHaveTextContent(mockHttpSecuritySchema.scheme)
|
||||
})
|
||||
|
||||
test("renders security information for api key security scheme", () => {
|
||||
const { getByTestId } = render(
|
||||
<SecurityDescription securitySchema={mockApiKeySecuritySchema} />
|
||||
)
|
||||
const titleElement = getByTestId("title")
|
||||
expect(titleElement).toBeInTheDocument()
|
||||
expect(titleElement).toHaveTextContent(mockApiKeySecuritySchema["x-displayName"] as string)
|
||||
const securitySchemeTypeElement = getByTestId("security-scheme-type")
|
||||
expect(securitySchemeTypeElement).toBeInTheDocument()
|
||||
expect(securitySchemeTypeElement).toHaveTextContent("security-schema-type")
|
||||
const securitySchemeTypeDetailsElement = getByTestId("security-scheme-type-details")
|
||||
expect(securitySchemeTypeDetailsElement).toBeInTheDocument()
|
||||
expect(securitySchemeTypeDetailsElement).toHaveTextContent("Cookie parameter name")
|
||||
const securitySchemeTypeDetailsValueElement = getByTestId("security-scheme-type-details-value")
|
||||
expect(securitySchemeTypeDetailsValueElement).toBeInTheDocument()
|
||||
expect(securitySchemeTypeDetailsValueElement).toHaveTextContent(mockApiKeySecuritySchema.name)
|
||||
})
|
||||
|
||||
test("render description with server component", async () => {
|
||||
const { getByTestId } = render(
|
||||
<SecurityDescription securitySchema={mockHttpSecuritySchema} isServer={true} />
|
||||
)
|
||||
await waitFor(() => {
|
||||
const mdxContentServerElement = getByTestId("mdx-content-server")
|
||||
expect(mdxContentServerElement).toBeInTheDocument()
|
||||
expect(mdxContentServerElement).toHaveTextContent(mockHttpSecuritySchema.description as string)
|
||||
})
|
||||
})
|
||||
|
||||
test("render description with client component", async () => {
|
||||
const { getByTestId } = render(
|
||||
<SecurityDescription securitySchema={mockHttpSecuritySchema} isServer={false} />
|
||||
)
|
||||
await waitFor(() => {
|
||||
const mdxContentClientElement = getByTestId("mdx-content-client")
|
||||
expect(mdxContentClientElement).toBeInTheDocument()
|
||||
expect(mdxContentClientElement).toHaveTextContent(mockHttpSecuritySchema.description as string)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import type { MDXContentClientProps } from "@/components/MDXContent/Client"
|
||||
import type { MDXContentServerProps } from "@/components/MDXContent/Server"
|
||||
import type { OpenAPI } from "types"
|
||||
@@ -7,14 +8,14 @@ import { Loading } from "docs-ui"
|
||||
import dynamic from "next/dynamic"
|
||||
|
||||
const MDXContentClient = dynamic<MDXContentClientProps>(
|
||||
async () => import("../../../MDXContent/Client"),
|
||||
async () => import("@/components/MDXContent/Client"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<MDXContentClientProps>
|
||||
|
||||
const MDXContentServer = dynamic<MDXContentServerProps>(
|
||||
async () => import("../../../MDXContent/Server"),
|
||||
async () => import("@/components/MDXContent/Server"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
@@ -31,22 +32,25 @@ const SecurityDescription = ({
|
||||
}: SecurityDescriptionProps) => {
|
||||
return (
|
||||
<>
|
||||
<h2>{securitySchema["x-displayName"] as string}</h2>
|
||||
<h2 data-testid="title">{securitySchema["x-displayName"] as string}</h2>
|
||||
{isServer && <MDXContentServer content={securitySchema.description} />}
|
||||
{!isServer && <MDXContentClient content={securitySchema.description} />}
|
||||
<p>
|
||||
<p data-testid="security-scheme-type">
|
||||
<strong>Security Scheme Type:</strong>{" "}
|
||||
{getSecuritySchemaTypeName(securitySchema)}
|
||||
</p>
|
||||
{(securitySchema.type === "http" || securitySchema.type === "apiKey") && (
|
||||
<p className={clsx("bg-medusa-bg-subtle", "p-1")}>
|
||||
<p
|
||||
className={clsx("bg-medusa-bg-subtle", "p-1")}
|
||||
data-testid="security-scheme-type-details"
|
||||
>
|
||||
<strong>
|
||||
{securitySchema.type === "http"
|
||||
? "HTTP Authorization Scheme"
|
||||
: "Cookie parameter name"}
|
||||
:
|
||||
</strong>{" "}
|
||||
<code>
|
||||
<code data-testid="security-scheme-type-details-value">
|
||||
{securitySchema.type === "http"
|
||||
? securitySchema.scheme
|
||||
: securitySchema.name}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render, waitFor } from "@testing-library/react"
|
||||
import { OpenAPI } from "types"
|
||||
|
||||
// mock data
|
||||
const mockSpecs: OpenAPI.OpenAPIV3.Document = {
|
||||
openapi: "3.0.0",
|
||||
info: {
|
||||
title: "Test API",
|
||||
version: "1.0.0",
|
||||
},
|
||||
paths: {},
|
||||
components: {
|
||||
securitySchemes: {
|
||||
bearer: {
|
||||
type: "http",
|
||||
scheme: "bearer",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const mockSpecsWithRef: OpenAPI.OpenAPIV3.Document = {
|
||||
...mockSpecs,
|
||||
components: {
|
||||
securitySchemes: {
|
||||
bearer: {
|
||||
$ref: "#/components/securitySchemes/bearer",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// mock components
|
||||
vi.mock("@/components/MDXComponents/Security/Description", () => ({
|
||||
default: ({ securitySchema }: { securitySchema: OpenAPI.SecuritySchemeObject }) => (
|
||||
<div data-testid="security-description">{securitySchema.type}</div>
|
||||
),
|
||||
}))
|
||||
|
||||
import Security from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("does not render when specs is not provided", () => {
|
||||
const { container } = render(<Security />)
|
||||
const securityDescriptionElements = container.querySelectorAll("[data-testid='security-description']")
|
||||
expect(securityDescriptionElements).toHaveLength(0)
|
||||
})
|
||||
test("renders security information for specs", async () => {
|
||||
const { getByTestId } = render(<Security specs={mockSpecs} />)
|
||||
await waitFor(() => {
|
||||
const securityDescriptionElement = getByTestId("security-description")
|
||||
expect(securityDescriptionElement).toBeInTheDocument()
|
||||
expect(securityDescriptionElement).toHaveTextContent("http")
|
||||
})
|
||||
})
|
||||
|
||||
test("does not render when security scheme is a $ref", () => {
|
||||
const { container } = render(<Security specs={mockSpecsWithRef} />)
|
||||
const securityDescriptionElements = container.querySelectorAll("[data-testid='security-description']")
|
||||
expect(securityDescriptionElements).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { OpenAPI } from "types"
|
||||
import type { SecurityDescriptionProps } from "./Description"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import type { MDXComponents } from "mdx/types"
|
||||
import Security from "./Security"
|
||||
import type { OpenAPI } from "types"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import getCustomComponents from "../../MDXComponents"
|
||||
import type { ScopeType } from "../../MDXComponents"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
"use server"
|
||||
|
||||
import React from "react"
|
||||
import { MDXRemote } from "next-mdx-remote/rsc"
|
||||
import getCustomComponents from "../../MDXComponents"
|
||||
import type { ScopeType } from "../../MDXComponents"
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render } from "@testing-library/react"
|
||||
|
||||
// mock data
|
||||
const mockMethod = "get"
|
||||
const mockMethod2 = "post"
|
||||
const mockMethod3 = "delete"
|
||||
|
||||
// mock components
|
||||
vi.mock("docs-ui", () => ({
|
||||
Badge: ({
|
||||
children,
|
||||
variant,
|
||||
className
|
||||
}: {
|
||||
children: React.ReactNode,
|
||||
variant: string,
|
||||
className: string
|
||||
}) => (
|
||||
<div data-testid="badge" data-variant={variant} className={className}>{children}</div>
|
||||
),
|
||||
capitalize: vi.fn((text: string) => text.charAt(0).toUpperCase() + text.slice(1)),
|
||||
}))
|
||||
|
||||
import MethodLabel from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders method label for get method", () => {
|
||||
const { getByTestId } = render(<MethodLabel method={mockMethod} />)
|
||||
const badgeElement = getByTestId("badge")
|
||||
expect(badgeElement).toBeInTheDocument()
|
||||
expect(badgeElement).toHaveTextContent("Get")
|
||||
expect(badgeElement).toHaveAttribute("data-variant", "green")
|
||||
})
|
||||
test("renders method label for post method", () => {
|
||||
const { getByTestId } = render(<MethodLabel method={mockMethod2} />)
|
||||
const badgeElement = getByTestId("badge")
|
||||
expect(badgeElement).toBeInTheDocument()
|
||||
expect(badgeElement).toHaveTextContent("Post")
|
||||
expect(badgeElement).toHaveAttribute("data-variant", "blue")
|
||||
})
|
||||
test("renders method label for delete method", () => {
|
||||
const { getByTestId } = render(<MethodLabel method={mockMethod3} />)
|
||||
const badgeElement = getByTestId("badge")
|
||||
expect(badgeElement).toBeInTheDocument()
|
||||
expect(badgeElement).toHaveTextContent("Del")
|
||||
expect(badgeElement).toHaveAttribute("data-variant", "red")
|
||||
})
|
||||
test("renders method label with className", () => {
|
||||
const { getByTestId } = render(<MethodLabel method={mockMethod} className="test-class" />)
|
||||
const badgeElement = getByTestId("badge")
|
||||
expect(badgeElement).toBeInTheDocument()
|
||||
expect(badgeElement).toHaveTextContent("Get")
|
||||
expect(badgeElement).toHaveAttribute("data-variant", "green")
|
||||
expect(badgeElement).toHaveClass("test-class")
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import { Badge, capitalize } from "docs-ui"
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render } from "@testing-library/react"
|
||||
|
||||
// mock components
|
||||
vi.mock("@/components/Section/Divider", () => ({
|
||||
default: () => <div data-testid="section-divider">Section Divider</div>,
|
||||
}))
|
||||
vi.mock("docs-ui", () => ({
|
||||
WideSection: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-testid="wide-section">{children}</div>
|
||||
),
|
||||
}))
|
||||
|
||||
import SectionContainer from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders section container with children", () => {
|
||||
const { getByTestId } = render(<SectionContainer>Test</SectionContainer>)
|
||||
const wideSectionElement = getByTestId("wide-section")
|
||||
expect(wideSectionElement).toBeInTheDocument()
|
||||
expect(wideSectionElement).toHaveTextContent("Test")
|
||||
})
|
||||
test("renders section container with no top padding", () => {
|
||||
const { getByTestId } = render(<SectionContainer noTopPadding>Test</SectionContainer>)
|
||||
const sectionContainerElement = getByTestId("section-container")
|
||||
expect(sectionContainerElement).not.toHaveClass("pt-7")
|
||||
})
|
||||
test("renders section container with top padding", () => {
|
||||
const { getByTestId } = render(<SectionContainer noTopPadding={false}>Test</SectionContainer>)
|
||||
const sectionContainerElement = getByTestId("section-container")
|
||||
expect(sectionContainerElement).toHaveClass("pt-7")
|
||||
})
|
||||
test("renders section container with divider", () => {
|
||||
const { getByTestId } = render(<SectionContainer>Test</SectionContainer>)
|
||||
const sectionDividerElement = getByTestId("section-divider")
|
||||
expect(sectionDividerElement).toBeInTheDocument()
|
||||
})
|
||||
test("renders section container with no divider", () => {
|
||||
const { container } = render(<SectionContainer noDivider>Test</SectionContainer>)
|
||||
const sectionDividerElement = container.querySelectorAll("[data-testid='section-divider']")
|
||||
expect(sectionDividerElement).toHaveLength(0)
|
||||
})
|
||||
test("renders section container with className", () => {
|
||||
const { getByTestId } = render(<SectionContainer className="test-class">Test</SectionContainer>)
|
||||
const sectionContainerElement = getByTestId("section-container")
|
||||
expect(sectionContainerElement).toHaveClass("test-class")
|
||||
})
|
||||
test("renders section container with ref", () => {
|
||||
const ref = vi.fn()
|
||||
render(<SectionContainer ref={ref}>Test</SectionContainer>)
|
||||
expect(ref).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import SectionDivider from "../Divider"
|
||||
import { forwardRef } from "react"
|
||||
@@ -23,6 +24,7 @@ const SectionContainer = forwardRef<HTMLDivElement, SectionContainerProps>(
|
||||
!noTopPadding && "pt-7",
|
||||
className
|
||||
)}
|
||||
data-testid="section-container"
|
||||
>
|
||||
<WideSection>{children}</WideSection>
|
||||
{!noDivider && <SectionDivider />}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
|
||||
type SectionDividerProps = {
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render } from "@testing-library/react"
|
||||
|
||||
// mock functions
|
||||
const mockSetActivePath = vi.fn()
|
||||
const mockPush = vi.fn()
|
||||
const mockReplace = vi.fn()
|
||||
const mockUseActiveOnScroll = vi.fn((options: unknown) => ({
|
||||
activeItemId: "",
|
||||
}))
|
||||
const mockUseSidebar = vi.fn(() => ({
|
||||
setActivePath: mockSetActivePath,
|
||||
}))
|
||||
const mockUseRouter = vi.fn(() => ({
|
||||
push: mockPush,
|
||||
replace: mockReplace,
|
||||
}))
|
||||
|
||||
// mock components
|
||||
vi.mock("docs-ui", () => ({
|
||||
useActiveOnScroll: (options: unknown) => mockUseActiveOnScroll(options),
|
||||
useSidebar: () => mockUseSidebar(),
|
||||
}))
|
||||
vi.mock("next/navigation", () => ({
|
||||
useRouter: () => mockUseRouter(),
|
||||
}))
|
||||
|
||||
import Section from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
|
||||
window.history.scrollRestoration = "auto"
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("passes checkActiveOnScroll prop to useActiveOnScroll", () => {
|
||||
render(<Section checkActiveOnScroll>Test</Section>)
|
||||
expect(mockUseActiveOnScroll).toHaveBeenCalledWith({
|
||||
rootElm: undefined,
|
||||
enable: true,
|
||||
useDefaultIfNoActive: false,
|
||||
maxLevel: 2,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("effect hooks", () => {
|
||||
test("sets active path when active item id is not empty", () => {
|
||||
mockUseActiveOnScroll.mockReturnValue({
|
||||
activeItemId: "test",
|
||||
})
|
||||
render(<Section>Test</Section>)
|
||||
expect(mockSetActivePath).toHaveBeenCalledWith("test")
|
||||
expect(mockPush).toHaveBeenCalledWith("#test", { scroll: false })
|
||||
})
|
||||
|
||||
test("does not set active path when active item id is empty", () => {
|
||||
mockUseActiveOnScroll.mockReturnValue({
|
||||
activeItemId: "",
|
||||
})
|
||||
render(<Section>Test</Section>)
|
||||
expect(mockSetActivePath).not.toHaveBeenCalled()
|
||||
expect(mockPush).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("disables scroll restoration when history is available", () => {
|
||||
render(<Section>Test</Section>)
|
||||
expect(history.scrollRestoration).toBe("manual")
|
||||
})
|
||||
|
||||
test("does not disable scroll restoration when history is not available", () => {
|
||||
delete (window.history as any).scrollRestoration
|
||||
render(<Section>Test</Section>)
|
||||
expect(history.scrollRestoration).not.toBe("manual")
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import { useActiveOnScroll, useSidebar } from "docs-ui"
|
||||
import { useRouter } from "next/navigation"
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render } from "@testing-library/react"
|
||||
|
||||
|
||||
import Space from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders space with default styles", () => {
|
||||
const { getByTestId } = render(<Space />)
|
||||
const spaceElement = getByTestId("space")
|
||||
expect(spaceElement).toBeInTheDocument()
|
||||
expect(spaceElement).toHaveStyle({
|
||||
height: "1px",
|
||||
marginTop: "0px",
|
||||
marginBottom: "0px",
|
||||
marginLeft: "0px",
|
||||
marginRight: "0px",
|
||||
})
|
||||
})
|
||||
|
||||
test("renders space with correct styles", () => {
|
||||
const { getByTestId } = render(<Space top={10} bottom={10} left={10} right={10} />)
|
||||
const spaceElement = getByTestId("space")
|
||||
expect(spaceElement).toBeInTheDocument()
|
||||
expect(spaceElement).toHaveStyle({
|
||||
height: "1px",
|
||||
marginTop: "9px",
|
||||
marginBottom: "9px",
|
||||
marginLeft: "10px",
|
||||
marginRight: "10px",
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,5 @@
|
||||
import React from "react"
|
||||
|
||||
type SpaceProps = {
|
||||
top?: number
|
||||
bottom?: number
|
||||
@@ -16,6 +18,7 @@ const Space = ({ top = 0, bottom = 0, left = 0, right = 0 }: SpaceProps) => {
|
||||
marginLeft: `${left}px`,
|
||||
marginRight: `${right}px`,
|
||||
}}
|
||||
data-testid="space"
|
||||
></div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render } from "@testing-library/react"
|
||||
|
||||
// mock data
|
||||
const mockCodeSamples = [
|
||||
{
|
||||
label: "Request Sample 1",
|
||||
lang: "javascript",
|
||||
source: "console.log('Request Sample 1')",
|
||||
},
|
||||
{
|
||||
label: "Request Sample 2",
|
||||
lang: "bash",
|
||||
source: "echo 'Request Sample 2'",
|
||||
},
|
||||
]
|
||||
|
||||
// mock components
|
||||
vi.mock("docs-ui", () => ({
|
||||
CodeBlock: ({
|
||||
source,
|
||||
collapsed,
|
||||
className,
|
||||
lang
|
||||
}: {
|
||||
source: string,
|
||||
collapsed: boolean,
|
||||
className: string
|
||||
lang: string
|
||||
}) => (
|
||||
<div data-testid="code-block" data-collapsed={collapsed} className={className} data-lang={lang}>{source}</div>
|
||||
),
|
||||
CodeTab: ({
|
||||
children,
|
||||
label,
|
||||
value
|
||||
}: {
|
||||
children: React.ReactNode,
|
||||
label: string,
|
||||
value: string
|
||||
}) => (
|
||||
<div data-testid="code-tab" data-label={label} data-value={value}>{children}</div>
|
||||
),
|
||||
CodeTabs: ({
|
||||
children,
|
||||
group
|
||||
}: {
|
||||
children: React.ReactNode,
|
||||
group: string
|
||||
}) => (
|
||||
<div data-testid="code-tabs" data-group={group}>{children}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("slugify", () => ({
|
||||
default: vi.fn((text: string) => text.toLowerCase()),
|
||||
}))
|
||||
|
||||
import TagOperationCodeSectionRequestSamples from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders code tabs and blocks for each code sample", () => {
|
||||
const { getByTestId, getAllByTestId } = render(
|
||||
<TagOperationCodeSectionRequestSamples codeSamples={mockCodeSamples} />
|
||||
)
|
||||
const codeTabsElement = getByTestId("code-tabs")
|
||||
expect(codeTabsElement).toBeInTheDocument()
|
||||
expect(codeTabsElement).toHaveAttribute("data-group", "request-examples")
|
||||
const codeBlocksElement = getAllByTestId("code-block")
|
||||
expect(codeBlocksElement).toHaveLength(mockCodeSamples.length)
|
||||
expect(codeBlocksElement[0]).toHaveAttribute("data-collapsed", "true")
|
||||
expect(codeBlocksElement[1]).toHaveAttribute("data-collapsed", "true")
|
||||
expect(codeBlocksElement[0]).toHaveClass("!mb-0")
|
||||
expect(codeBlocksElement[1]).toHaveClass("!mb-0")
|
||||
expect(codeBlocksElement[0]).toHaveAttribute("data-lang", mockCodeSamples[0].lang)
|
||||
expect(codeBlocksElement[1]).toHaveAttribute("data-lang", mockCodeSamples[1].lang)
|
||||
expect(codeBlocksElement[0]).toHaveTextContent(mockCodeSamples[0].source)
|
||||
expect(codeBlocksElement[1]).toHaveTextContent(mockCodeSamples[1].source)
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import type { OpenAPI } from "types"
|
||||
import { CodeBlock, CodeTab, CodeTabs } from "docs-ui"
|
||||
import slugify from "slugify"
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, fireEvent, render } from "@testing-library/react"
|
||||
import { OpenAPI } from "types"
|
||||
|
||||
// mock data
|
||||
const mockResponse: OpenAPI.ResponseObject = {
|
||||
description: "Mock response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: {
|
||||
type: "string",
|
||||
properties: {}
|
||||
},
|
||||
age: {
|
||||
type: "number",
|
||||
properties: {}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
const mockExamples: OpenAPI.ExampleObject[] = [
|
||||
{
|
||||
title: "example 1",
|
||||
value: "example 1",
|
||||
content: "Example 1",
|
||||
},
|
||||
]
|
||||
|
||||
// mock function
|
||||
const mockUseSchemaExample = vi.fn((options: unknown) => ({
|
||||
examples: mockExamples
|
||||
}))
|
||||
|
||||
// mock components and hooks
|
||||
vi.mock("docs-ui", () => ({
|
||||
CodeBlock: ({ source, collapsed, className, lang }: { source: string, collapsed: boolean, className: string, lang: string }) => (
|
||||
<div data-testid="code-block" data-collapsed={collapsed} className={className} data-lang={lang}>{source}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/hooks/use-schema-example", () => ({
|
||||
default: (options: unknown) => mockUseSchemaExample(options),
|
||||
}))
|
||||
|
||||
import TagsOperationCodeSectionResponsesSample from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("selects first example by default", () => {
|
||||
const { getByTestId, container } = render(
|
||||
<TagsOperationCodeSectionResponsesSample response={mockResponse} />
|
||||
)
|
||||
const codeBlockElement = getByTestId("code-block")
|
||||
expect(codeBlockElement).toBeInTheDocument()
|
||||
expect(codeBlockElement).toHaveAttribute("data-collapsed", "true")
|
||||
expect(codeBlockElement).toHaveAttribute("data-lang", "json")
|
||||
expect(codeBlockElement).toHaveTextContent("Example 1")
|
||||
})
|
||||
|
||||
test("renders empty response when no examples are available", () => {
|
||||
mockUseSchemaExample.mockReturnValue({
|
||||
examples: [],
|
||||
})
|
||||
const { container } = render(
|
||||
<TagsOperationCodeSectionResponsesSample response={mockResponse} />
|
||||
)
|
||||
expect(container).toHaveTextContent("Empty Response")
|
||||
})
|
||||
|
||||
test("renders content type when content is available", () => {
|
||||
const { container } = render(
|
||||
<TagsOperationCodeSectionResponsesSample response={mockResponse} />
|
||||
)
|
||||
const contentTypeElement = container.querySelector("[data-testid='content-type']")
|
||||
expect(contentTypeElement).toBeInTheDocument()
|
||||
expect(contentTypeElement).toHaveTextContent("Content type: application/json")
|
||||
})
|
||||
|
||||
test("doesn't render content type when content is empty", () => {
|
||||
const modifiedResponse: OpenAPI.ResponseObject = {
|
||||
...mockResponse,
|
||||
content: {},
|
||||
}
|
||||
const { container } = render(
|
||||
<TagsOperationCodeSectionResponsesSample response={modifiedResponse} />
|
||||
)
|
||||
const contentTypeElement = container.querySelector("[data-testid='content-type']")
|
||||
expect(contentTypeElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("doesn't render select for single example", () => {
|
||||
mockUseSchemaExample.mockReturnValue({
|
||||
examples: [
|
||||
{ title: "example 1", value: "example 1", content: "Example 1" },
|
||||
],
|
||||
})
|
||||
const { container } = render(
|
||||
<TagsOperationCodeSectionResponsesSample response={mockResponse} />
|
||||
)
|
||||
const selectElement = container.querySelector("select")
|
||||
expect(selectElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders select for multiple examples", () => {
|
||||
mockUseSchemaExample.mockReturnValue({
|
||||
examples: [
|
||||
{ title: "example 1", value: "example 1", content: "Example 1" },
|
||||
{ title: "example 2", value: "example 2", content: "Example 2" },
|
||||
],
|
||||
})
|
||||
const { container } = render(
|
||||
<TagsOperationCodeSectionResponsesSample response={mockResponse} />
|
||||
)
|
||||
const selectElement = container.querySelector("select")
|
||||
expect(selectElement).toBeInTheDocument()
|
||||
const options = selectElement!.querySelectorAll("option")
|
||||
expect(options).toHaveLength(2)
|
||||
expect(options[0]).toHaveValue("example 1")
|
||||
expect(options[1]).toHaveValue("example 2")
|
||||
expect(options[0]).toHaveTextContent("example 1")
|
||||
expect(options[1]).toHaveTextContent("example 2")
|
||||
})
|
||||
|
||||
test("renders empty response when no example is selected", () => {
|
||||
mockUseSchemaExample.mockReturnValue({
|
||||
examples: [
|
||||
{ title: "example 1", value: "example 1", content: "Example 1" },
|
||||
{ title: "example 2", value: "example 2", content: "Example 2" },
|
||||
],
|
||||
})
|
||||
const { container } = render(
|
||||
<TagsOperationCodeSectionResponsesSample response={mockResponse} />
|
||||
)
|
||||
const selectElement = container.querySelector("select")
|
||||
expect(selectElement).toBeInTheDocument()
|
||||
fireEvent.change(selectElement!, { target: { value: "example 3" } })
|
||||
const codeBlockElement = container.querySelector(
|
||||
"[data-testid='code-block']"
|
||||
)
|
||||
expect(codeBlockElement).not.toBeInTheDocument()
|
||||
expect(container).toHaveTextContent("Empty Response")
|
||||
})
|
||||
})
|
||||
|
||||
describe("interaction", () => {
|
||||
test("renders code block for selected example when select is changed", () => {
|
||||
mockUseSchemaExample.mockReturnValue({
|
||||
examples: [
|
||||
{ title: "example 1", value: "example 1", content: "Example 1" },
|
||||
{ title: "example 2", value: "example 2", content: "Example 2" },
|
||||
],
|
||||
})
|
||||
const { container, getByTestId } = render(
|
||||
<TagsOperationCodeSectionResponsesSample response={mockResponse} />
|
||||
)
|
||||
const selectElement = container.querySelector("select")
|
||||
fireEvent.change(selectElement!, { target: { value: "example 2" } })
|
||||
const codeBlockElement = getByTestId("code-block")
|
||||
expect(codeBlockElement).toHaveTextContent("Example 2")
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from "react"
|
||||
import { CodeBlock } from "docs-ui"
|
||||
import type { OpenAPI } from "types"
|
||||
import { useEffect, useState } from "react"
|
||||
import useSchemaExample from "../../../../../../hooks/use-schema-example"
|
||||
import useSchemaExample from "@/hooks/use-schema-example"
|
||||
|
||||
export type TagsOperationCodeSectionResponsesSampleProps = {
|
||||
response: OpenAPI.ResponseObject
|
||||
@@ -27,11 +28,17 @@ const TagsOperationCodeSectionResponsesSample = ({
|
||||
setSelectedExample(examples[0])
|
||||
}, [examples])
|
||||
|
||||
const isEmptyResponse =
|
||||
response?.content === undefined ||
|
||||
Object.keys(response.content).length === 0
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={className}>
|
||||
{response.content && (
|
||||
<span>Content type: {Object.keys(response.content)[0]}</span>
|
||||
{!isEmptyResponse && (
|
||||
<span data-testid="content-type">
|
||||
Content type: {Object.keys(response.content)[0]}
|
||||
</span>
|
||||
)}
|
||||
<>
|
||||
{examples.length > 1 && (
|
||||
@@ -70,5 +77,5 @@ const TagsOperationCodeSectionResponsesSample = ({
|
||||
export default TagsOperationCodeSectionResponsesSample
|
||||
|
||||
const getLanguageFromMedia = (media: string) => {
|
||||
return media.substring(media.indexOf("/"))
|
||||
return media.substring(media.indexOf("/") + 1)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render } from "@testing-library/react"
|
||||
import { OpenAPI } from "types"
|
||||
|
||||
// mock data
|
||||
const mockOperation: OpenAPI.Operation = {
|
||||
operationId: "mockOperation",
|
||||
summary: "Mock Operation",
|
||||
description: "Mock Operation",
|
||||
"x-authenticated": false,
|
||||
"x-codeSamples": [],
|
||||
requestBody: {
|
||||
content: {},
|
||||
},
|
||||
parameters: [],
|
||||
responses: {
|
||||
"200": {
|
||||
description: "OK",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// mock components
|
||||
vi.mock("docs-ui", () => ({
|
||||
Badge: ({ variant, children }: { variant: string, children: React.ReactNode }) => (
|
||||
<div data-testid="badge" data-variant={variant}>{children}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/CodeSection/Responses/Sample", () => ({
|
||||
default: () => <div data-testid="sample">Sample</div>,
|
||||
}))
|
||||
|
||||
import TagsOperationCodeSectionResponses from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders response code badge", () => {
|
||||
const { getByTestId } = render(
|
||||
<TagsOperationCodeSectionResponses operation={mockOperation} />
|
||||
)
|
||||
const badgeElement = getByTestId("badge")
|
||||
expect(badgeElement).toBeInTheDocument()
|
||||
expect(badgeElement).toHaveTextContent("200")
|
||||
expect(badgeElement).toHaveAttribute("data-variant", "green")
|
||||
})
|
||||
|
||||
test("doesn't render when no response is available", () => {
|
||||
const mockOperation: OpenAPI.Operation = {
|
||||
operationId: "mockOperation",
|
||||
summary: "Mock Operation",
|
||||
description: "Mock Operation",
|
||||
"x-authenticated": false,
|
||||
"x-codeSamples": [],
|
||||
requestBody: {
|
||||
content: {},
|
||||
},
|
||||
parameters: [],
|
||||
responses: {},
|
||||
}
|
||||
const { container } = render(
|
||||
<TagsOperationCodeSectionResponses operation={mockOperation} />
|
||||
)
|
||||
expect(container).toBeEmptyDOMElement()
|
||||
})
|
||||
|
||||
test("renders sample component", () => {
|
||||
const { getByTestId } = render(
|
||||
<TagsOperationCodeSectionResponses operation={mockOperation} />
|
||||
)
|
||||
const sampleElement = getByTestId("sample")
|
||||
expect(sampleElement).toBeInTheDocument()
|
||||
expect(sampleElement).toHaveTextContent("Sample")
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import type { OpenAPI } from "types"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { TagsOperationCodeSectionResponsesSampleProps } from "./Sample"
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render, waitFor } from "@testing-library/react"
|
||||
import { OpenAPI } from "types"
|
||||
|
||||
// mock data
|
||||
const mockOperation: OpenAPI.Operation = {
|
||||
operationId: "mockOperation",
|
||||
summary: "Mock Operation",
|
||||
description: "Mock Operation",
|
||||
"x-authenticated": false,
|
||||
"x-codeSamples": [
|
||||
{
|
||||
label: "Request Sample 1",
|
||||
lang: "javascript",
|
||||
source: "console.log('Request Sample 1')",
|
||||
}
|
||||
],
|
||||
requestBody: {
|
||||
content: {},
|
||||
},
|
||||
parameters: [],
|
||||
responses: {
|
||||
"200": {
|
||||
description: "OK",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// mock components
|
||||
vi.mock("@/components/MethodLabel", () => ({
|
||||
default: ({ method }: { method: string }) => (
|
||||
<div data-testid="method-label" data-method={method}>{method}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/CodeSection/RequestSamples", () => ({
|
||||
default: () => <div data-testid="request-samples">Request Samples</div>,
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/CodeSection/Responses", () => ({
|
||||
default: () => <div data-testid="responses">Responses</div>,
|
||||
}))
|
||||
vi.mock("docs-ui", () => ({
|
||||
CopyButton: ({ text }: { text: string }) => (
|
||||
<div data-testid="copy-button" data-text={text}>{text}</div>
|
||||
),
|
||||
}))
|
||||
|
||||
import TagsOperationCodeSection from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders method label, request samples, and responses", async () => {
|
||||
const { getByTestId } = render(
|
||||
<TagsOperationCodeSection operation={mockOperation} method="GET" endpointPath="/api/v1/users" />
|
||||
)
|
||||
const methodLabelElement = getByTestId("method-label")
|
||||
expect(methodLabelElement).toBeInTheDocument()
|
||||
expect(methodLabelElement).toHaveTextContent("GET")
|
||||
const endpointPathElement = getByTestId("endpoint-path")
|
||||
expect(endpointPathElement).toBeInTheDocument()
|
||||
expect(endpointPathElement).toHaveTextContent("/api/v1/users")
|
||||
const responsesElement = getByTestId("responses")
|
||||
expect(responsesElement).toBeInTheDocument()
|
||||
const copyButtonElement = getByTestId("copy-button")
|
||||
expect(copyButtonElement).toBeInTheDocument()
|
||||
expect(copyButtonElement).toHaveTextContent("/api/v1/users")
|
||||
|
||||
await waitFor(() => {
|
||||
const requestSamplesElement = getByTestId("request-samples")
|
||||
expect(requestSamplesElement).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
test("doesn't render request samples when no code samples are available", () => {
|
||||
const modifiedOperation: OpenAPI.Operation = {
|
||||
...mockOperation,
|
||||
"x-codeSamples": [],
|
||||
}
|
||||
const { container } = render(
|
||||
<TagsOperationCodeSection operation={modifiedOperation} method="GET" endpointPath="/api/v1/users" />
|
||||
)
|
||||
const requestSamplesElement = container.querySelector("[data-testid='request-samples']")
|
||||
expect(requestSamplesElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders code section with className", () => {
|
||||
const { getByTestId } = render(
|
||||
<TagsOperationCodeSection operation={mockOperation} method="GET" endpointPath="/api/v1/users" className="test-class" />
|
||||
)
|
||||
const codeSectionElement = getByTestId("code-section")
|
||||
expect(codeSectionElement).toHaveClass("test-class")
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import MethodLabel from "@/components/MethodLabel"
|
||||
import type { OpenAPI } from "types"
|
||||
import TagsOperationCodeSectionResponses from "./Responses"
|
||||
@@ -27,7 +28,10 @@ const TagOperationCodeSection = ({
|
||||
className,
|
||||
}: TagOperationCodeSectionProps) => {
|
||||
return (
|
||||
<div className={clsx("mt-2 flex flex-col gap-2", className)}>
|
||||
<div
|
||||
className={clsx("mt-2 flex flex-col gap-2", className)}
|
||||
data-testid="code-section"
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"bg-medusa-bg-subtle border-medusa-border-base px-0.75 rounded border py-0.5",
|
||||
@@ -36,7 +40,10 @@ const TagOperationCodeSection = ({
|
||||
>
|
||||
<div className={clsx("flex w-[calc(100%-36px)] gap-1")}>
|
||||
<MethodLabel method={method} className="h-fit" />
|
||||
<code className="text-medusa-fg-base =break-words break-all">
|
||||
<code
|
||||
className="text-medusa-fg-base =break-words break-all"
|
||||
data-testid="endpoint-path"
|
||||
>
|
||||
{endpointPath}
|
||||
</code>
|
||||
</div>
|
||||
@@ -44,7 +51,7 @@ const TagOperationCodeSection = ({
|
||||
<SquareTwoStack className="text-medusa-fg-muted" />
|
||||
</CopyButton>
|
||||
</div>
|
||||
{operation["x-codeSamples"] && (
|
||||
{operation["x-codeSamples"] && operation["x-codeSamples"].length > 0 && (
|
||||
<TagOperationCodeSectionRequestSamples
|
||||
codeSamples={operation["x-codeSamples"]}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render } from "@testing-library/react"
|
||||
|
||||
// mock data
|
||||
const mockDeprecationMessage = "This operation is deprecated"
|
||||
|
||||
// mock components
|
||||
vi.mock("docs-ui", () => ({
|
||||
Badge: ({ variant, children }: { variant: string, children: React.ReactNode }) => (
|
||||
<div data-testid="badge" data-variant={variant}>{children}</div>
|
||||
),
|
||||
Tooltip: ({ text, children }: { text: string, children: React.ReactNode }) => (
|
||||
<div data-testid="tooltip" data-text={text}>{children}</div>
|
||||
),
|
||||
}))
|
||||
|
||||
import TagsOperationDescriptionSectionDeprecationNotice from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders deprecation notice with tooltip", () => {
|
||||
const { getByTestId } = render(
|
||||
<TagsOperationDescriptionSectionDeprecationNotice deprecationMessage={mockDeprecationMessage} />
|
||||
)
|
||||
const badgeElement = getByTestId("badge")
|
||||
expect(badgeElement).toBeInTheDocument()
|
||||
expect(badgeElement).toHaveTextContent("deprecated")
|
||||
expect(badgeElement).toHaveAttribute("data-variant", "orange")
|
||||
const tooltipElement = getByTestId("tooltip")
|
||||
expect(tooltipElement).toBeInTheDocument()
|
||||
expect(tooltipElement).toHaveAttribute("data-text", mockDeprecationMessage)
|
||||
})
|
||||
|
||||
test("doesn't render tooltip when no deprecation message is available", () => {
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionDeprecationNotice />
|
||||
)
|
||||
const tooltipElement = container.querySelector("[data-testid='tooltip']")
|
||||
expect(tooltipElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders deprecation notice with className", () => {
|
||||
const { getByTestId } = render(
|
||||
<TagsOperationDescriptionSectionDeprecationNotice deprecationMessage={mockDeprecationMessage} className="test-class" />
|
||||
)
|
||||
const deprecationNoticeElement = getByTestId("deprecation-notice")
|
||||
expect(deprecationNoticeElement).toHaveClass("test-class")
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
import { Badge, Tooltip } from "docs-ui"
|
||||
|
||||
@@ -15,7 +16,10 @@ const TagsOperationDescriptionSectionDeprecationNotice = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx("inline-block", className)}>
|
||||
<div
|
||||
className={clsx("inline-block", className)}
|
||||
data-testid="deprecation-notice"
|
||||
>
|
||||
{deprecationMessage && (
|
||||
<Tooltip text={deprecationMessage}>{getBadge()}</Tooltip>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, fireEvent, render } from "@testing-library/react"
|
||||
import { MenuItem, OpenAPI } from "types"
|
||||
|
||||
// mock data
|
||||
const mockEvents: OpenAPI.OasEvents[] = [
|
||||
{
|
||||
name: "event1",
|
||||
payload: "payload1",
|
||||
description: "description1",
|
||||
},
|
||||
]
|
||||
|
||||
// mock functions
|
||||
const mockParseEventPayload = vi.fn((payload: string) => {
|
||||
return {
|
||||
parsed_payload: payload,
|
||||
payload_for_snippet: payload,
|
||||
}
|
||||
})
|
||||
const mockHandleCopy = vi.fn()
|
||||
const mockUseCopied = vi.fn((options: unknown) => ({
|
||||
handleCopy: mockHandleCopy,
|
||||
isCopied: false,
|
||||
}))
|
||||
const mockUseGenerateSnippet = vi.fn((options: unknown) => ({
|
||||
snippet: "snippet",
|
||||
}))
|
||||
|
||||
// mock components
|
||||
vi.mock("docs-ui", () => ({
|
||||
Badge: ({
|
||||
variant,
|
||||
children,
|
||||
...props
|
||||
}: { variant: string, children: React.ReactNode } & React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div data-testid="badge" data-variant={variant} {...props}>{children}</div>
|
||||
),
|
||||
DetailsSummary: ({ title, subtitle }: { title: string, subtitle: React.ReactNode }) => (
|
||||
<div data-testid="details-summary" data-title={title}>{title}</div>
|
||||
),
|
||||
DropdownMenu: ({
|
||||
dropdownButtonContent,
|
||||
menuItems
|
||||
}: { dropdownButtonContent: React.ReactNode, menuItems: MenuItem[] }) => (
|
||||
<div data-testid="dropdown-menu">
|
||||
<div data-testid="dropdown-button">{dropdownButtonContent}</div>
|
||||
<div data-testid="dropdown-menu-items">
|
||||
{menuItems.map((item, index) => (
|
||||
<div key={index} data-testid={"dropdown-menu-item"} onClick={() => {
|
||||
if ("action" in item) {
|
||||
item.action()
|
||||
}
|
||||
}}>
|
||||
{"title" in item && item.title}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
Link: ({ href, children }: { href: string, children: React.ReactNode }) => (
|
||||
<div data-testid="link" data-href={href}>{children}</div>
|
||||
),
|
||||
MarkdownContent: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-testid="markdown-content">{children}</div>
|
||||
),
|
||||
parseEventPayload: (payload: string) => mockParseEventPayload(payload),
|
||||
Tabs: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-testid="tabs">{children}</div>
|
||||
),
|
||||
TabsContent: ({ value, children }: { value: string, children: React.ReactNode }) => (
|
||||
<div data-testid="tabs-content" data-value={value}>{children}</div>
|
||||
),
|
||||
TabsContentWrapper: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-testid="tabs-content-wrapper">{children}</div>
|
||||
),
|
||||
TabsList: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-testid="tabs-list">{children}</div>
|
||||
),
|
||||
TabsTrigger: ({ value, children }: { value: string, children: React.ReactNode }) => (
|
||||
<div data-testid="tabs-trigger" data-value={value}>{children}</div>
|
||||
),
|
||||
Tooltip: ({
|
||||
text,
|
||||
children,
|
||||
...props
|
||||
}: { text: string, children: React.ReactNode } & React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div data-testid="tooltip" data-text={text} {...props}>{children}</div>
|
||||
),
|
||||
useCopy: (options: unknown) => mockUseCopied(options),
|
||||
useGenerateSnippet: (options: unknown) => mockUseGenerateSnippet(options),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/Parameters", () => ({
|
||||
default: () => <div data-testid="parameters">Parameters</div>,
|
||||
}))
|
||||
vi.mock("@medusajs/icons", () => ({
|
||||
CheckCircle: () => <div data-testid="check-circle">CheckCircle</div>,
|
||||
SquareTwoStack: () => <div data-testid="square-two-stack">SquareTwoStack</div>,
|
||||
Tag: () => <div data-testid="tag">Tag</div>,
|
||||
Brackets: () => <div data-testid="brackets">Brackets</div>,
|
||||
}))
|
||||
|
||||
import TagsOperationDescriptionSectionEvents from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders events", () => {
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionEvents events={mockEvents} />
|
||||
)
|
||||
const detailsSummaryElement = container.querySelector("[data-testid='details-summary']")
|
||||
expect(detailsSummaryElement).toBeInTheDocument()
|
||||
expect(detailsSummaryElement).toHaveAttribute("data-title", "Emitted Events")
|
||||
|
||||
const tabsElement = container.querySelector("[data-testid='tabs']")
|
||||
expect(tabsElement).toBeInTheDocument()
|
||||
|
||||
const tabsContentElement = tabsElement!.querySelectorAll("[data-testid='tabs-content']")
|
||||
expect(tabsContentElement).toHaveLength(mockEvents.length)
|
||||
expect(tabsContentElement[0]).toHaveAttribute("data-value", mockEvents[0].name)
|
||||
expect(tabsContentElement[0]).toHaveTextContent(mockEvents[0].description!)
|
||||
|
||||
const deprecatedBadge = tabsContentElement[0].querySelector("[data-testid='deprecated-badge']")
|
||||
expect(deprecatedBadge).not.toBeInTheDocument()
|
||||
|
||||
const sinceBadge = tabsContentElement[0].querySelector("[data-testid='since-badge']")
|
||||
expect(sinceBadge).not.toBeInTheDocument()
|
||||
|
||||
const parameters = tabsContentElement[0].querySelector("[data-testid='parameters']")
|
||||
expect(parameters).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders deprecated badge without tooltip when event is deprecated and does not have a deprecated message", () => {
|
||||
const modifiedEvents: OpenAPI.OasEvents[] = [
|
||||
{
|
||||
...mockEvents[0],
|
||||
deprecated: true,
|
||||
},
|
||||
]
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionEvents events={modifiedEvents} />
|
||||
)
|
||||
const deprecatedBadge = container.querySelector("[data-testid='deprecated-badge']")
|
||||
expect(deprecatedBadge).toBeInTheDocument()
|
||||
expect(deprecatedBadge).toHaveAttribute("data-variant", "orange")
|
||||
expect(deprecatedBadge).toHaveTextContent("Deprecated")
|
||||
const deprecatedTooltip = container.querySelector("[data-testid='deprecated-tooltip']")
|
||||
expect(deprecatedTooltip).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders deprecated badge with tooltip when event is deprecated and has a deprecated message", () => {
|
||||
const modifiedEvents: OpenAPI.OasEvents[] = [
|
||||
{
|
||||
...mockEvents[0],
|
||||
deprecated: true,
|
||||
deprecated_message: "This event is deprecated",
|
||||
},
|
||||
]
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionEvents events={modifiedEvents} />
|
||||
)
|
||||
const deprecatedBadge = container.querySelector("[data-testid='deprecated-badge']")
|
||||
expect(deprecatedBadge).toBeInTheDocument()
|
||||
expect(deprecatedBadge).toHaveAttribute("data-variant", "orange")
|
||||
expect(deprecatedBadge).toHaveTextContent("Deprecated")
|
||||
const deprecatedTooltip = container.querySelector("[data-testid='deprecated-tooltip']")
|
||||
expect(deprecatedTooltip).toBeInTheDocument()
|
||||
expect(deprecatedTooltip).toHaveAttribute("data-text", "This event is deprecated")
|
||||
})
|
||||
|
||||
test("renders since badge when event has a since version", () => {
|
||||
const modifiedEvents: OpenAPI.OasEvents[] = [
|
||||
{
|
||||
...mockEvents[0],
|
||||
since: "1.0.0",
|
||||
},
|
||||
]
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionEvents events={modifiedEvents} />
|
||||
)
|
||||
const sinceBadge = container.querySelector("[data-testid='since-badge']")
|
||||
expect(sinceBadge).toBeInTheDocument()
|
||||
expect(sinceBadge).toHaveAttribute("data-variant", "blue")
|
||||
expect(sinceBadge).toHaveTextContent("v1.0.0")
|
||||
const sinceTooltip = container.querySelector("[data-testid='since-tooltip']")
|
||||
expect(sinceTooltip).toBeInTheDocument()
|
||||
expect(sinceTooltip).toHaveAttribute("data-text", "This event is emitted since v1.0.0")
|
||||
})
|
||||
|
||||
test("renders SquareTwoStack icon when event name and snippet are not copied", () => {
|
||||
mockUseCopied.mockReturnValue({
|
||||
handleCopy: mockHandleCopy,
|
||||
isCopied: false,
|
||||
})
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionEvents events={mockEvents} />
|
||||
)
|
||||
const squareTwoStackIcon = container.querySelector("[data-testid='square-two-stack']")
|
||||
expect(squareTwoStackIcon).toBeInTheDocument()
|
||||
const checkCircleIcon = container.querySelector("[data-testid='check-circle']")
|
||||
expect(checkCircleIcon).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders CheckCircle icon when event name and snippet are copied", () => {
|
||||
mockUseCopied.mockReturnValue({
|
||||
handleCopy: mockHandleCopy,
|
||||
isCopied: true,
|
||||
})
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionEvents events={mockEvents} />
|
||||
)
|
||||
const checkCircleIcon = container.querySelector("[data-testid='check-circle']")
|
||||
expect(checkCircleIcon).toBeInTheDocument()
|
||||
const squareTwoStackIcon = container.querySelector("[data-testid='square-two-stack']")
|
||||
expect(squareTwoStackIcon).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe("interactions", () => {
|
||||
test("copies event name when copy button is clicked", () => {
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionEvents events={mockEvents} />
|
||||
)
|
||||
expect(mockUseCopied).toHaveBeenCalledWith(mockEvents[0].name)
|
||||
const dropdownMenuItems = container.querySelectorAll("[data-testid='dropdown-menu-item']")
|
||||
expect(dropdownMenuItems).toHaveLength(2)
|
||||
fireEvent.click(dropdownMenuItems[0]!)
|
||||
expect(mockHandleCopy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test("copies subscriber for event when copy button is clicked", () => {
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionEvents events={mockEvents} />
|
||||
)
|
||||
expect(mockUseCopied).toHaveBeenCalledWith("snippet")
|
||||
const dropdownMenuItems = container.querySelectorAll("[data-testid='dropdown-menu-item']")
|
||||
expect(dropdownMenuItems).toHaveLength(2)
|
||||
fireEvent.click(dropdownMenuItems[1]!)
|
||||
expect(mockHandleCopy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import {
|
||||
Badge,
|
||||
DetailsSummary,
|
||||
@@ -105,15 +106,27 @@ const TagsOperationDescriptionSectionEvent = ({
|
||||
</MarkdownContent>
|
||||
{event.deprecated &&
|
||||
(event.deprecated_message ? (
|
||||
<Tooltip text={event.deprecated_message}>
|
||||
<Badge variant="orange">Deprecated</Badge>
|
||||
<Tooltip
|
||||
text={event.deprecated_message}
|
||||
data-testid="deprecated-tooltip"
|
||||
>
|
||||
<Badge variant="orange" data-testid="deprecated-badge">
|
||||
Deprecated
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Badge variant="orange">Deprecated</Badge>
|
||||
<Badge variant="orange" data-testid="deprecated-badge">
|
||||
Deprecated
|
||||
</Badge>
|
||||
))}
|
||||
{event.since && (
|
||||
<Tooltip text={`This event is emitted since v${event.since}`}>
|
||||
<Badge variant="blue">v{event.since}</Badge>
|
||||
<Tooltip
|
||||
text={`This event is emitted since v${event.since}`}
|
||||
data-testid="since-tooltip"
|
||||
>
|
||||
<Badge variant="blue" data-testid="since-badge">
|
||||
v{event.since}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render } from "@testing-library/react"
|
||||
import { OpenAPI } from "types"
|
||||
import { TagOperationParametersProps } from "../../../Parameters"
|
||||
|
||||
// mock data
|
||||
const mockParameters: OpenAPI.Parameter[] = [
|
||||
{
|
||||
name: "parameter1",
|
||||
in: "query",
|
||||
description: "description1",
|
||||
schema: {
|
||||
type: "string",
|
||||
properties: {},
|
||||
} as OpenAPI.SchemaObject,
|
||||
examples: {},
|
||||
},
|
||||
]
|
||||
|
||||
// mock components
|
||||
vi.mock("@/components/Tags/Operation/Parameters", () => ({
|
||||
default: (props: TagOperationParametersProps) => (
|
||||
<div data-testid="parameters" {...props}>
|
||||
{Object.values(props.schemaObject.properties).map((property) => (
|
||||
<div key={property.parameterName} data-testid={"property"}>
|
||||
{property.parameterName}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
import TagsOperationDescriptionSectionParameters from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders header parameters when parameters have header in parameter location", () => {
|
||||
const modifiedParameters: OpenAPI.Parameter[] = [
|
||||
{
|
||||
...mockParameters[0],
|
||||
in: "header",
|
||||
},
|
||||
]
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionParameters parameters={modifiedParameters} />
|
||||
)
|
||||
|
||||
const headerParameters = container.querySelector("[data-testid='header-parameters']")
|
||||
expect(headerParameters).toBeInTheDocument()
|
||||
const properties = headerParameters!.querySelectorAll("[data-testid='property']")
|
||||
expect(properties).toHaveLength(1)
|
||||
expect(properties[0]).toHaveTextContent(mockParameters[0].name)
|
||||
})
|
||||
|
||||
test("does not render header parameters when parameters do not have header in parameter location", () => {
|
||||
const modifiedParameters: OpenAPI.Parameter[] = [
|
||||
{
|
||||
...mockParameters[0],
|
||||
in: "query",
|
||||
},
|
||||
]
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionParameters parameters={modifiedParameters} />
|
||||
)
|
||||
const headerParameters = container.querySelector("[data-testid='header-parameters']")
|
||||
expect(headerParameters).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders path parameters when parameters have path in parameter location", () => {
|
||||
const modifiedParameters: OpenAPI.Parameter[] = [
|
||||
{
|
||||
...mockParameters[0],
|
||||
in: "path",
|
||||
},
|
||||
]
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionParameters parameters={modifiedParameters} />
|
||||
)
|
||||
const pathParameters = container.querySelector("[data-testid='path-parameters']")
|
||||
expect(pathParameters).toBeInTheDocument()
|
||||
const properties = pathParameters!.querySelectorAll("[data-testid='property']")
|
||||
expect(properties).toHaveLength(1)
|
||||
expect(properties[0]).toHaveTextContent(mockParameters[0].name)
|
||||
})
|
||||
|
||||
test("does not render path parameters when parameters do not have path in parameter location", () => {
|
||||
const modifiedParameters: OpenAPI.Parameter[] = [
|
||||
{
|
||||
...mockParameters[0],
|
||||
in: "query",
|
||||
},
|
||||
]
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionParameters parameters={modifiedParameters} />
|
||||
)
|
||||
const pathParameters = container.querySelector("[data-testid='path-parameters']")
|
||||
expect(pathParameters).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders query parameters when parameters have query in parameter location", () => {
|
||||
const modifiedParameters: OpenAPI.Parameter[] = [
|
||||
{
|
||||
...mockParameters[0],
|
||||
in: "query",
|
||||
},
|
||||
]
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionParameters parameters={modifiedParameters} />
|
||||
)
|
||||
const queryParameters = container.querySelector("[data-testid='query-parameters']")
|
||||
expect(queryParameters).toBeInTheDocument()
|
||||
const properties = queryParameters!.querySelectorAll("[data-testid='property']")
|
||||
expect(properties).toHaveLength(1)
|
||||
expect(properties[0]).toHaveTextContent(mockParameters[0].name)
|
||||
})
|
||||
|
||||
test("does not render query parameters when parameters do not have query in parameter location", () => {
|
||||
const modifiedParameters: OpenAPI.Parameter[] = [
|
||||
{
|
||||
...mockParameters[0],
|
||||
in: "header",
|
||||
},
|
||||
]
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionParameters parameters={modifiedParameters} />
|
||||
)
|
||||
const queryParameters = container.querySelector("[data-testid='query-parameters']")
|
||||
expect(queryParameters).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import type { OpenAPI } from "types"
|
||||
import TagOperationParameters from "../../Parameters"
|
||||
|
||||
@@ -60,6 +61,7 @@ const TagsOperationDescriptionSectionParameters = ({
|
||||
<TagOperationParameters
|
||||
schemaObject={headerParameters}
|
||||
topLevel={true}
|
||||
data-testid="header-parameters"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -71,6 +73,7 @@ const TagsOperationDescriptionSectionParameters = ({
|
||||
<TagOperationParameters
|
||||
schemaObject={pathParameters}
|
||||
topLevel={true}
|
||||
data-testid="path-parameters"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -82,6 +85,7 @@ const TagsOperationDescriptionSectionParameters = ({
|
||||
<TagOperationParameters
|
||||
schemaObject={queryParameters}
|
||||
topLevel={true}
|
||||
data-testid="query-parameters"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import type { OpenAPI } from "types"
|
||||
import TagOperationParameters from "../../Parameters"
|
||||
import { DetailsSummary } from "docs-ui"
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render } from "@testing-library/react"
|
||||
import { OpenAPI } from "types"
|
||||
import { TagOperationParametersProps } from "../../../Parameters"
|
||||
|
||||
// mock data
|
||||
const mockResponses: OpenAPI.ResponsesObject = {
|
||||
"200": {
|
||||
description: "success",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// mock components
|
||||
vi.mock("@/components/Tags/Operation/Parameters", () => ({
|
||||
default: (props: TagOperationParametersProps) => (
|
||||
<div data-testid="parameters" {...props}>
|
||||
{Object.entries(props.schemaObject.properties).map(([key, property]) => (
|
||||
<div key={key} data-testid={"property"}>
|
||||
{key}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("docs-ui", () => ({
|
||||
Badge: ({ variant, children }: { variant: string, children: React.ReactNode }) => (
|
||||
<div data-testid="badge" data-variant={variant}>{children}</div>
|
||||
),
|
||||
DetailsSummary: ({
|
||||
title,
|
||||
badge,
|
||||
...props }: { title: string, [key: string]: any }) => (
|
||||
<div data-testid={props["data-testid"]} data-title={title}>
|
||||
{title}
|
||||
{badge}
|
||||
</div>
|
||||
),
|
||||
Details: ({
|
||||
children,
|
||||
summaryElm,
|
||||
...props
|
||||
}: { children: React.ReactNode, [key: string]: any }) => (
|
||||
<div data-testid={props["data-testid"]}>{summaryElm}{children}</div>
|
||||
),
|
||||
}))
|
||||
|
||||
import TagsOperationDescriptionSectionResponses from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders success response with content", () => {
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionResponses responses={mockResponses} />
|
||||
)
|
||||
const successResponse = container.querySelector("[data-testid='response-success']")
|
||||
expect(successResponse).toBeInTheDocument()
|
||||
const successResponseParameters = container.querySelector("[data-testid='response-success-parameters']")
|
||||
expect(successResponseParameters).toBeInTheDocument()
|
||||
const successResponseParametersProperties = successResponseParameters!.querySelectorAll("[data-testid='property']")
|
||||
expect(successResponseParametersProperties).toHaveLength(1)
|
||||
expect(successResponseParametersProperties[0]).toHaveTextContent("name")
|
||||
const successResponseBadge = container.querySelector("[data-testid='badge']")
|
||||
expect(successResponseBadge).toBeInTheDocument()
|
||||
expect(successResponseBadge).toHaveTextContent("Success")
|
||||
expect(successResponseBadge).toHaveAttribute("data-variant", "green")
|
||||
})
|
||||
|
||||
test("renders error response with content", () => {
|
||||
const modifiedResponses: OpenAPI.ResponsesObject = {
|
||||
"400": {
|
||||
description: "error",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionResponses responses={modifiedResponses} />
|
||||
)
|
||||
const errorResponse = container.querySelector("[data-testid='response-error']")
|
||||
expect(errorResponse).toBeInTheDocument()
|
||||
const errorResponseParameters = container.querySelector("[data-testid='response-error-parameters']")
|
||||
expect(errorResponseParameters).toBeInTheDocument()
|
||||
const errorResponseParametersProperties = errorResponseParameters!.querySelectorAll("[data-testid='property']")
|
||||
expect(errorResponseParametersProperties).toHaveLength(1)
|
||||
expect(errorResponseParametersProperties[0]).toHaveTextContent("name")
|
||||
const errorResponseBadge = container.querySelector("[data-testid='badge']")
|
||||
expect(errorResponseBadge).toBeInTheDocument()
|
||||
expect(errorResponseBadge).toHaveTextContent("Error")
|
||||
expect(errorResponseBadge).toHaveAttribute("data-variant", "red")
|
||||
})
|
||||
|
||||
test("renders empty success response", () => {
|
||||
const modifiedResponses: OpenAPI.ResponsesObject = {
|
||||
"204": {
|
||||
description: "empty",
|
||||
content: {},
|
||||
},
|
||||
}
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionResponses responses={modifiedResponses} />
|
||||
)
|
||||
const emptyResponse = container.querySelector("[data-testid='response-empty']")
|
||||
expect(emptyResponse).toBeInTheDocument()
|
||||
const emptyResponseBadge = container.querySelector("[data-testid='badge']")
|
||||
expect(emptyResponseBadge).toBeInTheDocument()
|
||||
expect(emptyResponseBadge).toHaveTextContent("Success")
|
||||
expect(emptyResponseBadge).toHaveAttribute("data-variant", "green")
|
||||
})
|
||||
|
||||
test("renders empty error response", () => {
|
||||
const modifiedResponses: OpenAPI.ResponsesObject = {
|
||||
"400": {
|
||||
description: "empty",
|
||||
content: {},
|
||||
},
|
||||
}
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionResponses responses={modifiedResponses} />
|
||||
)
|
||||
const emptyResponse = container.querySelector("[data-testid='response-empty']")
|
||||
expect(emptyResponse).toBeInTheDocument()
|
||||
const emptyResponseBadge = container.querySelector("[data-testid='badge']")
|
||||
expect(emptyResponseBadge).toBeInTheDocument()
|
||||
expect(emptyResponseBadge).toHaveTextContent("Error")
|
||||
expect(emptyResponseBadge).toHaveAttribute("data-variant", "red")
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import type { OpenAPI } from "types"
|
||||
import clsx from "clsx"
|
||||
import TagOperationParameters from "../../Parameters"
|
||||
@@ -19,9 +20,12 @@ const TagsOperationDescriptionSectionResponses = ({
|
||||
>
|
||||
{Object.entries(responses).map(([code, response], index) => {
|
||||
const successCode = code.startsWith("20")
|
||||
const isEmptyResponse =
|
||||
response?.content === undefined ||
|
||||
Object.keys(response.content).length === 0
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
{response.content && (
|
||||
{!isEmptyResponse && (
|
||||
<>
|
||||
{successCode && (
|
||||
<>
|
||||
@@ -34,6 +38,7 @@ const TagsOperationDescriptionSectionResponses = ({
|
||||
index !== 0 && "border-t-0",
|
||||
index === 0 && "border-b-0"
|
||||
)}
|
||||
data-testid="response-success"
|
||||
/>
|
||||
<TagOperationParameters
|
||||
schemaObject={
|
||||
@@ -41,6 +46,7 @@ const TagsOperationDescriptionSectionResponses = ({
|
||||
.schema
|
||||
}
|
||||
topLevel={true}
|
||||
data-testid="response-success-parameters"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
@@ -56,6 +62,7 @@ const TagsOperationDescriptionSectionResponses = ({
|
||||
}
|
||||
openInitial={index === 0}
|
||||
className={clsx(index > 1 && "border-t-0")}
|
||||
data-testid="response-error"
|
||||
>
|
||||
<TagOperationParameters
|
||||
schemaObject={
|
||||
@@ -63,12 +70,13 @@ const TagsOperationDescriptionSectionResponses = ({
|
||||
.schema
|
||||
}
|
||||
topLevel={true}
|
||||
data-testid="response-error-parameters"
|
||||
/>
|
||||
</Details>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!response.content && (
|
||||
{isEmptyResponse && (
|
||||
<DetailsSummary
|
||||
title={`${code} ${response.description}`}
|
||||
subtitle={"Empty response"}
|
||||
@@ -84,6 +92,7 @@ const TagsOperationDescriptionSectionResponses = ({
|
||||
Object.entries(responses).length > 1 &&
|
||||
"border-b-0"
|
||||
)}
|
||||
data-testid="response-empty"
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, fireEvent, render } from "@testing-library/react"
|
||||
import { OpenAPI } from "types"
|
||||
|
||||
// mock function
|
||||
const mockGetSecuritySchema = vi.fn((key: string) => ({
|
||||
"x-displayName": "Bearer Token",
|
||||
"x-is-auth": true,
|
||||
}))
|
||||
const mockUseBaseSpecs = vi.fn(() => ({
|
||||
getSecuritySchema: mockGetSecuritySchema,
|
||||
}))
|
||||
|
||||
// mock components and hooks
|
||||
vi.mock("@/providers/base-specs", () => ({
|
||||
useBaseSpecs: () => ({
|
||||
getSecuritySchema: mockGetSecuritySchema,
|
||||
}),
|
||||
}))
|
||||
vi.mock("docs-ui", () => ({
|
||||
Card: ({ title, text, href }: { title: string, text: string, href: string }) => (
|
||||
<div data-testid="card" data-title={title} data-href={href}>
|
||||
{text}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
import TagsOperationDescriptionSectionSecurity from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders security with authentication", () => {
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionSecurity security={[{ "bearer": [] }]} />
|
||||
)
|
||||
|
||||
const cardElement = container.querySelector("[data-testid='card']")
|
||||
expect(cardElement).toBeInTheDocument()
|
||||
expect(cardElement).toHaveTextContent("Bearer Token")
|
||||
expect(cardElement).toHaveAttribute("data-href", "#authentication")
|
||||
})
|
||||
|
||||
test("renders security without authentication", () => {
|
||||
mockGetSecuritySchema.mockReturnValue({
|
||||
"x-displayName": "Bearer Token",
|
||||
"x-is-auth": false,
|
||||
})
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionSecurity security={[{ "bearer": [] }]} />
|
||||
)
|
||||
|
||||
const cardElement = container.querySelector("[data-testid='card']")
|
||||
expect(cardElement).toBeInTheDocument()
|
||||
expect(cardElement).toHaveTextContent("Bearer Token")
|
||||
expect(cardElement).not.toHaveAttribute("data-href")
|
||||
})
|
||||
|
||||
test("renders security with multiple security schemes", () => {
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionSecurity security={[{ "bearer": [] }, { "apiKey": [] }]} />
|
||||
)
|
||||
|
||||
const cardElement = container.querySelector("[data-testid='card']")
|
||||
expect(cardElement).toBeInTheDocument()
|
||||
// it renders the same security scheme twice because we're mocking the getSecuritySchema function
|
||||
expect(cardElement).toHaveTextContent("Bearer Token or Bearer Token")
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import { useBaseSpecs } from "@/providers/base-specs"
|
||||
import type { OpenAPI } from "types"
|
||||
import { Card } from "docs-ui"
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, fireEvent, render } from "@testing-library/react"
|
||||
import { OpenAPI } from "types"
|
||||
|
||||
// mock data
|
||||
const mockWorkflow = "test-workflow"
|
||||
|
||||
// mock components
|
||||
vi.mock("@/config", () => ({
|
||||
config: {
|
||||
baseUrl: "https://example.com",
|
||||
},
|
||||
}))
|
||||
vi.mock("docs-ui", () => ({
|
||||
SourceCodeLink: ({ link, text, icon }: { link: string, text: string, icon: React.ReactNode }) => (
|
||||
<div data-testid="source-code-link" data-link={link} data-text={text}>
|
||||
{text} {icon}
|
||||
</div>
|
||||
),
|
||||
DecisionProcessIcon: () => (
|
||||
<div data-testid="decision-process-icon">
|
||||
Icon
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
import TagsOperationDescriptionSectionWorkflowBadge from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders workflow badge", () => {
|
||||
const { container } = render(
|
||||
<TagsOperationDescriptionSectionWorkflowBadge workflow={mockWorkflow} />
|
||||
)
|
||||
|
||||
const sourceCodeLinkElement = container.querySelector("[data-testid='source-code-link']")
|
||||
expect(sourceCodeLinkElement).toBeInTheDocument()
|
||||
expect(sourceCodeLinkElement).toHaveTextContent(mockWorkflow)
|
||||
expect(sourceCodeLinkElement).toHaveAttribute(
|
||||
"data-link",
|
||||
`https://example.com/resources/references/medusa-workflows/${mockWorkflow}`
|
||||
)
|
||||
const decisionProcessIconElement = container.querySelector("[data-testid='decision-process-icon']")
|
||||
expect(decisionProcessIconElement).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react"
|
||||
import { DecisionProcessIcon, SourceCodeLink } from "docs-ui"
|
||||
import { config } from "../../../../../config"
|
||||
import { config } from "@/config"
|
||||
|
||||
export type TagsOperationDescriptionSectionWorkflowBadgeProps = {
|
||||
workflow: string
|
||||
|
||||
@@ -0,0 +1,339 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, fireEvent, render, waitFor } from "@testing-library/react"
|
||||
import { OpenAPI } from "types"
|
||||
|
||||
// mock data
|
||||
const mockOperation: OpenAPI.Operation = {
|
||||
operationId: "test-operation-id",
|
||||
"x-authenticated": false,
|
||||
"x-codeSamples": [],
|
||||
tags: ["test-tag"],
|
||||
summary: "test-summary",
|
||||
description: "test-description",
|
||||
responses: {},
|
||||
parameters: [],
|
||||
requestBody: {
|
||||
content: {},
|
||||
},
|
||||
}
|
||||
|
||||
// mock components
|
||||
vi.mock("@/components/Tags/Operation/DescriptionSection/Security", () => ({
|
||||
default: ({ security }: { security: OpenAPI.OpenAPIV3.SecurityRequirementObject[] }) => (
|
||||
<div data-testid="security">{JSON.stringify(security)}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/DescriptionSection/RequestBody", () => ({
|
||||
default: ({ requestBody }: { requestBody: OpenAPI.RequestObject }) => (
|
||||
<div data-testid="request-body">{JSON.stringify(requestBody)}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/DescriptionSection/Responses", () => ({
|
||||
default: ({ responses }: { responses: OpenAPI.ResponsesObject }) => (
|
||||
<div data-testid="responses">{JSON.stringify(responses)}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/DescriptionSection/Parameters", () => ({
|
||||
default: ({ parameters }: { parameters: OpenAPI.Parameter[] }) => (
|
||||
<div data-testid="parameters">{JSON.stringify(parameters)}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/DescriptionSection/WorkflowBadge", () => ({
|
||||
default: ({ workflow }: { workflow: string }) => (
|
||||
<div data-testid="workflow">{workflow}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/DescriptionSection/Events", () => ({
|
||||
default: ({ events }: { events: OpenAPI.OasEvents[] }) => (
|
||||
<div data-testid="events">{JSON.stringify(events)}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/DescriptionSection/DeprecationNotice", () => ({
|
||||
default: ({ deprecationMessage }: { deprecationMessage: string }) => (
|
||||
<div data-testid="deprecation-notice">{deprecationMessage}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/DescriptionSection/FeatureFlagNotice", () => ({
|
||||
default: ({ featureFlag }: { featureFlag: string }) => (
|
||||
<div data-testid="feature-flag">{featureFlag}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/MDXContent/Client", () => ({
|
||||
default: ({ content }: { content: string }) => (
|
||||
<div data-testid="mdx-content">{content}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("docs-ui", () => ({
|
||||
Badge: ({
|
||||
variant,
|
||||
children,
|
||||
...props
|
||||
}: { variant: string, children: React.ReactNode, [key: string]: unknown }) => (
|
||||
<div data-testid="badge" data-variant={variant} {...props}>{children}</div>
|
||||
),
|
||||
Link: ({
|
||||
href,
|
||||
children,
|
||||
...props
|
||||
}: { href: string, children: React.ReactNode, [key: string]: unknown }) => (
|
||||
<div data-testid="link" data-href={href} {...props}>{children}</div>
|
||||
),
|
||||
FeatureFlagNotice: ({ featureFlag }: { featureFlag: string }) => (
|
||||
<div data-testid="feature-flag">{featureFlag}</div>
|
||||
),
|
||||
H2: ({ children }: { children: React.ReactNode }) => (
|
||||
<h2 data-testid="h2">{children}</h2>
|
||||
),
|
||||
Tooltip: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-testid="tooltip">{children}</div>
|
||||
),
|
||||
MarkdownContent: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-testid="markdown-content">{children}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Feedback", () => ({
|
||||
Feedback: ({ question }: { question: string }) => (
|
||||
<div data-testid="feedback">{question}</div>
|
||||
),
|
||||
}))
|
||||
|
||||
import TagsOperationDescriptionSection from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders operation summary, description, feedback, responses (default)", async () => {
|
||||
const { container } = render(<TagsOperationDescriptionSection operation={mockOperation} />)
|
||||
|
||||
const h2Element = container.querySelector("[data-testid='h2']")
|
||||
expect(h2Element).toBeInTheDocument()
|
||||
expect(h2Element).toHaveTextContent(mockOperation.summary)
|
||||
const mdxContentElement = container.querySelector("[data-testid='mdx-content']")
|
||||
expect(mdxContentElement).toBeInTheDocument()
|
||||
expect(mdxContentElement).toHaveTextContent(mockOperation.description)
|
||||
const feedbackElement = container.querySelector("[data-testid='feedback']")
|
||||
expect(feedbackElement).toBeInTheDocument()
|
||||
expect(feedbackElement).toHaveTextContent("Did this API Route run successfully?")
|
||||
await waitFor(() => {
|
||||
const responsesElement = container.querySelector("[data-testid='responses']")
|
||||
expect(responsesElement).toBeInTheDocument()
|
||||
expect(responsesElement).toHaveTextContent(JSON.stringify(mockOperation.responses))
|
||||
})
|
||||
})
|
||||
|
||||
test("renders deprecated notice when operation is deprecated", async () => {
|
||||
const modifiedOperation: OpenAPI.Operation = {
|
||||
...mockOperation,
|
||||
deprecated: true,
|
||||
"x-deprecated_message": "test-deprecated-message",
|
||||
}
|
||||
const { container } = render(<TagsOperationDescriptionSection operation={modifiedOperation} />)
|
||||
await waitFor(() => {
|
||||
const deprecationNoticeElement = container.querySelector("[data-testid='deprecation-notice']")
|
||||
expect(deprecationNoticeElement).toBeInTheDocument()
|
||||
expect(deprecationNoticeElement).toHaveTextContent("test-deprecated-message")
|
||||
})
|
||||
})
|
||||
|
||||
test("does not render deprecated notice when operation is not deprecated", () => {
|
||||
const { container } = render(<TagsOperationDescriptionSection operation={mockOperation} />)
|
||||
const deprecationNoticeElement = container.querySelector("[data-testid='deprecation-notice']")
|
||||
expect(deprecationNoticeElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders feature flag notice when operation has a feature flag", () => {
|
||||
const modifiedOperation: OpenAPI.Operation = {
|
||||
...mockOperation,
|
||||
"x-featureFlag": "test-feature-flag",
|
||||
}
|
||||
const { container } = render(<TagsOperationDescriptionSection operation={modifiedOperation} />)
|
||||
const featureFlagNoticeElement = container.querySelector("[data-testid='feature-flag']")
|
||||
expect(featureFlagNoticeElement).toBeInTheDocument()
|
||||
expect(featureFlagNoticeElement).toHaveTextContent("test-feature-flag")
|
||||
})
|
||||
|
||||
test("does not render feature flag notice when operation does not have a feature flag", () => {
|
||||
const { container } = render(<TagsOperationDescriptionSection operation={mockOperation} />)
|
||||
const featureFlagNoticeElement = container.querySelector("[data-testid='feature-flag']")
|
||||
expect(featureFlagNoticeElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders since badge when operation has a since", () => {
|
||||
const modifiedOperation: OpenAPI.Operation = {
|
||||
...mockOperation,
|
||||
"x-since": "1.0.0",
|
||||
}
|
||||
const { container } = render(<TagsOperationDescriptionSection operation={modifiedOperation} />)
|
||||
const sinceBadgeElement = container.querySelector("[data-testid='since-badge']")
|
||||
expect(sinceBadgeElement).toBeInTheDocument()
|
||||
expect(sinceBadgeElement).toHaveTextContent("1.0.0")
|
||||
})
|
||||
|
||||
test("does not render since badge when operation does not have a since", () => {
|
||||
const { container } = render(<TagsOperationDescriptionSection operation={mockOperation} />)
|
||||
const sinceBadgeElement = container.querySelector("[data-testid='since-badge']")
|
||||
expect(sinceBadgeElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders operation custom badges when operation has custom badges", () => {
|
||||
const modifiedOperation: OpenAPI.Operation = {
|
||||
...mockOperation,
|
||||
"x-badges": [{
|
||||
text: "test-badge",
|
||||
description: "test-description",
|
||||
variant: "blue",
|
||||
}],
|
||||
}
|
||||
const { container } = render(<TagsOperationDescriptionSection operation={modifiedOperation} />)
|
||||
const customBadgeElement = container.querySelector("[data-testid='custom-badge']")
|
||||
expect(customBadgeElement).toBeInTheDocument()
|
||||
expect(customBadgeElement).toHaveTextContent("test-badge")
|
||||
expect(customBadgeElement).toHaveAttribute("data-variant", "blue")
|
||||
})
|
||||
|
||||
test("does not render custom badges when operation does not have custom badges", () => {
|
||||
const { container } = render(<TagsOperationDescriptionSection operation={mockOperation} />)
|
||||
const customBadgeElement = container.querySelector("[data-testid='custom-badge']")
|
||||
expect(customBadgeElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders operation's workflow badge when operation has a workflow", async () => {
|
||||
const modifiedOperation: OpenAPI.Operation = {
|
||||
...mockOperation,
|
||||
"x-workflow": "test-workflow",
|
||||
}
|
||||
const { container } = render(<TagsOperationDescriptionSection operation={modifiedOperation} />)
|
||||
await waitFor(() => {
|
||||
const workflowBadgeElement = container.querySelector("[data-testid='workflow']")
|
||||
expect(workflowBadgeElement).toBeInTheDocument()
|
||||
expect(workflowBadgeElement).toHaveTextContent("test-workflow")
|
||||
})
|
||||
})
|
||||
|
||||
test("does not render workflow badge when operation does not have a workflow", () => {
|
||||
const { container } = render(<TagsOperationDescriptionSection operation={mockOperation} />)
|
||||
const workflowBadgeElement = container.querySelector("[data-testid='workflow']")
|
||||
expect(workflowBadgeElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders operation's related guide link when operation has a related guide", () => {
|
||||
const modifiedOperation: OpenAPI.Operation = {
|
||||
...mockOperation,
|
||||
externalDocs: {
|
||||
url: "https://example.com",
|
||||
description: "test-description",
|
||||
},
|
||||
}
|
||||
const { container } = render(<TagsOperationDescriptionSection operation={modifiedOperation} />)
|
||||
const relatedGuideLinkElement = container.querySelector("[data-testid='related-guide-link']")
|
||||
expect(relatedGuideLinkElement).toBeInTheDocument()
|
||||
expect(relatedGuideLinkElement).toHaveTextContent("test-description")
|
||||
expect(relatedGuideLinkElement).toHaveAttribute("data-href", "https://example.com")
|
||||
})
|
||||
|
||||
test("does not render related guide link when operation does not have a related guide", () => {
|
||||
const { container } = render(<TagsOperationDescriptionSection operation={mockOperation} />)
|
||||
const relatedGuideLinkElement = container.querySelector("[data-testid='related-guide-link']")
|
||||
expect(relatedGuideLinkElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders operation's security when operation has security", async () => {
|
||||
const modifiedOperation: OpenAPI.Operation = {
|
||||
...mockOperation,
|
||||
security: [{ "bearer": [] }],
|
||||
}
|
||||
const { container } = render(<TagsOperationDescriptionSection operation={modifiedOperation} />)
|
||||
await waitFor(() => {
|
||||
const securityElement = container.querySelector("[data-testid='security']")
|
||||
expect(securityElement).toBeInTheDocument()
|
||||
expect(securityElement).toHaveTextContent(JSON.stringify(modifiedOperation.security))
|
||||
})
|
||||
})
|
||||
|
||||
test("does not render security when operation does not have security", () => {
|
||||
const { container } = render(<TagsOperationDescriptionSection operation={mockOperation} />)
|
||||
const securityElement = container.querySelector("[data-testid='security']")
|
||||
expect(securityElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders operation's parameters when operation has parameters", () => {
|
||||
const modifiedOperation: OpenAPI.Operation = {
|
||||
...mockOperation,
|
||||
parameters: [{
|
||||
name: "test-parameter",
|
||||
in: "query",
|
||||
description: "test-description",
|
||||
schema: {
|
||||
type: "string",
|
||||
properties: {},
|
||||
},
|
||||
examples: {},
|
||||
}],
|
||||
}
|
||||
const { container } = render(<TagsOperationDescriptionSection operation={modifiedOperation} />)
|
||||
const parametersElement = container.querySelector("[data-testid='parameters']")
|
||||
expect(parametersElement).toBeInTheDocument()
|
||||
expect(parametersElement).toHaveTextContent(JSON.stringify(modifiedOperation.parameters))
|
||||
})
|
||||
|
||||
test("does not render parameters when operation does not have parameters", () => {
|
||||
const { container } = render(<TagsOperationDescriptionSection operation={mockOperation} />)
|
||||
const parametersElement = container.querySelector("[data-testid='parameters']")
|
||||
expect(parametersElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders operation's request body when operation has a request body", async () => {
|
||||
const modifiedOperation: OpenAPI.Operation = {
|
||||
...mockOperation,
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
const { container } = render(<TagsOperationDescriptionSection operation={modifiedOperation} />)
|
||||
await waitFor(() => {
|
||||
const requestBodyElement = container.querySelector("[data-testid='request-body']")
|
||||
expect(requestBodyElement).toBeInTheDocument()
|
||||
expect(requestBodyElement).toHaveTextContent(JSON.stringify(modifiedOperation.requestBody))
|
||||
})
|
||||
})
|
||||
|
||||
test("does not render request body when operation does not have a request body", () => {
|
||||
const { container } = render(<TagsOperationDescriptionSection operation={mockOperation} />)
|
||||
const requestBodyElement = container.querySelector("[data-testid='request-body']")
|
||||
expect(requestBodyElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders operation's events when operation has events", async () => {
|
||||
const modifiedOperation: OpenAPI.Operation = {
|
||||
...mockOperation,
|
||||
"x-events": [{
|
||||
name: "test-event",
|
||||
description: "test-description",
|
||||
payload: "test-payload",
|
||||
}],
|
||||
}
|
||||
const { container } = render(<TagsOperationDescriptionSection operation={modifiedOperation} />)
|
||||
await waitFor(() => {
|
||||
const eventsElement = container.querySelector("[data-testid='events']")
|
||||
expect(eventsElement).toBeInTheDocument()
|
||||
expect(eventsElement).toHaveTextContent(JSON.stringify(modifiedOperation["x-events"]))
|
||||
})
|
||||
})
|
||||
|
||||
test("does not render events when operation does not have events", () => {
|
||||
const { container } = render(<TagsOperationDescriptionSection operation={mockOperation} />)
|
||||
const eventsElement = container.querySelector("[data-testid='events']")
|
||||
expect(eventsElement).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import type { OpenAPI } from "types"
|
||||
import type { TagsOperationDescriptionSectionSecurityProps } from "./Security"
|
||||
import type { TagsOperationDescriptionSectionRequestProps } from "./RequestBody"
|
||||
@@ -77,7 +78,7 @@ const TagsOperationDescriptionSection = ({
|
||||
<Tooltip
|
||||
text={`This API route is available since v${operation["x-since"]}`}
|
||||
>
|
||||
<Badge variant="blue" className="ml-0.5">
|
||||
<Badge variant="blue" className="ml-0.5" data-testid="since-badge">
|
||||
v{operation["x-since"]}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
@@ -95,7 +96,11 @@ const TagsOperationDescriptionSection = ({
|
||||
}
|
||||
clickable={true}
|
||||
>
|
||||
<Badge variant={badge.variant || "neutral"} className="ml-0.5">
|
||||
<Badge
|
||||
variant={badge.variant || "neutral"}
|
||||
className="ml-0.5"
|
||||
data-testid="custom-badge"
|
||||
>
|
||||
{badge.text}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
@@ -116,6 +121,7 @@ const TagsOperationDescriptionSection = ({
|
||||
href={operation.externalDocs.url}
|
||||
target="_blank"
|
||||
variant="content"
|
||||
data-testid="related-guide-link"
|
||||
>
|
||||
{operation.externalDocs.description || "Read More"}
|
||||
</Link>
|
||||
@@ -133,16 +139,17 @@ const TagsOperationDescriptionSection = ({
|
||||
security={operation.security}
|
||||
/>
|
||||
)}
|
||||
{operation.parameters && (
|
||||
{operation.parameters && operation.parameters.length > 0 && (
|
||||
<TagsOperationDescriptionSectionParameters
|
||||
parameters={operation.parameters}
|
||||
/>
|
||||
)}
|
||||
{operation.requestBody && (
|
||||
<TagsOperationDescriptionSectionRequest
|
||||
requestBody={operation.requestBody}
|
||||
/>
|
||||
)}
|
||||
{operation.requestBody?.content !== undefined &&
|
||||
Object.keys(operation.requestBody.content).length > 0 && (
|
||||
<TagsOperationDescriptionSectionRequest
|
||||
requestBody={operation.requestBody}
|
||||
/>
|
||||
)}
|
||||
<TagsOperationDescriptionSectionResponses
|
||||
responses={operation.responses}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import React from "react"
|
||||
import { cleanup, render } from "@testing-library/react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
|
||||
// mock components
|
||||
vi.mock("docs-ui", () => ({
|
||||
Badge: ({ children, className }: BadgeProps) => (
|
||||
<div data-testid="badge" className={className}>{children}</div>
|
||||
),
|
||||
Link: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-testid="link">{children}</div>
|
||||
),
|
||||
Tooltip: ({ tooltipChildren, children }: TooltipProps) => (
|
||||
<div data-testid="tooltip">
|
||||
<div data-testid="tooltip-children">{tooltipChildren}</div>
|
||||
<div data-testid="children">{children}</div>
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
import TagsOperationFeatureFlagNotice from ".."
|
||||
import { BadgeProps, TooltipProps } from "docs-ui"
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders feature flag notice for endpoint type by default", () => {
|
||||
const { container } = render(<TagsOperationFeatureFlagNotice featureFlag="test-feature-flag" />)
|
||||
const tooltipElement = container.querySelector("[data-testid='tooltip']")
|
||||
expect(tooltipElement).toBeInTheDocument()
|
||||
const tooltipChildrenElement = container.querySelector("[data-testid='tooltip-children']")
|
||||
expect(tooltipChildrenElement).toBeInTheDocument()
|
||||
expect(tooltipChildrenElement).toHaveTextContent("To use this endpoint, make sure to enable its feature flag: test-feature-flag")
|
||||
|
||||
const badgeElement = container.querySelector("[data-testid='badge']")
|
||||
expect(badgeElement).toBeInTheDocument()
|
||||
expect(badgeElement).toHaveTextContent("feature flag")
|
||||
})
|
||||
|
||||
test("renders feature flag notice for parameter type", () => {
|
||||
const { container } = render(<TagsOperationFeatureFlagNotice featureFlag="test-feature-flag" type="parameter" />)
|
||||
const tooltipElement = container.querySelector("[data-testid='tooltip']")
|
||||
expect(tooltipElement).toBeInTheDocument()
|
||||
const tooltipChildrenElement = container.querySelector("[data-testid='tooltip-children']")
|
||||
expect(tooltipChildrenElement).toBeInTheDocument()
|
||||
expect(tooltipChildrenElement).toHaveTextContent("To use this parameter, make sure to enable its feature flag: test-feature-flag")
|
||||
|
||||
const badgeElement = container.querySelector("[data-testid='badge']")
|
||||
expect(badgeElement).toBeInTheDocument()
|
||||
expect(badgeElement).toHaveTextContent("feature flag")
|
||||
})
|
||||
|
||||
test("renders feature flag notice with tooltipTextClassName", () => {
|
||||
const { container } = render(<TagsOperationFeatureFlagNotice featureFlag="test-feature-flag" tooltipTextClassName="text-red-500" />)
|
||||
const tooltipTextElement = container.querySelector("[data-testid='tooltip-text']")
|
||||
expect(tooltipTextElement).toBeInTheDocument()
|
||||
expect(tooltipTextElement).toHaveClass("text-red-500")
|
||||
})
|
||||
|
||||
test("renders feature flag notice with badgeClassName", () => {
|
||||
const { container } = render(<TagsOperationFeatureFlagNotice featureFlag="test-feature-flag" badgeClassName="bg-red-500" />)
|
||||
const badgeElement = container.querySelector("[data-testid='badge']")
|
||||
expect(badgeElement).toBeInTheDocument()
|
||||
expect(badgeElement).toHaveClass("bg-red-500")
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import { Badge, Link, Tooltip } from "docs-ui"
|
||||
|
||||
export type TagsOperationFeatureFlagNoticeProps = {
|
||||
@@ -16,9 +17,8 @@ const TagsOperationFeatureFlagNotice = ({
|
||||
return (
|
||||
<Tooltip
|
||||
tooltipChildren={
|
||||
<span className={tooltipTextClassName}>
|
||||
To use this {type}, make sure to
|
||||
<br />
|
||||
<span className={tooltipTextClassName} data-testid="tooltip-text">
|
||||
To use this {type}, make sure to <br />
|
||||
<Link
|
||||
href="https://docs.medusajs.com/development/feature-flags/toggle"
|
||||
target="__blank"
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render, waitFor } from "@testing-library/react"
|
||||
import { OpenAPI } from "types"
|
||||
import { InlineCodeProps } from "docs-ui"
|
||||
|
||||
// mock data
|
||||
const mockSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
},
|
||||
}
|
||||
|
||||
// mock functions
|
||||
const mockCapitalize = vi.fn((text: string) => text.charAt(0).toUpperCase() + text.slice(1))
|
||||
|
||||
// mock components
|
||||
vi.mock("docs-ui", () => ({
|
||||
InlineCode: ({ children }: InlineCodeProps) => (
|
||||
<div data-testid="inline-code">{children}</div>
|
||||
),
|
||||
Link: (props: React.HTMLAttributes<HTMLAnchorElement>) => (
|
||||
<a {...props} data-testid="link" />
|
||||
),
|
||||
capitalize: (text: string) => mockCapitalize(text),
|
||||
}))
|
||||
vi.mock("@/components/MDXContent/Client", () => ({
|
||||
default: ({ content }: { content: string }) => (
|
||||
<div data-testid="mdx-content">{content}</div>
|
||||
),
|
||||
}))
|
||||
|
||||
import TagOperationParametersDescription from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders default when schema has a default", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
...mockSchema,
|
||||
default: "test-default",
|
||||
}
|
||||
const { container } = render(<TagOperationParametersDescription schema={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const defaultElement = container.querySelector("[data-testid='default']")
|
||||
expect(defaultElement).toBeInTheDocument()
|
||||
expect(defaultElement).toHaveTextContent("Default: " + JSON.stringify(modifiedSchema.default))
|
||||
})
|
||||
})
|
||||
|
||||
test("does not render default when schema does not have a default", () => {
|
||||
const { container } = render(<TagOperationParametersDescription schema={mockSchema} />)
|
||||
const defaultElement = container.querySelector("[data-testid='default']")
|
||||
expect(defaultElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders enum when schema has an enum", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
...mockSchema,
|
||||
enum: ["test-enum1", "test-enum2"],
|
||||
}
|
||||
const { container } = render(<TagOperationParametersDescription schema={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const enumElement = container.querySelector("[data-testid='enum']")
|
||||
expect(enumElement).toBeInTheDocument()
|
||||
expect(enumElement).toHaveTextContent("Enum: " + modifiedSchema.enum!.map((value) => JSON.stringify(value)).join(", "))
|
||||
})
|
||||
})
|
||||
|
||||
test("does not render enum when schema does not have an enum", () => {
|
||||
const { container } = render(<TagOperationParametersDescription schema={mockSchema} />)
|
||||
const enumElement = container.querySelector("[data-testid='enum']")
|
||||
expect(enumElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders example when schema has an example", () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
...mockSchema,
|
||||
example: "test-example",
|
||||
}
|
||||
const { container } = render(<TagOperationParametersDescription schema={modifiedSchema} />)
|
||||
const exampleElement = container.querySelector("[data-testid='example']")
|
||||
expect(exampleElement).toBeInTheDocument()
|
||||
expect(exampleElement).toHaveTextContent("Example: " + JSON.stringify(modifiedSchema.example))
|
||||
})
|
||||
|
||||
test("does not render example when schema does not have an example", () => {
|
||||
const { container } = render(<TagOperationParametersDescription schema={mockSchema} />)
|
||||
const exampleElement = container.querySelector("[data-testid='example']")
|
||||
expect(exampleElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders description when schema has a description", () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
...mockSchema,
|
||||
description: "test-description",
|
||||
}
|
||||
const { container } = render(<TagOperationParametersDescription schema={modifiedSchema} />)
|
||||
const descriptionElement = container.querySelector("[data-testid='mdx-content']")
|
||||
expect(descriptionElement).toBeInTheDocument()
|
||||
expect(descriptionElement).toHaveTextContent(mockCapitalize(modifiedSchema.description!))
|
||||
})
|
||||
|
||||
test("does not render description when schema does not have a description", () => {
|
||||
const { container } = render(<TagOperationParametersDescription schema={mockSchema} />)
|
||||
const descriptionElement = container.querySelector("[data-testid='mdx-content']")
|
||||
expect(descriptionElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders related guide when schema has a related guide", () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
...mockSchema,
|
||||
externalDocs: {
|
||||
url: "https://example.com",
|
||||
description: "test-description",
|
||||
},
|
||||
}
|
||||
const { container } = render(<TagOperationParametersDescription schema={modifiedSchema} />)
|
||||
const relatedGuideElement = container.querySelector("[data-testid='related-guide']")
|
||||
expect(relatedGuideElement).toBeInTheDocument()
|
||||
expect(relatedGuideElement).toHaveTextContent("Related guide: " + modifiedSchema.externalDocs!.description)
|
||||
const link = relatedGuideElement!.querySelector("[data-testid='link']")
|
||||
expect(link).toHaveAttribute("href", modifiedSchema.externalDocs!.url)
|
||||
expect(link).toHaveAttribute("target", "_blank")
|
||||
expect(link).toHaveAttribute("variant", "content")
|
||||
})
|
||||
|
||||
test("renders related guide with default description when schema has a related guide with no description", () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
...mockSchema,
|
||||
externalDocs: {
|
||||
url: "https://example.com",
|
||||
},
|
||||
}
|
||||
const { container } = render(<TagOperationParametersDescription schema={modifiedSchema} />)
|
||||
const relatedGuideElement = container.querySelector("[data-testid='related-guide']")
|
||||
expect(relatedGuideElement).toBeInTheDocument()
|
||||
expect(relatedGuideElement).toHaveTextContent("Related guide: " + "Read More")
|
||||
const link = relatedGuideElement!.querySelector("[data-testid='link']")
|
||||
expect(link).toHaveAttribute("href", modifiedSchema.externalDocs!.url)
|
||||
expect(link).toHaveAttribute("target", "_blank")
|
||||
expect(link).toHaveAttribute("variant", "content")
|
||||
})
|
||||
|
||||
test("does not render related guide when schema does not have a related guide", () => {
|
||||
const { container } = render(<TagOperationParametersDescription schema={mockSchema} />)
|
||||
const relatedGuideElement = container.querySelector("[data-testid='related-guide']")
|
||||
expect(relatedGuideElement).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import MDXContentClient from "@/components/MDXContent/Client"
|
||||
import type { OpenAPI } from "types"
|
||||
import clsx from "clsx"
|
||||
@@ -9,7 +10,7 @@ const InlineCode = dynamic<InlineCodeProps>(
|
||||
async () => (await import("docs-ui")).InlineCode
|
||||
) as React.FC<InlineCodeProps>
|
||||
|
||||
type TagOperationParametersDescriptionProps = {
|
||||
export type TagOperationParametersDescriptionProps = {
|
||||
schema: OpenAPI.SchemaObject
|
||||
}
|
||||
|
||||
@@ -19,7 +20,7 @@ const TagOperationParametersDescription = ({
|
||||
return (
|
||||
<div className={clsx("pb-0.5 flex flex-col gap-0.25")}>
|
||||
{schema.default !== undefined && (
|
||||
<span>
|
||||
<span data-testid="default">
|
||||
Default:{" "}
|
||||
<InlineCode className="break-words">
|
||||
{JSON.stringify(schema.default)}
|
||||
@@ -27,7 +28,7 @@ const TagOperationParametersDescription = ({
|
||||
</span>
|
||||
)}
|
||||
{schema.enum && (
|
||||
<span>
|
||||
<span data-testid="enum">
|
||||
Enum:{" "}
|
||||
{schema.enum.map((value, index) => (
|
||||
<Fragment key={index}>
|
||||
@@ -38,7 +39,7 @@ const TagOperationParametersDescription = ({
|
||||
</span>
|
||||
)}
|
||||
{schema.example !== undefined && (
|
||||
<span>
|
||||
<span data-testid="example">
|
||||
Example:{" "}
|
||||
<InlineCode className="break-words">
|
||||
{JSON.stringify(schema.example)}
|
||||
@@ -57,7 +58,7 @@ const TagOperationParametersDescription = ({
|
||||
</>
|
||||
)}
|
||||
{schema.externalDocs && (
|
||||
<span>
|
||||
<span data-testid="related-guide">
|
||||
Related guide:{" "}
|
||||
<Link
|
||||
href={schema.externalDocs.url}
|
||||
|
||||
@@ -0,0 +1,729 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render } from "@testing-library/react"
|
||||
import { OpenAPI } from "types"
|
||||
import { BadgeProps, ExpandableNoticeProps, FeatureFlagNoticeProps } from "docs-ui"
|
||||
|
||||
// mock data
|
||||
const mockName = "test-name"
|
||||
const mockSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
},
|
||||
}
|
||||
|
||||
// mock components
|
||||
vi.mock("docs-ui", () => ({
|
||||
Badge: ({ variant, children, className }: BadgeProps) => (
|
||||
<div data-testid="badge" className={className}>{children}</div>
|
||||
),
|
||||
ExpandableNotice: ({ type, link }: ExpandableNoticeProps) => (
|
||||
<div data-testid="expandable-notice" data-type={type} data-link={link}>
|
||||
Expandable Notice
|
||||
</div>
|
||||
),
|
||||
FeatureFlagNotice: ({ featureFlag, type }: FeatureFlagNoticeProps) => (
|
||||
<div data-testid="feature-flag-notice" data-feature-flag={featureFlag} data-type={type}>
|
||||
Feature Flag Notice
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
import TagOperationParametersName from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders name", () => {
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={mockSchema} />
|
||||
)
|
||||
const nameElement = container.querySelector("[data-testid='name']")
|
||||
expect(nameElement).toBeInTheDocument()
|
||||
expect(nameElement).toHaveTextContent(mockName)
|
||||
})
|
||||
|
||||
test("renders deprecated badge when schema is deprecated", () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
...mockSchema,
|
||||
deprecated: true,
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={modifiedSchema} />
|
||||
)
|
||||
const badgeElement = container.querySelector("[data-testid='badge']")
|
||||
expect(badgeElement).toBeInTheDocument()
|
||||
expect(badgeElement).toHaveTextContent("deprecated")
|
||||
})
|
||||
|
||||
test("renders expandable notice when schema is expandable", () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
...mockSchema,
|
||||
"x-expandable": "expanding-relations",
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={modifiedSchema} />
|
||||
)
|
||||
const expandableNoticeElement = container.querySelector("[data-testid='expandable-notice']")
|
||||
expect(expandableNoticeElement).toBeInTheDocument()
|
||||
expect(expandableNoticeElement).toHaveTextContent("Expandable Notice")
|
||||
expect(expandableNoticeElement).toHaveAttribute("data-type", "request")
|
||||
expect(expandableNoticeElement).toHaveAttribute("data-link", "#expanding-relations")
|
||||
})
|
||||
|
||||
test("renders feature flag notice when schema has a feature flag", () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
...mockSchema,
|
||||
"x-featureFlag": "test-feature-flag",
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={modifiedSchema} />
|
||||
)
|
||||
const featureFlagNoticeElement = container.querySelector("[data-testid='feature-flag-notice']")
|
||||
expect(featureFlagNoticeElement).toBeInTheDocument()
|
||||
expect(featureFlagNoticeElement).toHaveTextContent("Feature Flag Notice")
|
||||
expect(featureFlagNoticeElement).toHaveAttribute("data-feature-flag", "test-feature-flag")
|
||||
expect(featureFlagNoticeElement).toHaveAttribute("data-type", "type")
|
||||
})
|
||||
|
||||
test("renders optional span when schema is not required", () => {
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={mockSchema} />
|
||||
)
|
||||
const optionalElement = container.querySelector("[data-testid='optional']")
|
||||
expect(optionalElement).toBeInTheDocument()
|
||||
expect(optionalElement).toHaveTextContent("optional")
|
||||
})
|
||||
|
||||
test("does not render deprecated badge when schema is not deprecated", () => {
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={mockSchema} />
|
||||
)
|
||||
const badgeElement = container.querySelector("[data-testid='badge']")
|
||||
expect(badgeElement).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe("object type schema description formatting", () => {
|
||||
test("formats description for object type schema without title and no nullable", () => {
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={mockSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("object")
|
||||
})
|
||||
|
||||
test("formats description for object type schema with title and no nullable", () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
...mockSchema,
|
||||
title: "test-title",
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={modifiedSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("object (test-title)")
|
||||
})
|
||||
|
||||
test("formats description for object type schema with no title and nullable", () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
...mockSchema,
|
||||
nullable: true,
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={modifiedSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("object or null")
|
||||
})
|
||||
|
||||
test("formats description for object type schema with title and nullable", () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
...mockSchema,
|
||||
title: "test-title",
|
||||
nullable: true,
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={modifiedSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("object (test-title) or null")
|
||||
})
|
||||
})
|
||||
|
||||
describe("array type schema description formatting", () => {
|
||||
test("formats description for array type schema with no items and not nullable", () => {
|
||||
const arraySchema: OpenAPI.ArraySchemaObject = {
|
||||
type: "array",
|
||||
// @ts-expect-error - we are testing the case where items is undefined
|
||||
items: undefined,
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={arraySchema as OpenAPI.SchemaObject} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("Array")
|
||||
})
|
||||
|
||||
test("formats description for array type schema with no items and nullable", () => {
|
||||
const arraySchema: OpenAPI.ArraySchemaObject = {
|
||||
type: "array",
|
||||
// @ts-expect-error - we are testing the case where items is undefined
|
||||
items: undefined,
|
||||
nullable: true,
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={arraySchema as OpenAPI.SchemaObject} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("Array or null")
|
||||
})
|
||||
|
||||
test("formats description for array with object items (with title) and not nullable", () => {
|
||||
const arraySchema: OpenAPI.ArraySchemaObject = {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
title: "test-title",
|
||||
properties: {}
|
||||
},
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={arraySchema as OpenAPI.SchemaObject} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("Array of objects (test-title)")
|
||||
})
|
||||
|
||||
test("formats description for array with object items (with title) and nullable", () => {
|
||||
const arraySchema: OpenAPI.ArraySchemaObject = {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
title: "test-title",
|
||||
properties: {},
|
||||
},
|
||||
properties: {},
|
||||
nullable: true,
|
||||
}
|
||||
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={arraySchema as OpenAPI.SchemaObject} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("Array of objects (test-title) or null")
|
||||
})
|
||||
|
||||
test("formats description for array with object items (without title) and not nullable", () => {
|
||||
const arraySchema: OpenAPI.ArraySchemaObject = {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {},
|
||||
},
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={arraySchema as OpenAPI.SchemaObject} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("Array of objects")
|
||||
})
|
||||
|
||||
test("formats description for array with object items (without title) and nullable", () => {
|
||||
const arraySchema: OpenAPI.ArraySchemaObject = {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {},
|
||||
},
|
||||
properties: {},
|
||||
nullable: true,
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={arraySchema as OpenAPI.SchemaObject} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("Array of objects or null")
|
||||
})
|
||||
|
||||
test("formats description for array with any items and not nullable", () => {
|
||||
const arraySchema: OpenAPI.ArraySchemaObject = {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
properties: {},
|
||||
},
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={arraySchema as OpenAPI.SchemaObject} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("Array of strings")
|
||||
})
|
||||
|
||||
test("formats description for array with any items and nullable", () => {
|
||||
const arraySchema: OpenAPI.ArraySchemaObject = {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
properties: {},
|
||||
},
|
||||
properties: {},
|
||||
nullable: true,
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={arraySchema as OpenAPI.SchemaObject} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("Array of strings or null")
|
||||
})
|
||||
|
||||
test("formats description for array with items of no type and not nullable", () => {
|
||||
const arraySchema: OpenAPI.ArraySchemaObject = {
|
||||
type: "array",
|
||||
items: {
|
||||
properties: {},
|
||||
},
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={arraySchema as OpenAPI.SchemaObject} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("Array of objects")
|
||||
})
|
||||
|
||||
test("formats description for array with items of no type and nullable", () => {
|
||||
const arraySchema: OpenAPI.ArraySchemaObject = {
|
||||
type: "array",
|
||||
items: {
|
||||
properties: {},
|
||||
},
|
||||
properties: {},
|
||||
nullable: true,
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={arraySchema as OpenAPI.SchemaObject} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("Array of objects or null")
|
||||
})
|
||||
})
|
||||
|
||||
describe("union type schema description formatting", () => {
|
||||
test("formats description for union type schema with allOf and not nullable", () => {
|
||||
const unionSchema: OpenAPI.SchemaObject = {
|
||||
allOf: [
|
||||
{ type: "object", properties: {} },
|
||||
{ type: "string", properties: {} },
|
||||
],
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={unionSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("object or string")
|
||||
})
|
||||
|
||||
test("formats description for union type schema with allOf and nullable", () => {
|
||||
const unionSchema: OpenAPI.SchemaObject = {
|
||||
allOf: [
|
||||
{ type: "object", properties: {} },
|
||||
{ type: "string", properties: {} },
|
||||
],
|
||||
properties: {},
|
||||
nullable: true,
|
||||
}
|
||||
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={unionSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("object or string or null")
|
||||
})
|
||||
|
||||
test("formats description for union type schema with anyOf and not nullable", () => {
|
||||
const unionSchema: OpenAPI.SchemaObject = {
|
||||
anyOf: [
|
||||
{ type: "object", properties: {} },
|
||||
{ type: "string", properties: {} },
|
||||
],
|
||||
properties: {},
|
||||
}
|
||||
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={unionSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("object or string")
|
||||
})
|
||||
|
||||
test("formats description for union type schema with anyOf and nullable", () => {
|
||||
const unionSchema: OpenAPI.SchemaObject = {
|
||||
anyOf: [
|
||||
{ type: "object", properties: {} },
|
||||
{ type: "string", properties: {} },
|
||||
],
|
||||
properties: {},
|
||||
nullable: true,
|
||||
}
|
||||
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={unionSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("object or string or null")
|
||||
})
|
||||
|
||||
test("formats description for union type schema with same types and not nullable", () => {
|
||||
const unionSchema: OpenAPI.SchemaObject = {
|
||||
anyOf: [
|
||||
{ type: "object", properties: {} },
|
||||
{ type: "object", properties: {} },
|
||||
],
|
||||
properties: {},
|
||||
nullable: true,
|
||||
}
|
||||
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={unionSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("object")
|
||||
})
|
||||
|
||||
test("formats description for union type schema with same types and nullable", () => {
|
||||
const unionSchema: OpenAPI.SchemaObject = {
|
||||
anyOf: [
|
||||
{ type: "object", properties: {} },
|
||||
{ type: "object", properties: {} },
|
||||
],
|
||||
properties: {},
|
||||
nullable: true,
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={unionSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("object or null")
|
||||
})
|
||||
|
||||
test("gives precedence to allOf over anyOf", () => {
|
||||
const unionSchema: OpenAPI.SchemaObject = {
|
||||
allOf: [
|
||||
{ type: "object", properties: {} },
|
||||
],
|
||||
anyOf: [
|
||||
{ type: "string", properties: {} },
|
||||
],
|
||||
properties: {},
|
||||
}
|
||||
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={unionSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("object")
|
||||
expect(typeDescriptionElement).not.toHaveTextContent("string")
|
||||
})
|
||||
})
|
||||
|
||||
describe("oneOf type schema description formatting", () => {
|
||||
test("formats description for oneOf type schema with one item (without title) and not nullable", () => {
|
||||
const oneOfSchema: OpenAPI.SchemaObject = {
|
||||
oneOf: [
|
||||
{ type: "object", properties: {} },
|
||||
],
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={oneOfSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("object")
|
||||
})
|
||||
|
||||
test("formats description for oneOf type schema with one item (without title) and nullable", () => {
|
||||
const oneOfSchema: OpenAPI.SchemaObject = {
|
||||
oneOf: [
|
||||
{ type: "object", properties: {} },
|
||||
],
|
||||
properties: {},
|
||||
nullable: true,
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={oneOfSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("object or null")
|
||||
})
|
||||
|
||||
test("formats description for oneOf type schema with one item (with title) and not nullable", () => {
|
||||
const oneOfSchema: OpenAPI.SchemaObject = {
|
||||
oneOf: [
|
||||
{ type: "object", title: "test-title", properties: {} },
|
||||
],
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={oneOfSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("test-title")
|
||||
})
|
||||
|
||||
test("formats description for oneOf type schema with one item (with title) and nullable", () => {
|
||||
const oneOfSchema: OpenAPI.SchemaObject = {
|
||||
oneOf: [
|
||||
{ type: "object", title: "test-title", properties: {} },
|
||||
],
|
||||
properties: {},
|
||||
nullable: true,
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={oneOfSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("test-title or null")
|
||||
})
|
||||
|
||||
test("formats description for oneOf type schema with array items (with type) and not nullable", () => {
|
||||
const oneOfSchema: OpenAPI.SchemaObject = {
|
||||
oneOf: [
|
||||
{ type: "array", items: { type: "string", properties: {} }, properties: {} },
|
||||
],
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={oneOfSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("array of strings")
|
||||
})
|
||||
|
||||
test("formats description for oneOf type schema with array items (with type) and nullable", () => {
|
||||
const oneOfSchema: OpenAPI.SchemaObject = {
|
||||
oneOf: [
|
||||
{ type: "array", items: { type: "string", properties: {} }, properties: {} },
|
||||
],
|
||||
properties: {},
|
||||
nullable: true,
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={oneOfSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("array of strings or null")
|
||||
})
|
||||
|
||||
test("formats description for oneOf type schema with array items (without type) and not nullable", () => {
|
||||
const oneOfSchema: OpenAPI.SchemaObject = {
|
||||
oneOf: [
|
||||
{ type: "array", items: { properties: {} }, properties: {} },
|
||||
],
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={oneOfSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("array")
|
||||
})
|
||||
|
||||
test("formats description for oneOf type schema with array items (without type) and nullable", () => {
|
||||
const oneOfSchema: OpenAPI.SchemaObject = {
|
||||
oneOf: [
|
||||
{ type: "array", items: { properties: {} }, properties: {} },
|
||||
],
|
||||
properties: {},
|
||||
nullable: true,
|
||||
}
|
||||
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={oneOfSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("array or null")
|
||||
})
|
||||
|
||||
test("formats description for oneOf type schema with mixed items and not nullable", () => {
|
||||
const oneOfSchema: OpenAPI.SchemaObject = {
|
||||
oneOf: [
|
||||
{ type: "object", properties: {} },
|
||||
{ type: "array", items: { type: "string", properties: {} }, properties: {} },
|
||||
],
|
||||
properties: {},
|
||||
}
|
||||
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={oneOfSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("object or array of strings")
|
||||
})
|
||||
|
||||
test("formats description for oneOf type schema with mixed items and nullable", () => {
|
||||
const oneOfSchema: OpenAPI.SchemaObject = {
|
||||
oneOf: [
|
||||
{ type: "object", properties: {} },
|
||||
{ type: "array", items: { type: "string", properties: {} }, properties: {} },
|
||||
],
|
||||
properties: {},
|
||||
nullable: true,
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={oneOfSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("object or array of strings or null")
|
||||
})
|
||||
})
|
||||
|
||||
describe("default type schema description formatting", () => {
|
||||
test("formats description for default type schema with type, no nullable, no format", () => {
|
||||
const defaultSchema: OpenAPI.SchemaObject = {
|
||||
type: "string",
|
||||
properties: {}
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={defaultSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("string")
|
||||
})
|
||||
|
||||
test("formats description for default type schema with type, nullable, no format", () => {
|
||||
const defaultSchema: OpenAPI.SchemaObject = {
|
||||
type: "string",
|
||||
properties: {},
|
||||
nullable: true,
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={defaultSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("string or null")
|
||||
})
|
||||
|
||||
test("formats description for default type schema with type, no nullable, with format", () => {
|
||||
const defaultSchema: OpenAPI.SchemaObject = {
|
||||
type: "string",
|
||||
properties: {},
|
||||
format: "date-time",
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={defaultSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("string <date-time>")
|
||||
})
|
||||
|
||||
test("formats description for default type schema with type, nullable, with format", () => {
|
||||
const defaultSchema: OpenAPI.SchemaObject = {
|
||||
type: "string",
|
||||
properties: {},
|
||||
format: "date-time",
|
||||
nullable: true,
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={defaultSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("string <date-time> or null")
|
||||
})
|
||||
|
||||
test("formats description for default type schema without type and no nullable, no format", () => {
|
||||
const defaultSchema: OpenAPI.SchemaObject = {
|
||||
properties: {}
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={defaultSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("any")
|
||||
})
|
||||
|
||||
test("formats description for default type schema without type and nullable, no format", () => {
|
||||
const defaultSchema: OpenAPI.SchemaObject = {
|
||||
properties: {},
|
||||
nullable: true,
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={defaultSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("any or null")
|
||||
})
|
||||
|
||||
test("formats description for default type schema without type and no nullable, with format", () => {
|
||||
const defaultSchema: OpenAPI.SchemaObject = {
|
||||
properties: {},
|
||||
format: "date-time",
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={defaultSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("any <date-time>")
|
||||
})
|
||||
|
||||
test("formats description for default type schema without type and nullable, with format", () => {
|
||||
const defaultSchema: OpenAPI.SchemaObject = {
|
||||
properties: {},
|
||||
format: "date-time",
|
||||
nullable: true,
|
||||
}
|
||||
const { container } = render(
|
||||
<TagOperationParametersName name={mockName} isRequired={false} schema={defaultSchema} />
|
||||
)
|
||||
const typeDescriptionElement = container.querySelector("[data-testid='type-description']")
|
||||
expect(typeDescriptionElement).toBeInTheDocument()
|
||||
expect(typeDescriptionElement).toHaveTextContent("any <date-time> or null")
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import type { OpenAPI } from "types"
|
||||
import { Badge, ExpandableNotice, FeatureFlagNotice } from "docs-ui"
|
||||
import { Fragment } from "react"
|
||||
@@ -26,7 +27,7 @@ const TagOperationParametersName = ({
|
||||
case schema.type === "array":
|
||||
typeDescription = (
|
||||
<>
|
||||
{schema.type === "array" && formatArrayDescription(schema.items)}
|
||||
{formatArrayDescription((schema as OpenAPI.ArraySchemaObject).items)}
|
||||
{schema.nullable ? ` or null` : ""}
|
||||
</>
|
||||
)
|
||||
@@ -35,7 +36,7 @@ const TagOperationParametersName = ({
|
||||
case schema.allOf !== undefined:
|
||||
typeDescription = (
|
||||
<>
|
||||
{formatUnionDescription(schema.allOf)}
|
||||
{formatUnionDescription(schema.allOf || schema.anyOf)}
|
||||
{schema.nullable ? ` or null` : ""}
|
||||
</>
|
||||
)
|
||||
@@ -43,7 +44,7 @@ const TagOperationParametersName = ({
|
||||
case schema.oneOf !== undefined:
|
||||
typeDescription = (
|
||||
<>
|
||||
{schema.oneOf?.map((item, index) => (
|
||||
{schema.oneOf!.map((item, index) => (
|
||||
<Fragment key={index}>
|
||||
{index !== 0 && <> or </>}
|
||||
{item.type !== "array" && <>{item.title || item.type}</>}
|
||||
@@ -60,16 +61,21 @@ const TagOperationParametersName = ({
|
||||
typeDescription = (
|
||||
<>
|
||||
{!schema.type ? "any" : schema.type}
|
||||
{schema.nullable ? ` or null` : ""}
|
||||
{schema.format ? ` <${schema.format}>` : ""}
|
||||
{schema.nullable ? ` or null` : ""}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="inline-flex gap-0.5 items-center">
|
||||
<span className="font-monospace">{name}</span>
|
||||
<span className="text-medusa-fg-muted text-compact-small">
|
||||
<span className="font-monospace" data-testid="name">
|
||||
{name}
|
||||
</span>
|
||||
<span
|
||||
className="text-medusa-fg-muted text-compact-small"
|
||||
data-testid="type-description"
|
||||
>
|
||||
{typeDescription}
|
||||
</span>
|
||||
{schema.deprecated && (
|
||||
@@ -84,7 +90,10 @@ const TagOperationParametersName = ({
|
||||
<FeatureFlagNotice featureFlag={schema["x-featureFlag"]} type="type" />
|
||||
)}
|
||||
{!isRequired && (
|
||||
<span className="text-medusa-tag-blue-text text-compact-x-small">
|
||||
<span
|
||||
className="text-medusa-tag-blue-text text-compact-x-small"
|
||||
data-testid="optional"
|
||||
>
|
||||
optional
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import clsx from "clsx"
|
||||
|
||||
export type TagsOperationParametersNestedProps =
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render, waitFor } from "@testing-library/react"
|
||||
import { OpenAPI } from "types"
|
||||
import { TagOperationParametersProps } from "../../../Parameters"
|
||||
|
||||
// mock data
|
||||
const mockSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
},
|
||||
}
|
||||
|
||||
// mock components
|
||||
vi.mock("@/components/Tags/Operation/Parameters", () => ({
|
||||
default: ({ schemaObject }: TagOperationParametersProps) => (
|
||||
<div data-testid="parameters">{JSON.stringify(schemaObject)}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("docs-ui", () => ({
|
||||
Loading: () => <div data-testid="loading">Loading...</div>,
|
||||
}))
|
||||
|
||||
import TagsOperationParametersSection from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders parameters", async () => {
|
||||
const { container } = render(<TagsOperationParametersSection schema={mockSchema} />)
|
||||
await waitFor(() => {
|
||||
const parametersElement = container.querySelector("[data-testid='parameters']")
|
||||
expect(parametersElement).toBeInTheDocument()
|
||||
expect(parametersElement).toHaveTextContent(JSON.stringify(mockSchema))
|
||||
})
|
||||
})
|
||||
test("renders header when header is provided", () => {
|
||||
const { container } = render(
|
||||
<TagsOperationParametersSection header="test-header" schema={mockSchema} />
|
||||
)
|
||||
const headerElement = container.querySelector("[data-testid='header']")
|
||||
expect(headerElement).toBeInTheDocument()
|
||||
expect(headerElement).toHaveTextContent("test-header")
|
||||
})
|
||||
|
||||
test("does not render header when header is not provided", () => {
|
||||
const { container } = render(<TagsOperationParametersSection schema={mockSchema} />)
|
||||
const headerElement = container.querySelector("[data-testid='header']")
|
||||
expect(headerElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders content type when content type is provided", () => {
|
||||
const { container } = render(<TagsOperationParametersSection contentType="test-content-type" schema={mockSchema} />)
|
||||
const contentTypeElement = container.querySelector("[data-testid='content-type']")
|
||||
expect(contentTypeElement).toBeInTheDocument()
|
||||
expect(contentTypeElement).toHaveTextContent("Content type: test-content-type")
|
||||
})
|
||||
|
||||
test("does not render content type when content type is not provided", () => {
|
||||
const { container } = render(<TagsOperationParametersSection schema={mockSchema} />)
|
||||
const contentTypeElement = container.querySelector("[data-testid='content-type']")
|
||||
expect(contentTypeElement).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import type { OpenAPI } from "types"
|
||||
import clsx from "clsx"
|
||||
import type { TagOperationParametersProps } from ".."
|
||||
@@ -27,12 +28,13 @@ const TagsOperationParametersSection = ({
|
||||
{header && (
|
||||
<h3
|
||||
className={clsx(!contentType && "my-2", contentType && "mt-2 mb-0")}
|
||||
data-testid="header"
|
||||
>
|
||||
{header}
|
||||
</h3>
|
||||
)}
|
||||
{contentType && (
|
||||
<span className={clsx("mb-2 inline-block")}>
|
||||
<span className={clsx("mb-2 inline-block")} data-testid="content-type">
|
||||
Content type: {contentType}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render, waitFor } from "@testing-library/react"
|
||||
import { OpenAPI } from "types"
|
||||
import { TagOperationParametersProps } from "../../.."
|
||||
import { TagOperationParametersDefaultProps } from "../../Default"
|
||||
|
||||
// mock components
|
||||
vi.mock("@/components/Tags/Operation/Parameters", () => ({
|
||||
default: ({ schemaObject }: TagOperationParametersProps) => (
|
||||
<div data-testid="parameters">{JSON.stringify(schemaObject)}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/Parameters/Types/Default", () => ({
|
||||
default: ({ schema }: TagOperationParametersDefaultProps) => (
|
||||
<div data-testid="default">{JSON.stringify(schema)}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/Parameters/Nested", () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-testid="nested">{children}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("docs-ui", () => ({
|
||||
Loading: () => <div data-testid="loading">Loading...</div>,
|
||||
Details: ({ children, summaryElm }: { children: React.ReactNode, summaryElm: React.ReactNode }) => (
|
||||
<div data-testid="details">
|
||||
<div data-testid="summary">{summaryElm}</div>
|
||||
<div data-testid="content">{children}</div>
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
import TagsOperationParametersArray from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("does not render when schema is not an array", () => {
|
||||
const mockSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {}
|
||||
}
|
||||
const { container } = render(
|
||||
<TagsOperationParametersArray name="test-name" schema={mockSchema} />
|
||||
)
|
||||
expect(container).toBeEmptyDOMElement()
|
||||
})
|
||||
|
||||
test("renders default when items type is not an array, object, or undefined", async () => {
|
||||
const mockSchema: OpenAPI.SchemaObject = {
|
||||
type: "array",
|
||||
items: { type: "string", properties: {} },
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersArray name="test-name" schema={mockSchema} />)
|
||||
await waitFor(() => {
|
||||
const defaultElement = container.querySelector("[data-testid='default']")
|
||||
expect(defaultElement).toBeInTheDocument()
|
||||
expect(defaultElement).toHaveTextContent(JSON.stringify(mockSchema))
|
||||
})
|
||||
})
|
||||
|
||||
test("renders default when items is undefined", async () => {
|
||||
const mockSchema: OpenAPI.SchemaObject = {
|
||||
type: "array",
|
||||
// @ts-expect-error - we are testing the case where items is undefined
|
||||
items: undefined,
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(
|
||||
<TagsOperationParametersArray name="test-name" schema={mockSchema} />
|
||||
)
|
||||
await waitFor(() => {
|
||||
const defaultElement = container.querySelector("[data-testid='default']")
|
||||
expect(defaultElement).toBeInTheDocument()
|
||||
expect(defaultElement).toHaveTextContent(JSON.stringify({ ...mockSchema, items: undefined }))
|
||||
})
|
||||
})
|
||||
|
||||
test("renders default when items type is an object with no properties, allOf, anyOf, or oneOf", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "array",
|
||||
// @ts-expect-error - we are testing the case where items is an object with no properties, allOf, anyOf, or oneOf
|
||||
items: { type: "object" },
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersArray name="test-name" schema={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const defaultElement = container.querySelector("[data-testid='default']")
|
||||
expect(defaultElement).toBeInTheDocument()
|
||||
expect(defaultElement).toHaveTextContent(JSON.stringify(modifiedSchema))
|
||||
})
|
||||
})
|
||||
|
||||
test("renders details when items type is an object with properties, allOf, anyOf, or oneOf", () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "array",
|
||||
items: { type: "object", properties: { name: { type: "string", properties: {} } } },
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersArray name="test-name" schema={modifiedSchema} />)
|
||||
const detailsElement = container.querySelector("[data-testid='details']")
|
||||
expect(detailsElement).toBeInTheDocument()
|
||||
expect(detailsElement).toHaveTextContent(JSON.stringify(modifiedSchema))
|
||||
})
|
||||
|
||||
test("renders details when items type is an array", () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "array",
|
||||
items: { type: "string", properties: {} },
|
||||
properties: {},
|
||||
},
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersArray name="test-name" schema={modifiedSchema} />)
|
||||
const detailsElement = container.querySelector("[data-testid='details']")
|
||||
expect(detailsElement).toBeInTheDocument()
|
||||
expect(detailsElement).toHaveTextContent(JSON.stringify(modifiedSchema))
|
||||
})
|
||||
|
||||
test("renders items in nested parameters when items type is an array", () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "array",
|
||||
items: { type: "array", items: { type: "string", properties: {} }, properties: {} },
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersArray name="test-name" schema={modifiedSchema} />)
|
||||
const nestedElement = container.querySelector("[data-testid='nested']")
|
||||
expect(nestedElement).toBeInTheDocument()
|
||||
expect(nestedElement).toHaveTextContent(JSON.stringify(modifiedSchema.items))
|
||||
})
|
||||
|
||||
test("renders items in nested parameters when items type is an object", () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "array",
|
||||
items: { type: "object", properties: { name: { type: "string", properties: {} } } },
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersArray name="test-name" schema={modifiedSchema} />)
|
||||
const nestedElement = container.querySelector("[data-testid='nested']")
|
||||
expect(nestedElement).toBeInTheDocument()
|
||||
expect(nestedElement).toHaveTextContent(JSON.stringify(modifiedSchema.items))
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import type { OpenAPI } from "types"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { TagOperationParametersDefaultProps } from "../Default"
|
||||
@@ -7,14 +8,14 @@ import { Details, Loading } from "docs-ui"
|
||||
|
||||
const TagOperationParametersDefault =
|
||||
dynamic<TagOperationParametersDefaultProps>(
|
||||
async () => import("../Default"),
|
||||
async () => import("@/components/Tags/Operation/Parameters/Types/Default"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersDefaultProps>
|
||||
|
||||
const TagOperationParameters = dynamic<TagOperationParametersProps>(
|
||||
async () => import("../.."),
|
||||
async () => import("@/components/Tags/Operation/Parameters"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render, waitFor } from "@testing-library/react"
|
||||
import { OpenAPI } from "types"
|
||||
import { TagOperationParametersNameProps } from "../../../Name"
|
||||
import { TagOperationParametersDescriptionProps } from "../../../Description"
|
||||
|
||||
// mock data
|
||||
const mockSchema: OpenAPI.SchemaObject = {
|
||||
type: "string",
|
||||
properties: {},
|
||||
}
|
||||
|
||||
// mock components
|
||||
vi.mock("@/components/Tags/Operation/Parameters/Description", () => ({
|
||||
default: ({ schema }: TagOperationParametersDescriptionProps) => (
|
||||
<div data-testid="description">{JSON.stringify(schema)}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/Parameters/Name", () => ({
|
||||
default: ({ name, isRequired, schema }: TagOperationParametersNameProps) => (
|
||||
<div data-testid="name">{name} {isRequired ? "required" : "optional"} {JSON.stringify(schema)}</div>
|
||||
),
|
||||
}))
|
||||
|
||||
import TagsOperationParametersDefault from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders description", async () => {
|
||||
const { container } = render(<TagsOperationParametersDefault schema={mockSchema} />)
|
||||
const descriptionElement = container.querySelector("[data-testid='description']")
|
||||
expect(descriptionElement).toBeInTheDocument()
|
||||
expect(descriptionElement).toHaveTextContent(JSON.stringify(mockSchema))
|
||||
})
|
||||
|
||||
test("renders name when name is provided", () => {
|
||||
const { container } = render(<TagsOperationParametersDefault name="test-name" schema={mockSchema} />)
|
||||
const nameElement = container.querySelector("[data-testid='name']")
|
||||
expect(nameElement).toBeInTheDocument()
|
||||
expect(nameElement).toHaveTextContent("test-name")
|
||||
})
|
||||
|
||||
test("renders name with required when isRequired is true", () => {
|
||||
const { container } = render(<TagsOperationParametersDefault name="test-name" schema={mockSchema} isRequired={true} />)
|
||||
const nameElement = container.querySelector("[data-testid='name']")
|
||||
expect(nameElement).toBeInTheDocument()
|
||||
expect(nameElement).toHaveTextContent("test-name required")
|
||||
})
|
||||
|
||||
test("does not render name when name is not provided", () => {
|
||||
const { container } = render(<TagsOperationParametersDefault schema={mockSchema} />)
|
||||
const nameElement = container.querySelector("[data-testid='name']")
|
||||
expect(nameElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("add expandable class when expandable is true", () => {
|
||||
const { container } = render(<TagsOperationParametersDefault schema={mockSchema} expandable={true} />)
|
||||
const element = container.querySelector("[data-testid='default']")
|
||||
expect(element).toHaveClass("w-[calc(100%-16px)]")
|
||||
expect(element).not.toHaveClass("w-full pl-1")
|
||||
})
|
||||
|
||||
test("does not add expandable class when expandable is false", () => {
|
||||
const { container } = render(<TagsOperationParametersDefault schema={mockSchema} expandable={false} />)
|
||||
const element = container.querySelector("[data-testid='default']")
|
||||
expect(element).not.toHaveClass("w-[calc(100%-16px)]")
|
||||
expect(element).toHaveClass("w-full pl-1")
|
||||
})
|
||||
|
||||
test("adds className when provided", () => {
|
||||
const { container } = render(<TagsOperationParametersDefault schema={mockSchema} className="test-class" />)
|
||||
const element = container.querySelector("[data-testid='default']")
|
||||
expect(element).toHaveClass("test-class")
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import type { OpenAPI } from "types"
|
||||
import TagOperationParametersDescription from "../../Description"
|
||||
import clsx from "clsx"
|
||||
@@ -26,6 +27,7 @@ const TagOperationParametersDefault = ({
|
||||
!expandable && "w-full pl-1",
|
||||
className
|
||||
)}
|
||||
data-testid="default"
|
||||
>
|
||||
{name && (
|
||||
<TagOperationParametersName
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render, waitFor } from "@testing-library/react"
|
||||
import { OpenAPI } from "types"
|
||||
import { TagOperationParametersDefaultProps } from "../../Default"
|
||||
import { TagOperationParametersProps } from "../../.."
|
||||
|
||||
// mock functions
|
||||
const mockCheckRequired = vi.fn()
|
||||
|
||||
// mock components
|
||||
vi.mock("@/components/Tags/Operation/Parameters/Types/Default", () => ({
|
||||
default: ({ schema }: TagOperationParametersDefaultProps) => (
|
||||
<div data-testid="default">{JSON.stringify(schema)}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/Parameters/Nested", () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-testid="nested">{children}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("docs-ui", () => ({
|
||||
Loading: () => <div data-testid="loading">Loading...</div>,
|
||||
Details: ({ children, summaryElm }: { children: React.ReactNode, summaryElm: React.ReactNode }) => (
|
||||
<div data-testid="details">
|
||||
<div data-testid="summary">{summaryElm}</div>
|
||||
<div data-testid="content">{children}</div>
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/Parameters", () => ({
|
||||
default: ({ schemaObject, isRequired, isExpanded }: TagOperationParametersProps) => (
|
||||
<div data-testid="parameters">
|
||||
{JSON.stringify(schemaObject)} {isRequired ? "required" : "optional"}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/utils/check-required", () => ({
|
||||
default: (schema: OpenAPI.SchemaObject, property: string) => mockCheckRequired(schema, property),
|
||||
}))
|
||||
|
||||
import TagsOperationParametersObject from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("does not render when schema is not an object", () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "array",
|
||||
items: { type: "string", properties: {} },
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersObject schema={modifiedSchema} />)
|
||||
expect(container).toBeEmptyDOMElement()
|
||||
})
|
||||
|
||||
test("does not render when schema type is undefined", () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: undefined,
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersObject schema={modifiedSchema} />)
|
||||
expect(container).toBeEmptyDOMElement()
|
||||
})
|
||||
|
||||
test("does not render when properties is empty and additionalProperties is empty and name is not provided", () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {},
|
||||
additionalProperties: undefined,
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersObject schema={modifiedSchema} />)
|
||||
expect(container).toBeEmptyDOMElement()
|
||||
})
|
||||
|
||||
test("renders description only when properties is empty and additionalProperties is empty and name is provided", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {},
|
||||
additionalProperties: undefined,
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersObject schema={modifiedSchema} name="test-name" />)
|
||||
await waitFor(() => {
|
||||
const defaultElement = container.querySelector("[data-testid='default']")
|
||||
expect(defaultElement).toBeInTheDocument()
|
||||
expect(defaultElement).toHaveTextContent("object")
|
||||
const parametersElement = container.querySelector("[data-testid='parameters']")
|
||||
expect(parametersElement).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
test("renders parameters only when topLevel is true", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
},
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersObject schema={modifiedSchema} topLevel={true} />)
|
||||
await waitFor(() => {
|
||||
const parametersElement = container.querySelector("[data-testid='parameters']")
|
||||
expect(parametersElement).toBeInTheDocument()
|
||||
expect(parametersElement).toHaveTextContent(JSON.stringify({
|
||||
type: "string",
|
||||
properties: {},
|
||||
parameterName: "name",
|
||||
}))
|
||||
const nestedElement = container.querySelector("[data-testid='nested']")
|
||||
expect(nestedElement).not.toBeInTheDocument()
|
||||
const detailsElement = container.querySelector("[data-testid='details']")
|
||||
expect(detailsElement).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
test("renders description and properties when properties is not empty", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
},
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersObject schema={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const descriptionElement = container.querySelector("[data-testid='default']")
|
||||
expect(descriptionElement).toBeInTheDocument()
|
||||
expect(descriptionElement).toHaveTextContent(JSON.stringify(modifiedSchema))
|
||||
const parametersElement = container.querySelector("[data-testid='parameters']")
|
||||
expect(parametersElement).toBeInTheDocument()
|
||||
expect(parametersElement).toHaveTextContent(JSON.stringify({
|
||||
type: "string",
|
||||
properties: {},
|
||||
parameterName: "name",
|
||||
}))
|
||||
const nestedElement = container.querySelector("[data-testid='nested']")
|
||||
expect(nestedElement).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
test("renders description and properties when additionalProperties is not empty", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {},
|
||||
additionalProperties: { type: "object", properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
} },
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersObject schema={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const descriptionElement = container.querySelector("[data-testid='default']")
|
||||
expect(descriptionElement).toBeInTheDocument()
|
||||
expect(descriptionElement).toHaveTextContent("object")
|
||||
})
|
||||
})
|
||||
|
||||
test("renders description in summary when isExpanded is true", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
},
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersObject schema={modifiedSchema} isExpanded={true} />)
|
||||
await waitFor(() => {
|
||||
const summaryElement = container.querySelector("summary")
|
||||
expect(summaryElement).toBeInTheDocument()
|
||||
const descriptionElement = summaryElement?.querySelector("[data-testid='default']")
|
||||
expect(descriptionElement).toBeInTheDocument()
|
||||
expect(descriptionElement).toHaveTextContent("object")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("parameters rendering", () => {
|
||||
test("renders parameters when not empty", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
},
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersObject schema={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const parametersElement = container.querySelector("[data-testid='parameters']")
|
||||
expect(parametersElement).toBeInTheDocument()
|
||||
expect(parametersElement).toHaveTextContent(JSON.stringify({
|
||||
type: "string",
|
||||
properties: {},
|
||||
parameterName: "name",
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
test("renders parameters when additionalProperties is not empty", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {},
|
||||
additionalProperties: { type: "object", properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
} },
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersObject schema={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const parametersElement = container.querySelector("[data-testid='parameters']")
|
||||
expect(parametersElement).toBeInTheDocument()
|
||||
expect(parametersElement).toHaveTextContent(JSON.stringify({
|
||||
type: "string",
|
||||
properties: {},
|
||||
parameterName: "name",
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
test("gives precedence to properties over additionalProperties", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
},
|
||||
additionalProperties: { type: "number", properties: {} },
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersObject schema={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const parametersElement = container.querySelector("[data-testid='parameters']")
|
||||
expect(parametersElement).toBeInTheDocument()
|
||||
expect(parametersElement).toHaveTextContent(JSON.stringify({
|
||||
type: "string",
|
||||
properties: {},
|
||||
parameterName: "name",
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
test("sorts properties to show required fields first", async () => {
|
||||
mockCheckRequired.mockReturnValue(false)
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {}, isRequired: false },
|
||||
age: { type: "number", properties: {}, isRequired: true },
|
||||
},
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersObject schema={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const parametersElement = container.querySelectorAll("[data-testid='parameters']")
|
||||
expect(parametersElement).toHaveLength(2)
|
||||
expect(parametersElement[0]).toHaveTextContent(JSON.stringify({
|
||||
type: "number",
|
||||
properties: {},
|
||||
isRequired: true,
|
||||
parameterName: "age",
|
||||
}))
|
||||
expect(parametersElement[1]).toHaveTextContent(JSON.stringify({
|
||||
type: "string",
|
||||
properties: {},
|
||||
isRequired: false,
|
||||
parameterName: "name",
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
test("adds hr between properties", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
age: { type: "number", properties: {} },
|
||||
},
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersObject schema={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const hrElements = container.querySelectorAll("hr")
|
||||
expect(hrElements).toHaveLength(1)
|
||||
expect(hrElements[0]).toBeInTheDocument()
|
||||
expect(hrElements[0]).toHaveClass("bg-medusa-border-base my-0")
|
||||
})
|
||||
})
|
||||
|
||||
test("renders property as required when the property's isRequired is true", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {}, isRequired: true },
|
||||
},
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersObject schema={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const parametersElement = container.querySelector("[data-testid='parameters']")
|
||||
expect(parametersElement).toBeInTheDocument()
|
||||
expect(parametersElement).toHaveTextContent("required")
|
||||
})
|
||||
})
|
||||
|
||||
test("renders property as required when property's isRequired is true and checkRequired returns true", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {}, isRequired: true },
|
||||
},
|
||||
}
|
||||
mockCheckRequired.mockReturnValue(true)
|
||||
const { container } = render(<TagsOperationParametersObject schema={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const parametersElement = container.querySelector("[data-testid='parameters']")
|
||||
expect(parametersElement).toBeInTheDocument()
|
||||
expect(parametersElement).toHaveTextContent("required")
|
||||
})
|
||||
})
|
||||
|
||||
test("renders property as optional when the property's isRequired is false and checkRequired returns false", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {}, isRequired: false },
|
||||
},
|
||||
}
|
||||
mockCheckRequired.mockReturnValue(false)
|
||||
const { container } = render(<TagsOperationParametersObject schema={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const parametersElement = container.querySelector("[data-testid='parameters']")
|
||||
expect(parametersElement).toBeInTheDocument()
|
||||
expect(parametersElement).toHaveTextContent("optional")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import type { OpenAPI } from "types"
|
||||
import TagOperationParametersDefault from "../Default"
|
||||
import dynamic from "next/dynamic"
|
||||
@@ -10,7 +11,7 @@ import { Loading, type DetailsProps } from "docs-ui"
|
||||
import { Fragment, useMemo } from "react"
|
||||
|
||||
const TagOperationParameters = dynamic<TagOperationParametersProps>(
|
||||
async () => import("../.."),
|
||||
async () => import("@/components/Tags/Operation/Parameters"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
@@ -18,7 +19,7 @@ const TagOperationParameters = dynamic<TagOperationParametersProps>(
|
||||
|
||||
const TagsOperationParametersNested =
|
||||
dynamic<TagsOperationParametersNestedProps>(
|
||||
async () => import("../../Nested"),
|
||||
async () => import("@/components/Tags/Operation/Parameters/Nested"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
@@ -89,8 +90,10 @@ const TagOperationParametersObject = ({
|
||||
// sort properties to show required fields first
|
||||
const sortedProperties = Object.keys(properties).sort(
|
||||
(property1, property2) => {
|
||||
properties[property1].isRequired = checkRequired(schema, property1)
|
||||
properties[property2].isRequired = checkRequired(schema, property2)
|
||||
properties[property1].isRequired =
|
||||
properties[property1].isRequired || checkRequired(schema, property1)
|
||||
properties[property2].isRequired =
|
||||
properties[property2].isRequired || checkRequired(schema, property2)
|
||||
|
||||
return properties[property1].isRequired &&
|
||||
properties[property2].isRequired
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import React from "react"
|
||||
import { cleanup, fireEvent, render, waitFor } from "@testing-library/react"
|
||||
import { describe, test, expect, vi, beforeEach } from "vitest"
|
||||
import type { OpenAPI } from "types"
|
||||
import { TagOperationParametersDefaultProps } from "../../Default"
|
||||
import { TagOperationParametersProps } from "../../.."
|
||||
|
||||
// mock data
|
||||
const mockSchema: OpenAPI.SchemaObject = {
|
||||
title: "test-title",
|
||||
oneOf: [
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
properties: {}
|
||||
}
|
||||
],
|
||||
properties: {}
|
||||
}
|
||||
|
||||
// mock components
|
||||
vi.mock("@/components/Tags/Operation/Parameters/Types/Default", () => ({
|
||||
default: ({ schema }: TagOperationParametersDefaultProps) => (
|
||||
<div data-testid="default">{JSON.stringify(schema)}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/Parameters/Nested", () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-testid="nested">{children}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/Parameters", () => ({
|
||||
default: ({ schemaObject }: TagOperationParametersProps) => (
|
||||
<div data-testid="parameters">{JSON.stringify(schemaObject)}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("docs-ui", () => ({
|
||||
Details: ({ children, summaryElm }: { children: React.ReactNode, summaryElm: React.ReactNode }) => (
|
||||
<div data-testid="details">
|
||||
{summaryElm}
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
Loading: () => <div data-testid="loading">Loading...</div>,
|
||||
}))
|
||||
|
||||
import TagsOperationParametersTypesOneOf from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("does not render when schema is not an oneOf type", () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
},
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersTypesOneOf schema={modifiedSchema} />)
|
||||
expect(container).toBeEmptyDOMElement()
|
||||
})
|
||||
|
||||
test("renders one of type schema not nested by default", async () => {
|
||||
const { container } = render(<TagsOperationParametersTypesOneOf schema={mockSchema} />)
|
||||
await waitFor(() => {
|
||||
const defaultElement = container.querySelector("[data-testid='default']")
|
||||
expect(defaultElement).not.toBeInTheDocument()
|
||||
const nestedElement = container.querySelector("[data-testid='nested']")
|
||||
expect(nestedElement).not.toBeInTheDocument()
|
||||
const parametersElement = container.querySelector("[data-testid='parameters']")
|
||||
expect(parametersElement).toBeInTheDocument()
|
||||
expect(parametersElement).toHaveTextContent(JSON.stringify(mockSchema.oneOf![0]))
|
||||
})
|
||||
})
|
||||
|
||||
test("renders nested one of type schema", async () => {
|
||||
const { container } = render(<TagsOperationParametersTypesOneOf schema={mockSchema} isNested={true} />)
|
||||
await waitFor(() => {
|
||||
const defaultElement = container.querySelector("[data-testid='default']")
|
||||
expect(defaultElement).toBeInTheDocument()
|
||||
const nestedElement = container.querySelector("[data-testid='nested']")
|
||||
expect(nestedElement).toBeInTheDocument()
|
||||
const parametersElement = nestedElement!.querySelector("[data-testid='parameters']")
|
||||
expect(parametersElement).toBeInTheDocument()
|
||||
expect(parametersElement).toHaveTextContent(JSON.stringify(mockSchema.oneOf![0]))
|
||||
})
|
||||
})
|
||||
|
||||
test("renders parameter name when provided and nested is enabled", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
...mockSchema,
|
||||
parameterName: "test-parameter-name",
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersTypesOneOf schema={modifiedSchema} isNested={true} />)
|
||||
await waitFor(() => {
|
||||
const detailsElement = container.querySelector("[data-testid='details']")
|
||||
expect(detailsElement).toBeInTheDocument()
|
||||
const summaryElement = detailsElement!.querySelector("summary")
|
||||
expect(summaryElement).toBeInTheDocument()
|
||||
expect(summaryElement).toHaveTextContent(modifiedSchema.parameterName!)
|
||||
})
|
||||
})
|
||||
|
||||
test("renders schema title when parameter name is not provided and nested is enabled", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
...mockSchema,
|
||||
parameterName: undefined,
|
||||
}
|
||||
const { container } = render(<TagsOperationParametersTypesOneOf schema={modifiedSchema} isNested={true} />)
|
||||
await waitFor(() => {
|
||||
const detailsElement = container.querySelector("[data-testid='details']")
|
||||
expect(detailsElement).toBeInTheDocument()
|
||||
const summaryElement = detailsElement!.querySelector("summary")
|
||||
expect(summaryElement).toBeInTheDocument()
|
||||
expect(summaryElement).toHaveTextContent(modifiedSchema.title!)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("interaction", () => {
|
||||
test("toggles between one of options when clicking on the tab", async () => {
|
||||
const { container } = render(<TagsOperationParametersTypesOneOf schema={mockSchema} />)
|
||||
const tabElements = container.querySelectorAll("[data-testid='tab']")
|
||||
expect(tabElements).toHaveLength(mockSchema.oneOf!.length)
|
||||
fireEvent.click(tabElements[1]!)
|
||||
await waitFor(() => {
|
||||
const parametersElement = container.querySelector("[data-testid='parameters']")
|
||||
expect(parametersElement).toBeInTheDocument()
|
||||
expect(parametersElement).toHaveTextContent(JSON.stringify(mockSchema.oneOf![1]))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from "react"
|
||||
import type { OpenAPI } from "types"
|
||||
import clsx from "clsx"
|
||||
import dynamic from "next/dynamic"
|
||||
import { useState } from "react"
|
||||
import type { TagOperationParametersDefaultProps } from "../Default"
|
||||
import type { TagsOperationParametersNestedProps } from "../../Nested"
|
||||
import type { TagOperationParametersProps } from "../.."
|
||||
@@ -43,22 +43,15 @@ const TagOperationParamatersOneOf = ({
|
||||
}: TagOperationParamatersOneOfProps) => {
|
||||
const [activeTab, setActiveTab] = useState<number>(0)
|
||||
|
||||
if (!schema.oneOf) {
|
||||
return null
|
||||
}
|
||||
|
||||
const getName = (item: OpenAPI.SchemaObject): string => {
|
||||
if (item.title) {
|
||||
return item.title
|
||||
}
|
||||
|
||||
if (item.anyOf || item.allOf) {
|
||||
// return the name of any of the items
|
||||
const name = item.anyOf
|
||||
? item.anyOf.find((i) => i.title !== undefined)?.title
|
||||
: item.allOf?.find((i) => i.title !== undefined)?.title
|
||||
|
||||
if (name) {
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
return item.type || ""
|
||||
}
|
||||
|
||||
@@ -84,6 +77,7 @@ const TagOperationParamatersOneOf = ({
|
||||
]
|
||||
)}
|
||||
onClick={() => setActiveTab(index)}
|
||||
data-testid={"tab"}
|
||||
>
|
||||
{getName(item)}
|
||||
</li>
|
||||
@@ -91,14 +85,10 @@ const TagOperationParamatersOneOf = ({
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{schema.oneOf && (
|
||||
<>
|
||||
<TagOperationParameters
|
||||
schemaObject={schema.oneOf[activeTab]}
|
||||
topLevel={true}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<TagOperationParameters
|
||||
schemaObject={schema.oneOf![activeTab]}
|
||||
topLevel={true}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
import React from "react"
|
||||
import { cleanup, render, waitFor } from "@testing-library/react"
|
||||
import { describe, test, expect, vi, beforeEach } from "vitest"
|
||||
import type { OpenAPI } from "types"
|
||||
import { TagOperationParametersObjectProps } from "../../Object"
|
||||
import { TagOperationParametersDefaultProps } from "../../Default"
|
||||
|
||||
// mock data
|
||||
const mockAnyOfSchema: OpenAPI.SchemaObject = {
|
||||
anyOf: [
|
||||
{ type: "object", properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
} },
|
||||
{ type: "string", properties: {} },
|
||||
],
|
||||
properties: {}
|
||||
}
|
||||
|
||||
const mockAllOfSchema: OpenAPI.SchemaObject = {
|
||||
allOf: [
|
||||
{ type: "object", properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
} },
|
||||
{ type: "string", properties: {} },
|
||||
],
|
||||
properties: {}
|
||||
}
|
||||
|
||||
// mock functions
|
||||
const mockMergeAllOfTypes = vi.fn((schema: OpenAPI.SchemaObject) => schema.allOf?.[0])
|
||||
|
||||
// mock components
|
||||
vi.mock("@/components/Tags/Operation/Parameters/Types/Object", () => ({
|
||||
default: ({ schema }: TagOperationParametersObjectProps) => (
|
||||
<div data-testid="object" data-description={schema.description}>{JSON.stringify(schema)}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/Parameters/Types/Default", () => ({
|
||||
default: ({ schema }: TagOperationParametersDefaultProps) => (
|
||||
<div data-testid="default">{JSON.stringify(schema)}</div>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock("docs-ui", () => ({
|
||||
Loading: () => <div data-testid="loading">Loading...</div>,
|
||||
}))
|
||||
|
||||
vi.mock("@/utils/merge-all-of-types", () => ({
|
||||
default: (schema: OpenAPI.SchemaObject) => mockMergeAllOfTypes(schema),
|
||||
}))
|
||||
|
||||
import TagOperationParametersUnion from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders default when schema is not an anyOf or allOf type", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
},
|
||||
}
|
||||
const { container } = render(<TagOperationParametersUnion name="test-name" schema={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const defaultElement = container.querySelector("[data-testid='default']")
|
||||
expect(defaultElement).toBeInTheDocument()
|
||||
expect(defaultElement).toHaveTextContent(JSON.stringify(modifiedSchema))
|
||||
})
|
||||
})
|
||||
|
||||
test("renders default when schema is any of and type is not object", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
anyOf: [
|
||||
{ type: "string", properties: {} },
|
||||
],
|
||||
properties: {}
|
||||
}
|
||||
const { container } = render(<TagOperationParametersUnion name="test-name" schema={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const defaultElement = container.querySelector("[data-testid='default']")
|
||||
expect(defaultElement).toBeInTheDocument()
|
||||
expect(defaultElement).toHaveTextContent(JSON.stringify(modifiedSchema))
|
||||
})
|
||||
})
|
||||
|
||||
test("renders default when schema is any of and type is object and properties is not defined", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
anyOf: [
|
||||
{ type: "object", properties: {} },
|
||||
],
|
||||
properties: {}
|
||||
}
|
||||
const { container } = render(<TagOperationParametersUnion name="test-name" schema={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const defaultElement = container.querySelector("[data-testid='default']")
|
||||
expect(defaultElement).toBeInTheDocument()
|
||||
expect(defaultElement).toHaveTextContent(JSON.stringify(modifiedSchema))
|
||||
})
|
||||
})
|
||||
|
||||
test("renders any of type schema", async () => {
|
||||
const { container } = render(<TagOperationParametersUnion name="test-name" schema={mockAnyOfSchema} />)
|
||||
await waitFor(() => {
|
||||
const objectElement = container.querySelector("[data-testid='object']")
|
||||
expect(objectElement).toBeInTheDocument()
|
||||
expect(objectElement).toHaveTextContent(JSON.stringify(mockAnyOfSchema.anyOf![0]))
|
||||
})
|
||||
})
|
||||
|
||||
test("renders all of type schema", async () => {
|
||||
const { container } = render(<TagOperationParametersUnion name="test-name" schema={mockAllOfSchema} />)
|
||||
await waitFor(() => {
|
||||
const objectElement = container.querySelector("[data-testid='object']")
|
||||
expect(objectElement).toBeInTheDocument()
|
||||
expect(objectElement).toHaveTextContent(JSON.stringify(mockAllOfSchema.allOf![0]))
|
||||
})
|
||||
expect(mockMergeAllOfTypes).toHaveBeenCalledWith(mockAllOfSchema)
|
||||
})
|
||||
|
||||
test("renders schema description from selected object schema when provided", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
anyOf: [
|
||||
{ ...mockAllOfSchema.allOf![0], description: "test-description 1" },
|
||||
{ ...mockAllOfSchema.allOf![1], description: "test-description 2" },
|
||||
],
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(<TagOperationParametersUnion name="test-name" schema={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const objectElement = container.querySelector("[data-testid='object']")
|
||||
expect(objectElement).toBeInTheDocument()
|
||||
expect(objectElement).toHaveAttribute("data-description", "test-description 1")
|
||||
})
|
||||
})
|
||||
|
||||
test("renders schema description from any item having description when selected object doesn't have description", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
anyOf: [
|
||||
{ ...mockAllOfSchema.allOf![0] },
|
||||
{ ...mockAllOfSchema.allOf![1], description: "test-description" },
|
||||
],
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(<TagOperationParametersUnion name="test-name" schema={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const objectElement = container.querySelector("[data-testid='object']")
|
||||
expect(objectElement).toBeInTheDocument()
|
||||
expect(objectElement).toHaveAttribute("data-description", "test-description")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from "react"
|
||||
import type { OpenAPI } from "types"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { TagOperationParametersDefaultProps } from "../Default"
|
||||
import { TagOperationParametersObjectProps } from "../Object"
|
||||
import { Loading } from "docs-ui"
|
||||
import mergeAllOfTypes from "../../../../../../utils/merge-all-of-types"
|
||||
import mergeAllOfTypes from "@/utils/merge-all-of-types"
|
||||
|
||||
const TagOperationParametersObject = dynamic<TagOperationParametersObjectProps>(
|
||||
async () => import("../Object"),
|
||||
@@ -34,7 +35,12 @@ const TagOperationParametersUnion = ({
|
||||
topLevel,
|
||||
}: TagOperationParametersUnionProps) => {
|
||||
const objectSchema = schema.anyOf
|
||||
? schema.anyOf.find((item) => item.type === "object" && item.properties)
|
||||
? schema.anyOf.find(
|
||||
(item) =>
|
||||
item.type === "object" &&
|
||||
item.properties &&
|
||||
Object.keys(item.properties).length > 0
|
||||
)
|
||||
: schema.allOf
|
||||
? mergeAllOfTypes(schema)
|
||||
: undefined
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
import React from "react"
|
||||
import { cleanup, render, waitFor } from "@testing-library/react"
|
||||
import { describe, test, expect, vi, beforeEach } from "vitest"
|
||||
import type { OpenAPI } from "types"
|
||||
import { TagOperationParametersUnionProps } from "../Types/Union"
|
||||
import { TagOperationParametersArrayProps } from "../Types/Array"
|
||||
import { TagOperationParamatersOneOfProps } from "../Types/OneOf"
|
||||
import { TagOperationParametersObjectProps } from "../Types/Object"
|
||||
import { TagOperationParametersDefaultProps } from "../Types/Default"
|
||||
|
||||
// mock functions
|
||||
const mockCheckRequired = vi.fn()
|
||||
|
||||
// mock components
|
||||
vi.mock("@/components/Tags/Operation/Parameters/Types/Union", () => ({
|
||||
default: ({ schema, isRequired, name }: TagOperationParametersUnionProps) => (
|
||||
<div data-testid="union" data-required={isRequired} data-name={name}>{JSON.stringify(schema)}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/Parameters/Types/OneOf", () => ({
|
||||
default: ({ schema, isRequired }: TagOperationParamatersOneOfProps) => (
|
||||
<div data-testid="one-of" data-required={isRequired}>{JSON.stringify(schema)}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/Parameters/Types/Array", () => ({
|
||||
default: ({ schema, isRequired, name }: TagOperationParametersArrayProps) => (
|
||||
<div data-testid="array" data-required={isRequired} data-name={name}>{JSON.stringify(schema)}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/Parameters/Types/Object", () => ({
|
||||
default: ({ schema, isRequired, name }: TagOperationParametersObjectProps) => (
|
||||
<div data-testid="object" data-required={isRequired} data-name={name}>{JSON.stringify(schema)}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/Parameters/Types/Default", () => ({
|
||||
default: ({ schema, isRequired, name }: TagOperationParametersDefaultProps) => (
|
||||
<div data-testid="default" data-required={isRequired} data-name={name}>{JSON.stringify(schema)}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/utils/check-required", () => ({
|
||||
default: (schema: OpenAPI.SchemaObject) => mockCheckRequired(schema),
|
||||
}))
|
||||
vi.mock("docs-utils", () => ({
|
||||
Loading: () => <div data-testid="loading">Loading...</div>,
|
||||
}))
|
||||
|
||||
import TagOperationParameters from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders union when schema is anyOf", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
anyOf: [
|
||||
{ type: "object", properties: {} },
|
||||
{ type: "string", properties: {} },
|
||||
],
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(<TagOperationParameters schemaObject={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const unionElement = container.querySelector("[data-testid='union']")
|
||||
expect(unionElement).toBeInTheDocument()
|
||||
expect(unionElement).toHaveTextContent(JSON.stringify(modifiedSchema))
|
||||
})
|
||||
})
|
||||
|
||||
test("renders union when schema is allOf", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
allOf: [
|
||||
{ type: "object", properties: {} },
|
||||
{ type: "string", properties: {} },
|
||||
],
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(<TagOperationParameters schemaObject={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const unionElement = container.querySelector("[data-testid='union']")
|
||||
expect(unionElement).toBeInTheDocument()
|
||||
expect(unionElement).toHaveTextContent(JSON.stringify(modifiedSchema))
|
||||
})
|
||||
expect(container.querySelector("[data-testid='object']")).not.toBeInTheDocument()
|
||||
expect(container.querySelector("[data-testid='array']")).not.toBeInTheDocument()
|
||||
expect(container.querySelector("[data-testid='default']")).not.toBeInTheDocument()
|
||||
expect(container.querySelector("[data-testid='one-of']")).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders oneOf when schema is oneOf", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
oneOf: [
|
||||
{ type: "object", properties: {} },
|
||||
{ type: "string", properties: {} },
|
||||
],
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(<TagOperationParameters schemaObject={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const oneOfElement = container.querySelector("[data-testid='one-of']")
|
||||
expect(oneOfElement).toBeInTheDocument()
|
||||
expect(oneOfElement).toHaveTextContent(JSON.stringify(modifiedSchema))
|
||||
})
|
||||
expect(container.querySelector("[data-testid='union']")).not.toBeInTheDocument()
|
||||
expect(container.querySelector("[data-testid='object']")).not.toBeInTheDocument()
|
||||
expect(container.querySelector("[data-testid='array']")).not.toBeInTheDocument()
|
||||
expect(container.querySelector("[data-testid='default']")).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders array when schema is array", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "array",
|
||||
items: { type: "string", properties: {} },
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(<TagOperationParameters schemaObject={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const arrayElement = container.querySelector("[data-testid='array']")
|
||||
expect(arrayElement).toBeInTheDocument()
|
||||
expect(arrayElement).toHaveTextContent(JSON.stringify(modifiedSchema))
|
||||
})
|
||||
expect(container.querySelector("[data-testid='union']")).not.toBeInTheDocument()
|
||||
expect(container.querySelector("[data-testid='object']")).not.toBeInTheDocument()
|
||||
expect(container.querySelector("[data-testid='one-of']")).not.toBeInTheDocument()
|
||||
expect(container.querySelector("[data-testid='default']")).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders object when schema is object", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
},
|
||||
}
|
||||
const { container } = render(<TagOperationParameters schemaObject={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const objectElement = container.querySelector("[data-testid='object']")
|
||||
expect(objectElement).toBeInTheDocument()
|
||||
expect(objectElement).toHaveTextContent(JSON.stringify(modifiedSchema))
|
||||
})
|
||||
expect(container.querySelector("[data-testid='union']")).not.toBeInTheDocument()
|
||||
expect(container.querySelector("[data-testid='one-of']")).not.toBeInTheDocument()
|
||||
expect(container.querySelector("[data-testid='array']")).not.toBeInTheDocument()
|
||||
expect(container.querySelector("[data-testid='default']")).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders object when schema's type is undefined", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: undefined,
|
||||
properties: {}
|
||||
}
|
||||
const { container } = render(<TagOperationParameters schemaObject={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const objectElement = container.querySelector("[data-testid='object']")
|
||||
expect(objectElement).toBeInTheDocument()
|
||||
expect(objectElement).toHaveTextContent(JSON.stringify(modifiedSchema))
|
||||
})
|
||||
expect(container.querySelector("[data-testid='union']")).not.toBeInTheDocument()
|
||||
expect(container.querySelector("[data-testid='one-of']")).not.toBeInTheDocument()
|
||||
expect(container.querySelector("[data-testid='array']")).not.toBeInTheDocument()
|
||||
expect(container.querySelector("[data-testid='default']")).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders default when schema is not an anyOf, allOf, oneOf, array, or object", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "string",
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(<TagOperationParameters schemaObject={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const defaultElement = container.querySelector("[data-testid='default']")
|
||||
expect(defaultElement).toBeInTheDocument()
|
||||
expect(defaultElement).toHaveTextContent(JSON.stringify(modifiedSchema))
|
||||
})
|
||||
expect(container.querySelector("[data-testid='union']")).not.toBeInTheDocument()
|
||||
expect(container.querySelector("[data-testid='one-of']")).not.toBeInTheDocument()
|
||||
expect(container.querySelector("[data-testid='array']")).not.toBeInTheDocument()
|
||||
expect(container.querySelector("[data-testid='object']")).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("adds className when provided", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "string",
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(<TagOperationParameters schemaObject={modifiedSchema} className="test-class" />)
|
||||
const parametersElement = container.querySelector("[data-testid='parameters']")
|
||||
expect(parametersElement).toBeInTheDocument()
|
||||
expect(parametersElement).toHaveClass("test-class")
|
||||
})
|
||||
})
|
||||
|
||||
describe("isRequired", () => {
|
||||
test("sets parameter as required when isRequired is true", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "string",
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(<TagOperationParameters schemaObject={modifiedSchema} isRequired={true} />)
|
||||
await waitFor(() => {
|
||||
const defaultElement = container.querySelector("[data-testid='default']")
|
||||
expect(defaultElement).toBeInTheDocument()
|
||||
expect(defaultElement).toHaveAttribute("data-required", "true")
|
||||
})
|
||||
})
|
||||
|
||||
test("sets parameter as required when isRequired is false and checkRequired returns true", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "string",
|
||||
properties: {},
|
||||
}
|
||||
mockCheckRequired.mockReturnValue(true)
|
||||
const { container } = render(<TagOperationParameters schemaObject={modifiedSchema} isRequired={false} />)
|
||||
await waitFor(() => {
|
||||
const defaultElement = container.querySelector("[data-testid='default']")
|
||||
expect(defaultElement).toBeInTheDocument()
|
||||
expect(defaultElement).toHaveAttribute("data-required", "true")
|
||||
})
|
||||
})
|
||||
|
||||
test("sets parameter as optional when isRequired is false and checkRequired returns false", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "string",
|
||||
properties: {},
|
||||
}
|
||||
mockCheckRequired.mockReturnValue(false)
|
||||
const { container } = render(<TagOperationParameters schemaObject={modifiedSchema} isRequired={false} />)
|
||||
await waitFor(() => {
|
||||
const defaultElement = container.querySelector("[data-testid='default']")
|
||||
expect(defaultElement).toBeInTheDocument()
|
||||
expect(defaultElement).toHaveAttribute("data-required", "false")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("parameterName", () => {
|
||||
test("sets parameter name to parameterName when provided", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "string",
|
||||
properties: {},
|
||||
parameterName: "test-name",
|
||||
}
|
||||
const { container } = render(<TagOperationParameters schemaObject={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const defaultElement = container.querySelector("[data-testid='default']")
|
||||
expect(defaultElement).toBeInTheDocument()
|
||||
expect(defaultElement).toHaveAttribute("data-name", "test-name")
|
||||
})
|
||||
})
|
||||
|
||||
test("sets parameter name to title when parameterName is not provided", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "string",
|
||||
properties: {},
|
||||
title: "test-title",
|
||||
}
|
||||
const { container } = render(<TagOperationParameters schemaObject={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const defaultElement = container.querySelector("[data-testid='default']")
|
||||
expect(defaultElement).toBeInTheDocument()
|
||||
expect(defaultElement).toHaveAttribute("data-name", "test-title")
|
||||
})
|
||||
})
|
||||
|
||||
test("sets parameter name to empty string when parameterName is not provided and title is not provided", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "string",
|
||||
properties: {},
|
||||
}
|
||||
const { container } = render(<TagOperationParameters schemaObject={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const defaultElement = container.querySelector("[data-testid='default']")
|
||||
expect(defaultElement).toBeInTheDocument()
|
||||
expect(defaultElement).toHaveAttribute("data-name", "")
|
||||
})
|
||||
})
|
||||
|
||||
test("gives precedence to parameterName over title", async () => {
|
||||
const modifiedSchema: OpenAPI.SchemaObject = {
|
||||
type: "string",
|
||||
properties: {},
|
||||
parameterName: "test-name",
|
||||
title: "test-title",
|
||||
}
|
||||
const { container } = render(<TagOperationParameters schemaObject={modifiedSchema} />)
|
||||
await waitFor(() => {
|
||||
const defaultElement = container.querySelector("[data-testid='default']")
|
||||
expect(defaultElement).toBeInTheDocument()
|
||||
expect(defaultElement).toHaveAttribute("data-name", "test-name")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import type { OpenAPI } from "types"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { TagOperationParametersObjectProps } from "./Types/Object"
|
||||
@@ -109,11 +110,13 @@ const TagOperationParameters = ({
|
||||
isRequired={isRequired}
|
||||
/>
|
||||
)
|
||||
|
||||
return <></>
|
||||
}
|
||||
|
||||
return <div className={className}>{getElement()}</div>
|
||||
return (
|
||||
<div className={className} data-testid="parameters">
|
||||
{getElement()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagOperationParameters
|
||||
|
||||
@@ -0,0 +1,489 @@
|
||||
import React, { useState } from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render, waitFor, act, fireEvent } from "@testing-library/react"
|
||||
import { OpenAPI } from "types"
|
||||
|
||||
// mock data
|
||||
const mockOperation: OpenAPI.Operation = {
|
||||
operationId: "mockOperation",
|
||||
summary: "Mock Operation",
|
||||
description: "Mock Operation",
|
||||
"x-authenticated": false,
|
||||
"x-codeSamples": [
|
||||
{
|
||||
label: "Request Sample 1",
|
||||
lang: "javascript",
|
||||
source: "console.log('Request Sample 1')",
|
||||
}
|
||||
],
|
||||
requestBody: {
|
||||
content: {},
|
||||
},
|
||||
parameters: [],
|
||||
responses: {
|
||||
"200": {
|
||||
description: "OK",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const mockTag: OpenAPI.OpenAPIV3.TagObject = {
|
||||
name: "mock-tag",
|
||||
description: "Mock Tag",
|
||||
}
|
||||
const mockEndpointPath = "/mock-endpoint-path"
|
||||
|
||||
// mock functions
|
||||
const mockIsElmWindow = vi.fn(() => false)
|
||||
const mockUseIsBrowser = vi.fn(() => ({
|
||||
isBrowser: true,
|
||||
}))
|
||||
const mockScrollToTop = vi.fn()
|
||||
const mockUseScrollController = vi.fn(() => ({
|
||||
scrollableElement: null as HTMLElement | null,
|
||||
scrollToTop: mockScrollToTop,
|
||||
}))
|
||||
const mockSetActivePath = vi.fn()
|
||||
const mockUseSidebar = vi.fn(() => ({
|
||||
activePath: null as string | null,
|
||||
setActivePath: mockSetActivePath,
|
||||
}))
|
||||
const mockRemoveLoading = vi.fn()
|
||||
const mockUseLoading = vi.fn(() => ({
|
||||
loading: false,
|
||||
removeLoading: mockRemoveLoading,
|
||||
}))
|
||||
const mockPush = vi.fn()
|
||||
const mockReplace = vi.fn()
|
||||
const mockUseRouter = vi.fn(() => ({
|
||||
push: mockPush,
|
||||
replace: mockReplace,
|
||||
}))
|
||||
const mockGetSectionId = vi.fn((options: unknown) => "mock-section-id")
|
||||
const mockCheckElementInViewport = vi.fn(() => true)
|
||||
|
||||
|
||||
// mock components
|
||||
vi.mock("react-intersection-observer", () => ({
|
||||
InView: ({ children, onChange, root }: { children: React.ReactNode, onChange: (inView: boolean) => void, root: HTMLElement | null }) => {
|
||||
const [inView, setInView] = useState(false)
|
||||
return (
|
||||
<div data-testid="in-view" data-root={root?.tagName}>
|
||||
{children}
|
||||
<button type="button" data-testid="in-view-toggle-button" onClick={(e) => {
|
||||
setInView(!inView)
|
||||
onChange(!inView)
|
||||
}}>
|
||||
{inView.toString()}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
vi.mock("docs-ui", () => ({
|
||||
isElmWindow: () => mockIsElmWindow(),
|
||||
useIsBrowser: () => mockUseIsBrowser(),
|
||||
useScrollController: () => mockUseScrollController(),
|
||||
useSidebar: () => mockUseSidebar(),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/CodeSection", () => ({
|
||||
default: ({ operation, method }: { operation: OpenAPI.Operation, method?: string }) => (
|
||||
<div data-testid="code-section" data-method={method}>{JSON.stringify(operation)}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/DescriptionSection", () => ({
|
||||
default: ({ operation}: { operation: OpenAPI.Operation }) => (
|
||||
<div data-testid="description-section">{JSON.stringify(operation)}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/layouts/Divided", () => ({
|
||||
default: ({ mainContent, codeContent }: { mainContent: React.ReactNode, codeContent: React.ReactNode }) => (
|
||||
<div data-testid="divided">
|
||||
<div data-testid="divided-main-content">{mainContent}</div>
|
||||
<div data-testid="divided-code-content">{codeContent}</div>
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/providers/loading", () => ({
|
||||
useLoading: () => mockUseLoading(),
|
||||
}))
|
||||
vi.mock("@/utils/check-element-in-viewport", () => ({
|
||||
default: () => mockCheckElementInViewport(),
|
||||
}))
|
||||
vi.mock("@/components/DividedLoading", () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-testid="divided-loading">{children}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Section/Container", () => ({
|
||||
default: React.forwardRef<HTMLDivElement, { children: React.ReactNode; className?: string }>(
|
||||
({ children, className }, ref) => (
|
||||
<div data-testid="section-container" className={className} ref={ref}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
),
|
||||
}))
|
||||
vi.mock("docs-utils", () => ({
|
||||
getSectionId: (options: unknown) => mockGetSectionId(options),
|
||||
}))
|
||||
vi.mock("next/navigation", () => ({
|
||||
useRouter: () => mockUseRouter(),
|
||||
}))
|
||||
|
||||
import TagOperation from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
// Reset location hash
|
||||
window.location.hash = ""
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders InView wrapper with correct props", () => {
|
||||
const { getByTestId } = render(
|
||||
<TagOperation
|
||||
operation={mockOperation}
|
||||
tag={mockTag}
|
||||
endpointPath={mockEndpointPath}
|
||||
/>
|
||||
)
|
||||
const inViewElement = getByTestId("in-view")
|
||||
expect(inViewElement).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders SectionContainer", () => {
|
||||
const { getByTestId } = render(
|
||||
<TagOperation
|
||||
operation={mockOperation}
|
||||
tag={mockTag}
|
||||
endpointPath={mockEndpointPath}
|
||||
/>
|
||||
)
|
||||
const sectionContainerElement = getByTestId("section-container")
|
||||
expect(sectionContainerElement).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders with className", () => {
|
||||
const { getByTestId } = render(
|
||||
<TagOperation
|
||||
operation={mockOperation}
|
||||
tag={mockTag}
|
||||
endpointPath={mockEndpointPath}
|
||||
className="test-class"
|
||||
/>
|
||||
)
|
||||
const sectionContainerElement = getByTestId("section-container")
|
||||
expect(sectionContainerElement).toHaveClass("test-class")
|
||||
})
|
||||
|
||||
test("renders loading component when show is false", () => {
|
||||
const { getByTestId } = render(
|
||||
<TagOperation
|
||||
operation={mockOperation}
|
||||
tag={mockTag}
|
||||
endpointPath={mockEndpointPath}
|
||||
/>
|
||||
)
|
||||
const loadingElement = getByTestId("divided-loading")
|
||||
expect(loadingElement).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders loading component initially", () => {
|
||||
const { getByTestId } = render(
|
||||
<TagOperation
|
||||
operation={mockOperation}
|
||||
tag={mockTag}
|
||||
endpointPath={mockEndpointPath}
|
||||
/>
|
||||
)
|
||||
const loadingElement = getByTestId("divided-loading")
|
||||
expect(loadingElement).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe("path generation", () => {
|
||||
test("generates path using getSectionId with tags and operationId", () => {
|
||||
const operationWithTags: OpenAPI.Operation = {
|
||||
...mockOperation,
|
||||
tags: ["tag1", "tag2"],
|
||||
operationId: "test-operation",
|
||||
}
|
||||
render(
|
||||
<TagOperation
|
||||
operation={operationWithTags}
|
||||
tag={mockTag}
|
||||
endpointPath={mockEndpointPath}
|
||||
/>
|
||||
)
|
||||
expect(mockGetSectionId).toHaveBeenCalledWith(["tag1", "tag2", "test-operation"])
|
||||
})
|
||||
|
||||
test("generates path using getSectionId with empty tags array when tags are not provided", () => {
|
||||
const operationWithoutTags: OpenAPI.Operation = {
|
||||
...mockOperation,
|
||||
tags: undefined,
|
||||
operationId: "test-operation",
|
||||
}
|
||||
render(
|
||||
<TagOperation
|
||||
operation={operationWithoutTags}
|
||||
tag={mockTag}
|
||||
endpointPath={mockEndpointPath}
|
||||
/>
|
||||
)
|
||||
expect(mockGetSectionId).toHaveBeenCalledWith(["test-operation"])
|
||||
})
|
||||
})
|
||||
|
||||
describe("hash matching and scrolling", () => {
|
||||
test("removes loading when nodeRef is set", async () => {
|
||||
render(
|
||||
<TagOperation
|
||||
operation={mockOperation}
|
||||
tag={mockTag}
|
||||
endpointPath={mockEndpointPath}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(mockRemoveLoading).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("scrolls into view when hash matches path", async () => {
|
||||
const mockPath = "mock-section-id"
|
||||
mockGetSectionId.mockReturnValue(mockPath)
|
||||
window.location.hash = `#${mockPath}`
|
||||
|
||||
const { container } = render(
|
||||
<TagOperation
|
||||
operation={mockOperation}
|
||||
tag={mockTag}
|
||||
endpointPath={mockEndpointPath}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(mockRemoveLoading).toHaveBeenCalled()
|
||||
|
||||
await waitFor(() => {
|
||||
const operationContainer = container.querySelector("[data-testid='operation-container']")
|
||||
expect(operationContainer).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
test("sets show to true when hash prefix matches path prefix", async () => {
|
||||
const mockPath = "mock-section-id_subsection"
|
||||
mockGetSectionId.mockReturnValue(mockPath)
|
||||
window.location.hash = "#mock-section-id"
|
||||
|
||||
const { container } = render(
|
||||
<TagOperation
|
||||
operation={mockOperation}
|
||||
tag={mockTag}
|
||||
endpointPath={mockEndpointPath}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(mockRemoveLoading).toHaveBeenCalled()
|
||||
|
||||
await waitFor(() => {
|
||||
const operationContainer = container.querySelector("[data-testid='operation-container']")
|
||||
expect(operationContainer).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("InView behavior", () => {
|
||||
test("sets active path when in view and activePath is different", () => {
|
||||
const mockPath = "mock-section-id"
|
||||
window.location.hash = "#different-hash"
|
||||
mockGetSectionId.mockReturnValue(mockPath)
|
||||
mockUseSidebar.mockReturnValue({
|
||||
activePath: "different-hash",
|
||||
setActivePath: mockSetActivePath,
|
||||
})
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TagOperation
|
||||
operation={mockOperation}
|
||||
tag={mockTag}
|
||||
endpointPath={mockEndpointPath}
|
||||
/>
|
||||
)
|
||||
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
fireEvent.click(inViewToggleButton)
|
||||
expect(mockSetActivePath).toHaveBeenCalledWith(mockPath)
|
||||
})
|
||||
|
||||
test("updates router hash when in view and hash is different", () => {
|
||||
const mockPath = "mock-section-id"
|
||||
mockGetSectionId.mockReturnValue(mockPath)
|
||||
window.location.hash = "#different-hash"
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TagOperation
|
||||
operation={mockOperation}
|
||||
tag={mockTag}
|
||||
endpointPath={mockEndpointPath}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(getByTestId("in-view")).toBeInTheDocument()
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
fireEvent.click(inViewToggleButton)
|
||||
expect(mockPush).toHaveBeenCalledWith(`#${mockPath}`, { scroll: false })
|
||||
})
|
||||
|
||||
test("removes loading when in view and loading is true", () => {
|
||||
mockUseLoading.mockReturnValue({
|
||||
loading: true,
|
||||
removeLoading: mockRemoveLoading,
|
||||
})
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TagOperation
|
||||
operation={mockOperation}
|
||||
tag={mockTag}
|
||||
endpointPath={mockEndpointPath}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(getByTestId("in-view")).toBeInTheDocument()
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
fireEvent.click(inViewToggleButton)
|
||||
expect(mockRemoveLoading).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("sets show to false when out of view and element is not in viewport", () => {
|
||||
mockCheckElementInViewport.mockReturnValue(false)
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TagOperation
|
||||
operation={mockOperation}
|
||||
tag={mockTag}
|
||||
endpointPath={mockEndpointPath}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(getByTestId("in-view")).toBeInTheDocument()
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
// toggle it to true
|
||||
fireEvent.click(inViewToggleButton)
|
||||
// toggle it to false
|
||||
fireEvent.click(inViewToggleButton)
|
||||
// should show the divided loading now
|
||||
expect(getByTestId("divided-loading")).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe("browser environment", () => {
|
||||
test("handles non-browser environment", () => {
|
||||
mockUseIsBrowser.mockReturnValue({ isBrowser: false })
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TagOperation
|
||||
operation={mockOperation}
|
||||
tag={mockTag}
|
||||
endpointPath={mockEndpointPath}
|
||||
/>
|
||||
)
|
||||
|
||||
const inViewElement = getByTestId("in-view")
|
||||
expect(inViewElement).toBeInTheDocument()
|
||||
expect(inViewElement).not.toHaveAttribute("data-root")
|
||||
})
|
||||
|
||||
test("uses document.body as root when scrollableElement is window and isBrowser is true", () => {
|
||||
mockIsElmWindow.mockReturnValue(true)
|
||||
mockUseScrollController.mockReturnValue({
|
||||
scrollableElement: window as unknown as HTMLElement,
|
||||
scrollToTop: mockScrollToTop,
|
||||
})
|
||||
mockUseIsBrowser.mockReturnValue({ isBrowser: true })
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TagOperation
|
||||
operation={mockOperation}
|
||||
tag={mockTag}
|
||||
endpointPath={mockEndpointPath}
|
||||
/>
|
||||
)
|
||||
|
||||
const inViewElement = getByTestId("in-view")
|
||||
expect(inViewElement).toBeInTheDocument()
|
||||
expect(inViewElement).toHaveAttribute("data-root", "BODY")
|
||||
})
|
||||
|
||||
test("uses scrollableElement as root when it is not window and isBrowser is true", () => {
|
||||
const mockScrollableElement = document.createElement("div")
|
||||
mockIsElmWindow.mockReturnValue(false)
|
||||
mockUseIsBrowser.mockReturnValue({ isBrowser: true })
|
||||
mockUseScrollController.mockReturnValue({
|
||||
scrollableElement: mockScrollableElement,
|
||||
scrollToTop: mockScrollToTop,
|
||||
})
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TagOperation
|
||||
operation={mockOperation}
|
||||
tag={mockTag}
|
||||
endpointPath={mockEndpointPath}
|
||||
/>
|
||||
)
|
||||
|
||||
const inViewElement = getByTestId("in-view")
|
||||
expect(inViewElement).toBeInTheDocument()
|
||||
expect(inViewElement).toHaveAttribute("data-root", "DIV")
|
||||
})
|
||||
})
|
||||
|
||||
describe("method prop", () => {
|
||||
test("renders component with method prop", async () => {
|
||||
const { getByTestId } = render(
|
||||
<TagOperation
|
||||
operation={mockOperation}
|
||||
tag={mockTag}
|
||||
endpointPath={mockEndpointPath}
|
||||
method="POST"
|
||||
/>
|
||||
)
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
// set show to true
|
||||
fireEvent.click(inViewToggleButton)
|
||||
await waitFor(() => {
|
||||
const codeSectionElement = getByTestId("code-section")
|
||||
expect(codeSectionElement).toBeInTheDocument()
|
||||
expect(codeSectionElement).toHaveAttribute("data-method", "POST")
|
||||
})
|
||||
})
|
||||
|
||||
test("renders component without method prop", async () => {
|
||||
const { getByTestId } = render(
|
||||
<TagOperation
|
||||
operation={mockOperation}
|
||||
tag={mockTag}
|
||||
endpointPath={mockEndpointPath}
|
||||
/>
|
||||
)
|
||||
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
fireEvent.click(inViewToggleButton)
|
||||
await waitFor(() => {
|
||||
const codeSectionElement = getByTestId("code-section")
|
||||
expect(codeSectionElement).toBeInTheDocument()
|
||||
expect(codeSectionElement).toHaveAttribute("data-method", "")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import type { OpenAPI } from "types"
|
||||
import clsx from "clsx"
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
@@ -75,14 +76,16 @@ const TagOperation = ({
|
||||
}, [nodeRef, isBrowser, scrollToTop])
|
||||
|
||||
useEffect(() => {
|
||||
if (nodeRef && nodeRef.current) {
|
||||
removeLoading()
|
||||
const currentHash = location.hash.replace("#", "")
|
||||
if (currentHash === path) {
|
||||
setTimeout(scrollIntoView, 200)
|
||||
} else if (currentHash.split("_")[0] === path.split("_")[0]) {
|
||||
setShow(true)
|
||||
}
|
||||
if (!nodeRef.current) {
|
||||
return
|
||||
}
|
||||
|
||||
removeLoading()
|
||||
const currentHash = location.hash.replace("#", "")
|
||||
if (currentHash === path) {
|
||||
setTimeout(scrollIntoView, 200)
|
||||
} else if (currentHash.split("_")[0] === path.split("_")[0]) {
|
||||
setShow(true)
|
||||
}
|
||||
}, [nodeRef, path, scrollIntoView])
|
||||
|
||||
@@ -129,6 +132,7 @@ const TagOperation = ({
|
||||
style={{
|
||||
animationFillMode: "forwards",
|
||||
}}
|
||||
data-testid="operation-container"
|
||||
>
|
||||
<DividedLayout
|
||||
mainContent={
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render } from "@testing-library/react"
|
||||
import { OpenAPI, Sidebar } from "types"
|
||||
|
||||
// mock data
|
||||
const mockTag: OpenAPI.TagObject = {
|
||||
name: "mockTag",
|
||||
description: "Mock Tag",
|
||||
}
|
||||
const mockOperation: OpenAPI.Operation = {
|
||||
operationId: "mockOperation",
|
||||
summary: "Mock Operation",
|
||||
description: "Mock Operation",
|
||||
"x-authenticated": false,
|
||||
"x-codeSamples": [],
|
||||
requestBody: { content: {} },
|
||||
parameters: [],
|
||||
responses: {
|
||||
"200": {
|
||||
description: "OK",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: { name: { type: "string", properties: {} } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const mockPaths: OpenAPI.PathsObject = {
|
||||
"/mock-path": {
|
||||
get: mockOperation,
|
||||
},
|
||||
}
|
||||
const mockSidebar: Sidebar.Sidebar = {
|
||||
sidebar_id: "mock-sidebar-id",
|
||||
title: "Mock Sidebar",
|
||||
items: [],
|
||||
}
|
||||
|
||||
// mock functions
|
||||
const mockFindSidebarItem = vi.fn((options: unknown) => undefined as Sidebar.SidebarItem | undefined)
|
||||
const mockAddItems = vi.fn()
|
||||
const mockUseSidebar = vi.fn(() => ({
|
||||
shownSidebar: mockSidebar as Sidebar.Sidebar | Sidebar.SidebarItemSidebar | undefined,
|
||||
addItems: mockAddItems,
|
||||
}))
|
||||
const mockUseLoading = vi.fn(() => ({
|
||||
loading: false,
|
||||
}))
|
||||
const mockGetTagChildSidebarItems = vi.fn(() => [] as Sidebar.SidebarItem[])
|
||||
const mockCompareOperations = vi.fn((options: unknown) => 0)
|
||||
|
||||
// mock components and hooks
|
||||
vi.mock("docs-ui", () => ({
|
||||
findSidebarItem: (options: unknown) => mockFindSidebarItem(options),
|
||||
useSidebar: () => mockUseSidebar(),
|
||||
}))
|
||||
vi.mock("react", async () => {
|
||||
const actual = await vi.importActual<typeof React>("react")
|
||||
|
||||
return {
|
||||
...actual,
|
||||
Suspense: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
}
|
||||
})
|
||||
vi.mock("@/utils/get-tag-child-sidebar-items", () => ({
|
||||
default: () => mockGetTagChildSidebarItems(),
|
||||
}))
|
||||
vi.mock("@/providers/loading", () => ({
|
||||
useLoading: () => mockUseLoading(),
|
||||
}))
|
||||
vi.mock("@/components/DividedLoading", () => ({
|
||||
default: (className: string) => <div data-testid="divided-loading" className={className}>Loading...</div>,
|
||||
}))
|
||||
vi.mock("@/utils/sort-operations-utils", () => ({
|
||||
compareOperations: (options: unknown) => mockCompareOperations(options),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation", () => ({
|
||||
default: (props: TagOperationProps) => (
|
||||
<div
|
||||
data-testid="operation-container"
|
||||
data-method={props.method}
|
||||
data-endpoint-path={props.endpointPath}
|
||||
data-operation-id={props.operation.operationId}
|
||||
>Operation</div>
|
||||
),
|
||||
}))
|
||||
|
||||
import TagPaths from ".."
|
||||
import { TagOperationProps } from "../../Operation"
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders loading when loading is true", () => {
|
||||
mockUseLoading.mockReturnValue({ loading: true })
|
||||
const { container } = render(
|
||||
<TagPaths tag={mockTag} paths={mockPaths} />
|
||||
)
|
||||
const dividedLoadingElement = container.querySelector("[data-testid='divided-loading']")
|
||||
expect(dividedLoadingElement).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("does not render loading when loading is false", () => {
|
||||
mockUseLoading.mockReturnValue({ loading: false })
|
||||
const { container } = render(
|
||||
<TagPaths tag={mockTag} paths={mockPaths} />
|
||||
)
|
||||
const dividedLoadingElement = container.querySelector("[data-testid='divided-loading']")
|
||||
expect(dividedLoadingElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders operations", () => {
|
||||
const { container } = render(
|
||||
<TagPaths tag={mockTag} paths={mockPaths} />
|
||||
)
|
||||
const operationElements = container.querySelectorAll("[data-testid='operation-container']")
|
||||
expect(operationElements).toHaveLength(1)
|
||||
expect(operationElements[0]).toHaveAttribute("data-method", "get")
|
||||
expect(operationElements[0]).toHaveAttribute("data-endpoint-path", "/mock-path")
|
||||
expect(operationElements[0]).toHaveAttribute("data-operation-id", "mockOperation")
|
||||
})
|
||||
|
||||
test("renders operations in the correct order", () => {
|
||||
// change the mockCompareOperations return value to 1
|
||||
mockCompareOperations.mockReturnValue(-1)
|
||||
const modifiedMockPaths: OpenAPI.PathsObject = {
|
||||
"/mock-path": {
|
||||
get: {
|
||||
...mockOperation,
|
||||
operationId: "mockOperation1",
|
||||
},
|
||||
post: {
|
||||
...mockOperation,
|
||||
operationId: "mockOperation2",
|
||||
},
|
||||
},
|
||||
}
|
||||
const { container } = render(
|
||||
<TagPaths tag={mockTag} paths={modifiedMockPaths} />
|
||||
)
|
||||
const operationElements = container.querySelectorAll("[data-testid='operation-container']")
|
||||
expect(operationElements).toHaveLength(2)
|
||||
expect(operationElements[0]).toHaveAttribute("data-operation-id", "mockOperation2")
|
||||
expect(operationElements[1]).toHaveAttribute("data-operation-id", "mockOperation1")
|
||||
})
|
||||
})
|
||||
|
||||
describe("sidebar", () => {
|
||||
test("doesn't add items to sidebar when shownSidebar is not set", () => {
|
||||
mockUseSidebar.mockReturnValue({
|
||||
shownSidebar: undefined,
|
||||
addItems: mockAddItems,
|
||||
})
|
||||
render(
|
||||
<TagPaths tag={mockTag} paths={mockPaths} />
|
||||
)
|
||||
expect(mockAddItems).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("doesn't add items to sidebar when paths is not set", () => {
|
||||
mockUseSidebar.mockReturnValue({
|
||||
shownSidebar: mockSidebar,
|
||||
addItems: mockAddItems,
|
||||
})
|
||||
render(
|
||||
<TagPaths tag={mockTag} paths={{}} />
|
||||
)
|
||||
expect(mockAddItems).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("adds items to sidebar when shownSidebar is set and paths is set and tag doesn't have an associated schema", () => {
|
||||
mockUseSidebar.mockReturnValue({
|
||||
shownSidebar: {
|
||||
...mockSidebar,
|
||||
items: [
|
||||
{
|
||||
type: "category",
|
||||
title: mockTag.name,
|
||||
children: [],
|
||||
}
|
||||
]
|
||||
},
|
||||
addItems: mockAddItems,
|
||||
})
|
||||
mockFindSidebarItem.mockReturnValue({
|
||||
type: "category",
|
||||
title: mockTag.name,
|
||||
children: [],
|
||||
})
|
||||
const mockPathItems: Sidebar.SidebarItem[] = [
|
||||
{
|
||||
type: "link",
|
||||
title: "Mock Link",
|
||||
path: "/mock-link",
|
||||
children: [],
|
||||
}
|
||||
]
|
||||
mockGetTagChildSidebarItems.mockReturnValue(mockPathItems)
|
||||
render(
|
||||
<TagPaths tag={mockTag} paths={mockPaths} />
|
||||
)
|
||||
expect(mockAddItems).toHaveBeenCalledWith(mockPathItems, {
|
||||
sidebar_id: mockSidebar.sidebar_id,
|
||||
// since there is no associated schema, the index position is 0
|
||||
indexPosition: 0,
|
||||
parent: {
|
||||
type: "category",
|
||||
title: mockTag.name,
|
||||
path: "",
|
||||
changeLoaded: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("adds items to sidebar when shownSidebar is set and paths is set and tag has an associated schema", () => {
|
||||
mockUseSidebar.mockReturnValue({
|
||||
shownSidebar: {
|
||||
...mockSidebar,
|
||||
items: [
|
||||
{
|
||||
type: "category",
|
||||
title: mockTag.name,
|
||||
children: [],
|
||||
}
|
||||
]
|
||||
},
|
||||
addItems: mockAddItems,
|
||||
})
|
||||
mockFindSidebarItem.mockReturnValue({
|
||||
type: "category",
|
||||
title: mockTag.name,
|
||||
children: [],
|
||||
})
|
||||
const mockPathItems: Sidebar.SidebarItem[] = [
|
||||
{
|
||||
type: "link",
|
||||
title: "Mock Link",
|
||||
path: "/mock-link",
|
||||
children: [],
|
||||
}
|
||||
]
|
||||
mockGetTagChildSidebarItems.mockReturnValue(mockPathItems)
|
||||
render(
|
||||
<TagPaths tag={{
|
||||
...mockTag,
|
||||
"x-associatedSchema": {
|
||||
$ref: "#/components/schemas/MockSchema",
|
||||
}
|
||||
}} paths={mockPaths} />
|
||||
)
|
||||
expect(mockAddItems).toHaveBeenCalledWith(mockPathItems, {
|
||||
sidebar_id: mockSidebar.sidebar_id,
|
||||
indexPosition: 1,
|
||||
parent: {
|
||||
type: "category",
|
||||
title: mockTag.name,
|
||||
path: "",
|
||||
changeLoaded: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("doesn't add items to sidebar when parent item is not found", () => {
|
||||
mockUseSidebar.mockReturnValue({
|
||||
shownSidebar: mockSidebar,
|
||||
addItems: mockAddItems,
|
||||
})
|
||||
mockFindSidebarItem.mockReturnValue(undefined)
|
||||
render(
|
||||
<TagPaths tag={mockTag} paths={mockPaths} />
|
||||
)
|
||||
expect(mockAddItems).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("doesn't add items to sidebar when parent item has enough children and tag doesn't have an associated schema", () => {
|
||||
mockUseSidebar.mockReturnValue({
|
||||
shownSidebar: mockSidebar,
|
||||
addItems: mockAddItems,
|
||||
})
|
||||
mockFindSidebarItem.mockReturnValue({
|
||||
type: "category",
|
||||
title: mockTag.name,
|
||||
children: [{ type: "link", title: "Mock Link", path: "/mock-link", children: [] }],
|
||||
})
|
||||
render(
|
||||
<TagPaths tag={mockTag} paths={mockPaths} />
|
||||
)
|
||||
expect(mockAddItems).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("adds item to sidebar when parent item has an item and tag has an associated schema", () => {
|
||||
mockUseSidebar.mockReturnValue({
|
||||
shownSidebar: mockSidebar,
|
||||
addItems: mockAddItems,
|
||||
})
|
||||
mockFindSidebarItem.mockReturnValue({
|
||||
type: "category",
|
||||
title: mockTag.name,
|
||||
children: [{ type: "link", title: "Mock Schema", path: "/mock-schema", children: [] }],
|
||||
})
|
||||
render(
|
||||
<TagPaths tag={{
|
||||
...mockTag,
|
||||
"x-associatedSchema": {
|
||||
$ref: "#/components/schemas/MockSchema",
|
||||
}
|
||||
}} paths={mockPaths} />
|
||||
)
|
||||
expect(mockAddItems).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("doesn't add items to sidebar when parent item has enough children and tag has an associated schema", () => {
|
||||
mockUseSidebar.mockReturnValue({
|
||||
shownSidebar: mockSidebar,
|
||||
addItems: mockAddItems,
|
||||
})
|
||||
mockFindSidebarItem.mockReturnValue({
|
||||
type: "category",
|
||||
title: mockTag.name,
|
||||
children: [{
|
||||
type: "link",
|
||||
title: "Mock Link",
|
||||
path: "/mock-link",
|
||||
children: []
|
||||
}, {
|
||||
type: "link",
|
||||
title: "Mock Link 2",
|
||||
path: "/mock-link-2",
|
||||
children: [] }],
|
||||
})
|
||||
render(
|
||||
<TagPaths tag={{
|
||||
...mockTag,
|
||||
"x-associatedSchema": {
|
||||
$ref: "#/components/schemas/MockSchema",
|
||||
}
|
||||
}} paths={mockPaths} />
|
||||
)
|
||||
expect(mockAddItems).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import type { OpenAPI } from "types"
|
||||
import { findSidebarItem, useSidebar } from "docs-ui"
|
||||
import { Fragment, Suspense, useEffect, useMemo } from "react"
|
||||
@@ -10,7 +11,7 @@ import getTagChildSidebarItems from "@/utils/get-tag-child-sidebar-items"
|
||||
import { useLoading } from "@/providers/loading"
|
||||
import DividedLoading from "@/components/DividedLoading"
|
||||
import { Sidebar } from "types"
|
||||
import { compareOperations } from "../../../utils/sort-operations-utils"
|
||||
import { compareOperations } from "@/utils/sort-operations-utils"
|
||||
|
||||
const TagOperation = dynamic<TagOperationProps>(
|
||||
async () => import("../Operation")
|
||||
@@ -26,34 +27,31 @@ const TagPaths = ({ tag, className, paths }: TagPathsProps) => {
|
||||
const { loading } = useLoading()
|
||||
|
||||
useEffect(() => {
|
||||
if (!shownSidebar) {
|
||||
if (!shownSidebar || !Object.keys(paths).length) {
|
||||
return
|
||||
}
|
||||
|
||||
if (paths) {
|
||||
const parentItem = findSidebarItem({
|
||||
sidebarItems:
|
||||
"items" in shownSidebar
|
||||
? shownSidebar.items
|
||||
: shownSidebar.children || [],
|
||||
item: { title: tag.name, type: "category" },
|
||||
checkChildren: false,
|
||||
}) as Sidebar.SidebarItemCategory
|
||||
const pathItems: Sidebar.SidebarItem[] = getTagChildSidebarItems(paths)
|
||||
const targetLength =
|
||||
pathItems.length + (tag["x-associatedSchema"] ? 1 : 0)
|
||||
if ((parentItem.children?.length || 0) < targetLength) {
|
||||
addItems(pathItems, {
|
||||
sidebar_id: shownSidebar.sidebar_id,
|
||||
parent: {
|
||||
type: "category",
|
||||
title: tag.name,
|
||||
path: "",
|
||||
changeLoaded: true,
|
||||
},
|
||||
indexPosition: tag["x-associatedSchema"] ? 1 : 0,
|
||||
})
|
||||
}
|
||||
const parentItem = findSidebarItem({
|
||||
sidebarItems:
|
||||
"items" in shownSidebar
|
||||
? shownSidebar.items
|
||||
: shownSidebar.children || [],
|
||||
item: { title: tag.name, type: "category" },
|
||||
checkChildren: false,
|
||||
}) as Sidebar.SidebarItemCategory | undefined
|
||||
const pathItems: Sidebar.SidebarItem[] = getTagChildSidebarItems(paths)
|
||||
const targetLength = pathItems.length + (tag["x-associatedSchema"] ? 1 : 0)
|
||||
if (parentItem && (parentItem.children?.length || 0) < targetLength) {
|
||||
addItems(pathItems, {
|
||||
sidebar_id: shownSidebar.sidebar_id,
|
||||
parent: {
|
||||
type: "category",
|
||||
title: tag.name,
|
||||
path: "",
|
||||
changeLoaded: true,
|
||||
},
|
||||
indexPosition: tag["x-associatedSchema"] ? 1 : 0,
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [paths, shownSidebar?.sidebar_id])
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render } from "@testing-library/react"
|
||||
import { OpenAPI, Sidebar } from "types"
|
||||
|
||||
// mock data
|
||||
const mockTagName = "mockTagName"
|
||||
const mockOperation: OpenAPI.Operation = {
|
||||
operationId: "mockOperation",
|
||||
summary: "Mock Operation",
|
||||
description: "Mock Operation",
|
||||
"x-authenticated": false,
|
||||
"x-codeSamples": [],
|
||||
requestBody: { content: {} },
|
||||
parameters: [],
|
||||
responses: {
|
||||
"200": {
|
||||
description: "OK",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: { name: { type: "string", properties: {} } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const mockPaths: OpenAPI.PathsObject = {
|
||||
"/mock-path": {
|
||||
get: mockOperation,
|
||||
},
|
||||
}
|
||||
|
||||
// mock functions
|
||||
const mockCompareOperations = vi.fn((options: unknown) => 0)
|
||||
const mockGetSectionId = vi.fn((options: unknown) => "mock-section-id")
|
||||
|
||||
// mock components and hooks
|
||||
vi.mock("docs-utils", () => ({
|
||||
getSectionId: (options: unknown) => mockGetSectionId(options),
|
||||
}))
|
||||
vi.mock("@/utils/sort-operations-utils", () => ({
|
||||
compareOperations: (options: unknown) => mockCompareOperations(options),
|
||||
}))
|
||||
|
||||
import { RoutesSummary } from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders nothing when there are no operations", () => {
|
||||
const { container } = render(
|
||||
<RoutesSummary tagName={mockTagName} paths={{}} />
|
||||
)
|
||||
expect(container).toBeEmptyDOMElement()
|
||||
})
|
||||
|
||||
test("renders operation", () => {
|
||||
const { container } = render(
|
||||
<RoutesSummary tagName={mockTagName} paths={mockPaths} />
|
||||
)
|
||||
const operationLink = container.querySelector("[data-testid='link']")
|
||||
expect(operationLink).toBeInTheDocument()
|
||||
expect(operationLink).toHaveAttribute("href", "#mock-section-id")
|
||||
expect(operationLink).toHaveTextContent("/mock-path")
|
||||
})
|
||||
|
||||
test("renders operations in the correct order", () => {
|
||||
const modifiedMockPaths: OpenAPI.PathsObject = {
|
||||
"/mock-path": {
|
||||
get: {
|
||||
...mockOperation,
|
||||
operationId: "mockOperation1",
|
||||
},
|
||||
post: {
|
||||
...mockOperation,
|
||||
operationId: "mockOperation2",
|
||||
},
|
||||
},
|
||||
}
|
||||
mockCompareOperations.mockReturnValue(-1)
|
||||
mockGetSectionId.mockImplementation(
|
||||
(options: unknown) => (options as string[]).join("-")
|
||||
)
|
||||
const { container } = render(
|
||||
<RoutesSummary tagName={mockTagName} paths={modifiedMockPaths} />
|
||||
)
|
||||
const operationLinks = container.querySelectorAll("[data-testid='link']")
|
||||
expect(operationLinks).toHaveLength(2)
|
||||
expect(operationLinks[0]).toHaveAttribute("href", "#mockTagName-mockOperation2")
|
||||
expect(operationLinks[1]).toHaveAttribute("href", "#mockTagName-mockOperation1")
|
||||
})
|
||||
})
|
||||
@@ -3,7 +3,7 @@ import { getSectionId } from "docs-utils"
|
||||
import Link from "next/link"
|
||||
import React, { useMemo } from "react"
|
||||
import { OpenAPI } from "types"
|
||||
import { compareOperations } from "../../../../utils/sort-operations-utils"
|
||||
import { compareOperations } from "@/utils/sort-operations-utils"
|
||||
|
||||
type RoutesSummaryProps = {
|
||||
tagName: string
|
||||
@@ -85,6 +85,7 @@ export const RoutesSummary = ({ tagName, paths }: RoutesSummaryProps) => {
|
||||
<Link
|
||||
href={`#${operationId}`}
|
||||
className="text-medusa-contrast-fg-secondary hover:text-medusa-contrast-fg-primary w-[85%]"
|
||||
data-testid="link"
|
||||
>
|
||||
{endpointPath}
|
||||
</Link>
|
||||
|
||||
@@ -0,0 +1,592 @@
|
||||
import React, { useState } from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render, waitFor, fireEvent } from "@testing-library/react"
|
||||
import { OpenAPI } from "types"
|
||||
|
||||
// mock data
|
||||
const mockSchema: OpenAPI.SchemaObject = {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
},
|
||||
}
|
||||
const mockTagName = "mockTagName"
|
||||
|
||||
// mock functions
|
||||
const mockIsElmWindow = vi.fn(() => false)
|
||||
const mockUseIsBrowser = vi.fn(() => ({
|
||||
isBrowser: true,
|
||||
}))
|
||||
const mockScrollToElement = vi.fn()
|
||||
const mockUseScrollController = vi.fn(() => ({
|
||||
scrollableElement: null as HTMLElement | null,
|
||||
scrollToElement: mockScrollToElement,
|
||||
}))
|
||||
const mockSetActivePath = vi.fn()
|
||||
const mockUseSidebar = vi.fn(() => ({
|
||||
activePath: null as string | null,
|
||||
setActivePath: mockSetActivePath,
|
||||
}))
|
||||
const mockUseArea = vi.fn(() => ({
|
||||
displayedArea: "Store",
|
||||
}))
|
||||
const mockUseSchemaExample = vi.fn((_options: unknown) => ({
|
||||
examples: [
|
||||
{
|
||||
content: '{"name": "test"}',
|
||||
},
|
||||
],
|
||||
}))
|
||||
const mockGetSectionId = vi.fn((options: unknown) => "mock-schema-slug")
|
||||
const mockCheckElementInViewport = vi.fn(
|
||||
(_element: HTMLElement, _threshold: number) => true
|
||||
)
|
||||
const mockSingular = vi.fn((str: string) => str.replace(/s$/, ""))
|
||||
|
||||
// mock components
|
||||
vi.mock("react-intersection-observer", () => ({
|
||||
InView: ({
|
||||
children,
|
||||
onChange,
|
||||
root,
|
||||
id,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
onChange: (inView: boolean, entry: IntersectionObserverEntry) => void
|
||||
root?: HTMLElement | null
|
||||
id?: string
|
||||
}) => {
|
||||
const [inView, setInView] = useState(false)
|
||||
const mockTarget = document.createElement("div")
|
||||
const mockEntry = {
|
||||
target: mockTarget,
|
||||
boundingClientRect: mockTarget.getBoundingClientRect(),
|
||||
intersectionRatio: 1,
|
||||
intersectionRect: mockTarget.getBoundingClientRect(),
|
||||
isIntersecting: true,
|
||||
rootBounds: null,
|
||||
time: Date.now(),
|
||||
} as unknown as IntersectionObserverEntry
|
||||
|
||||
return (
|
||||
<div data-testid="in-view" data-root={root?.tagName} id={id}>
|
||||
{children}
|
||||
<button
|
||||
type="button"
|
||||
data-testid="in-view-toggle-button"
|
||||
onClick={() => {
|
||||
const newInView = !inView
|
||||
setInView(newInView)
|
||||
onChange(newInView, mockEntry)
|
||||
}}
|
||||
>
|
||||
{inView.toString()}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
vi.mock("docs-ui", () => ({
|
||||
isElmWindow: () => mockIsElmWindow(),
|
||||
useIsBrowser: () => mockUseIsBrowser(),
|
||||
useScrollController: () => mockUseScrollController(),
|
||||
useSidebar: () => mockUseSidebar(),
|
||||
CodeBlock: ({
|
||||
source,
|
||||
lang,
|
||||
title,
|
||||
className,
|
||||
style,
|
||||
}: {
|
||||
source: string
|
||||
lang: string
|
||||
title?: string
|
||||
className?: string
|
||||
style?: React.CSSProperties
|
||||
}) => (
|
||||
<div
|
||||
data-testid="code-block"
|
||||
data-lang={lang}
|
||||
data-title={title}
|
||||
className={className}
|
||||
style={style}
|
||||
>
|
||||
{source}
|
||||
</div>
|
||||
),
|
||||
Note: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-testid="note">{children}</div>
|
||||
),
|
||||
Link: ({
|
||||
href,
|
||||
children,
|
||||
variant,
|
||||
}: {
|
||||
href: string
|
||||
children: React.ReactNode
|
||||
variant?: string
|
||||
}) => (
|
||||
<a data-testid="link" href={href} data-variant={variant}>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Operation/Parameters", () => ({
|
||||
default: ({
|
||||
schemaObject,
|
||||
topLevel,
|
||||
}: {
|
||||
schemaObject: OpenAPI.SchemaObject
|
||||
topLevel?: boolean
|
||||
}) => (
|
||||
<div
|
||||
data-testid="tag-operation-parameters"
|
||||
data-top-level={topLevel?.toString()}
|
||||
>
|
||||
{JSON.stringify(schemaObject)}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/layouts/Divided", () => ({
|
||||
default: ({
|
||||
mainContent,
|
||||
codeContent,
|
||||
}: {
|
||||
mainContent: React.ReactNode
|
||||
codeContent: React.ReactNode
|
||||
}) => (
|
||||
<div data-testid="divided">
|
||||
<div data-testid="divided-main-content">{mainContent}</div>
|
||||
<div data-testid="divided-code-content">{codeContent}</div>
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Section/Container", () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-testid="section-container">{children}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/hooks/use-schema-example", () => ({
|
||||
default: (options: unknown) => mockUseSchemaExample(options),
|
||||
}))
|
||||
vi.mock("@/providers/area", () => ({
|
||||
useArea: () => mockUseArea(),
|
||||
}))
|
||||
vi.mock("@/utils/check-element-in-viewport", () => ({
|
||||
default: (element: HTMLElement, threshold: number) =>
|
||||
mockCheckElementInViewport(element, threshold),
|
||||
}))
|
||||
vi.mock("docs-utils", () => ({
|
||||
getSectionId: (options: unknown) => mockGetSectionId(options),
|
||||
}))
|
||||
vi.mock("pluralize", () => ({
|
||||
singular: (str: string) => mockSingular(str),
|
||||
}))
|
||||
|
||||
import TagSectionSchema from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
window.location.hash = ""
|
||||
// Reset mocks to default values
|
||||
mockIsElmWindow.mockReturnValue(false)
|
||||
mockUseIsBrowser.mockReturnValue({ isBrowser: true })
|
||||
mockUseScrollController.mockReturnValue({
|
||||
scrollableElement: null,
|
||||
scrollToElement: mockScrollToElement,
|
||||
})
|
||||
mockUseSidebar.mockReturnValue({
|
||||
activePath: null,
|
||||
setActivePath: mockSetActivePath,
|
||||
})
|
||||
mockUseArea.mockReturnValue({
|
||||
displayedArea: "Store",
|
||||
})
|
||||
mockUseSchemaExample.mockReturnValue({
|
||||
examples: [
|
||||
{
|
||||
content: '{"name": "test"}',
|
||||
},
|
||||
],
|
||||
})
|
||||
mockGetSectionId.mockReturnValue("mock-schema-slug")
|
||||
mockCheckElementInViewport.mockReturnValue(true)
|
||||
mockSingular.mockImplementation((str: string) => str.replace(/s$/, ""))
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders InView wrapper with correct props", () => {
|
||||
const { getByTestId } = render(
|
||||
<TagSectionSchema schema={mockSchema} tagName={mockTagName} />
|
||||
)
|
||||
const inViewElement = getByTestId("in-view")
|
||||
expect(inViewElement).toBeInTheDocument()
|
||||
expect(inViewElement).toHaveAttribute("id", "mock-schema-slug")
|
||||
})
|
||||
|
||||
test("renders SectionContainer", () => {
|
||||
const { getByTestId } = render(
|
||||
<TagSectionSchema schema={mockSchema} tagName={mockTagName} />
|
||||
)
|
||||
const sectionContainerElement = getByTestId("section-container")
|
||||
expect(sectionContainerElement).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders DividedLayout with mainContent and codeContent", () => {
|
||||
const { getByTestId } = render(
|
||||
<TagSectionSchema schema={mockSchema} tagName={mockTagName} />
|
||||
)
|
||||
const dividedElement = getByTestId("divided")
|
||||
expect(dividedElement).toBeInTheDocument()
|
||||
expect(getByTestId("divided-main-content")).toBeInTheDocument()
|
||||
expect(getByTestId("divided-code-content")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders formatted name in heading", () => {
|
||||
mockSingular.mockReturnValue("mockTagNam")
|
||||
const { container } = render(
|
||||
<TagSectionSchema schema={mockSchema} tagName={mockTagName} />
|
||||
)
|
||||
const heading = container.querySelector("h2")
|
||||
expect(heading).toBeInTheDocument()
|
||||
expect(heading).toHaveTextContent("mockTagNam Object")
|
||||
})
|
||||
|
||||
test("renders Note with displayedArea", () => {
|
||||
mockUseArea.mockReturnValue({
|
||||
displayedArea: "Admin",
|
||||
})
|
||||
const { getByTestId } = render(
|
||||
<TagSectionSchema schema={mockSchema} tagName={mockTagName} />
|
||||
)
|
||||
const noteElement = getByTestId("note")
|
||||
expect(noteElement).toBeInTheDocument()
|
||||
expect(noteElement).toHaveTextContent("Admin")
|
||||
})
|
||||
|
||||
test("renders Link to Commerce Modules Documentation", () => {
|
||||
const { getByTestId } = render(
|
||||
<TagSectionSchema schema={mockSchema} tagName={mockTagName} />
|
||||
)
|
||||
const linkElement = getByTestId("link")
|
||||
expect(linkElement).toBeInTheDocument()
|
||||
expect(linkElement).toHaveAttribute(
|
||||
"href",
|
||||
"https://docs.medusajs.com/resources/commerce-modules"
|
||||
)
|
||||
expect(linkElement).toHaveTextContent("Commerce Modules Documentation")
|
||||
})
|
||||
|
||||
test("renders Fields heading", () => {
|
||||
const { container } = render(
|
||||
<TagSectionSchema schema={mockSchema} tagName={mockTagName} />
|
||||
)
|
||||
const fieldsHeading = container.querySelector("h4")
|
||||
expect(fieldsHeading).toBeInTheDocument()
|
||||
expect(fieldsHeading).toHaveTextContent("Fields")
|
||||
})
|
||||
|
||||
test("renders TagOperationParameters with schema and topLevel prop", () => {
|
||||
const { getByTestId } = render(
|
||||
<TagSectionSchema schema={mockSchema} tagName={mockTagName} />
|
||||
)
|
||||
const parametersElement = getByTestId("tag-operation-parameters")
|
||||
expect(parametersElement).toBeInTheDocument()
|
||||
expect(parametersElement).toHaveAttribute("data-top-level", "true")
|
||||
expect(parametersElement).toHaveTextContent(JSON.stringify(mockSchema))
|
||||
})
|
||||
|
||||
test("renders CodeBlock when examples exist", async () => {
|
||||
const { getByTestId } = render(
|
||||
<TagSectionSchema schema={mockSchema} tagName={mockTagName} />
|
||||
)
|
||||
await waitFor(() => {
|
||||
const codeBlockElement = getByTestId("code-block")
|
||||
expect(codeBlockElement).toBeInTheDocument()
|
||||
expect(codeBlockElement).toHaveAttribute("data-lang", "json")
|
||||
expect(codeBlockElement).toHaveTextContent('{"name": "test"}')
|
||||
})
|
||||
})
|
||||
|
||||
test("does not render CodeBlock when examples are empty", () => {
|
||||
mockUseSchemaExample.mockReturnValue({
|
||||
examples: [],
|
||||
})
|
||||
const { queryByTestId } = render(
|
||||
<TagSectionSchema schema={mockSchema} tagName={mockTagName} />
|
||||
)
|
||||
const codeBlockElement = queryByTestId("code-block")
|
||||
expect(codeBlockElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders CodeBlock with formatted name as title", async () => {
|
||||
mockSingular.mockReturnValue("mockTagNam")
|
||||
const { getByTestId } = render(
|
||||
<TagSectionSchema schema={mockSchema} tagName={mockTagName} />
|
||||
)
|
||||
await waitFor(() => {
|
||||
const codeBlockElement = getByTestId("code-block")
|
||||
expect(codeBlockElement).toHaveAttribute(
|
||||
"data-title",
|
||||
"The mockTagNam Object"
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("name formatting", () => {
|
||||
test("formats tag name using singular", () => {
|
||||
mockSingular.mockReturnValue("product")
|
||||
render(<TagSectionSchema schema={mockSchema} tagName="products" />)
|
||||
expect(mockSingular).toHaveBeenCalledWith("products")
|
||||
})
|
||||
|
||||
test("removes spaces from formatted name", () => {
|
||||
mockSingular.mockReturnValue("test tag")
|
||||
const { container } = render(
|
||||
<TagSectionSchema schema={mockSchema} tagName="test tags" />
|
||||
)
|
||||
const heading = container.querySelector("h2")
|
||||
expect(heading).toHaveTextContent("testtag Object")
|
||||
})
|
||||
})
|
||||
|
||||
describe("schema slug generation", () => {
|
||||
test("generates schema slug using getSectionId with tagName, formattedName, and 'schema'", () => {
|
||||
mockSingular.mockReturnValue("mockTagNam")
|
||||
render(<TagSectionSchema schema={mockSchema} tagName={mockTagName} />)
|
||||
expect(mockGetSectionId).toHaveBeenCalledWith([
|
||||
mockTagName,
|
||||
"mockTagNam",
|
||||
"schema",
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("useEffect scrolling behavior", () => {
|
||||
test("scrolls to element when hash matches schemaSlug and element is not in viewport", () => {
|
||||
const mockPath = "mock-schema-slug"
|
||||
mockGetSectionId.mockReturnValue(mockPath)
|
||||
window.location.hash = `#${mockPath}`
|
||||
mockUseSidebar.mockReturnValue({
|
||||
activePath: mockPath,
|
||||
setActivePath: mockSetActivePath,
|
||||
})
|
||||
mockCheckElementInViewport.mockReturnValue(false)
|
||||
|
||||
// Create a mock element with the id
|
||||
const mockElement = document.createElement("div")
|
||||
mockElement.id = mockPath
|
||||
document.body.appendChild(mockElement)
|
||||
|
||||
render(<TagSectionSchema schema={mockSchema} tagName={mockTagName} />)
|
||||
|
||||
expect(mockScrollToElement).toHaveBeenCalledWith(mockElement)
|
||||
|
||||
document.body.removeChild(mockElement)
|
||||
})
|
||||
|
||||
test("does not scroll when element is already in viewport", () => {
|
||||
const mockPath = "mock-schema-slug"
|
||||
mockGetSectionId.mockReturnValue(mockPath)
|
||||
window.location.hash = `#${mockPath}`
|
||||
mockUseSidebar.mockReturnValue({
|
||||
activePath: mockPath,
|
||||
setActivePath: mockSetActivePath,
|
||||
})
|
||||
mockCheckElementInViewport.mockReturnValue(true)
|
||||
|
||||
const mockElement = document.createElement("div")
|
||||
mockElement.id = mockPath
|
||||
document.body.appendChild(mockElement)
|
||||
|
||||
render(<TagSectionSchema schema={mockSchema} tagName={mockTagName} />)
|
||||
|
||||
expect(mockScrollToElement).not.toHaveBeenCalled()
|
||||
|
||||
document.body.removeChild(mockElement)
|
||||
})
|
||||
|
||||
test("does not scroll when hash does not match schemaSlug", () => {
|
||||
const mockPath = "mock-schema-slug"
|
||||
mockGetSectionId.mockReturnValue(mockPath)
|
||||
window.location.hash = "#different-hash"
|
||||
mockUseSidebar.mockReturnValue({
|
||||
activePath: "different-hash",
|
||||
setActivePath: mockSetActivePath,
|
||||
})
|
||||
|
||||
render(<TagSectionSchema schema={mockSchema} tagName={mockTagName} />)
|
||||
|
||||
expect(mockScrollToElement).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("scrolls when activePath matches but hash does not", () => {
|
||||
const mockPath = "mock-schema-slug"
|
||||
mockGetSectionId.mockReturnValue(mockPath)
|
||||
window.location.hash = "#different-hash"
|
||||
mockUseSidebar.mockReturnValue({
|
||||
activePath: mockPath,
|
||||
setActivePath: mockSetActivePath,
|
||||
})
|
||||
mockCheckElementInViewport.mockReturnValue(false)
|
||||
|
||||
// Create a mock element with the id
|
||||
const mockElement = document.createElement("div")
|
||||
mockElement.id = mockPath
|
||||
document.body.appendChild(mockElement)
|
||||
|
||||
render(<TagSectionSchema schema={mockSchema} tagName={mockTagName} />)
|
||||
|
||||
expect(mockScrollToElement).toHaveBeenCalledWith(mockElement)
|
||||
|
||||
document.body.removeChild(mockElement)
|
||||
})
|
||||
|
||||
test("does not scroll when not in browser", () => {
|
||||
mockUseIsBrowser.mockReturnValue({ isBrowser: false })
|
||||
const mockPath = "mock-schema-slug"
|
||||
mockGetSectionId.mockReturnValue(mockPath)
|
||||
window.location.hash = `#${mockPath}`
|
||||
mockUseSidebar.mockReturnValue({
|
||||
activePath: mockPath,
|
||||
setActivePath: mockSetActivePath,
|
||||
})
|
||||
|
||||
render(<TagSectionSchema schema={mockSchema} tagName={mockTagName} />)
|
||||
|
||||
expect(mockScrollToElement).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("handleViewChange behavior", () => {
|
||||
test("updates URL and active path when in view and activePath is different", () => {
|
||||
const mockPath = "mock-schema-slug"
|
||||
mockGetSectionId.mockReturnValue(mockPath)
|
||||
mockUseSidebar.mockReturnValue({
|
||||
activePath: "different-path",
|
||||
setActivePath: mockSetActivePath,
|
||||
})
|
||||
mockCheckElementInViewport.mockReturnValue(true)
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TagSectionSchema schema={mockSchema} tagName={mockTagName} />
|
||||
)
|
||||
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
fireEvent.click(inViewToggleButton)
|
||||
|
||||
expect(mockSetActivePath).toHaveBeenCalledWith(mockPath)
|
||||
expect(window.location.hash).toBe(`#${mockPath}`)
|
||||
})
|
||||
|
||||
test("does not update when activePath already matches schemaSlug", () => {
|
||||
const mockPath = "mock-schema-slug"
|
||||
mockGetSectionId.mockReturnValue(mockPath)
|
||||
mockUseSidebar.mockReturnValue({
|
||||
activePath: mockPath,
|
||||
setActivePath: mockSetActivePath,
|
||||
})
|
||||
mockCheckElementInViewport.mockReturnValue(true)
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TagSectionSchema schema={mockSchema} tagName={mockTagName} />
|
||||
)
|
||||
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
mockSetActivePath.mockClear()
|
||||
fireEvent.click(inViewToggleButton)
|
||||
|
||||
// Should not be called because activePath === schemaSlug, so the condition fails
|
||||
expect(mockSetActivePath).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("updates when element is in viewport even if not in view", () => {
|
||||
const mockPath = "mock-schema-slug"
|
||||
mockGetSectionId.mockReturnValue(mockPath)
|
||||
mockUseSidebar.mockReturnValue({
|
||||
activePath: "different-path",
|
||||
setActivePath: mockSetActivePath,
|
||||
})
|
||||
mockCheckElementInViewport.mockReturnValue(true)
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TagSectionSchema schema={mockSchema} tagName={mockTagName} />
|
||||
)
|
||||
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
// Click to set inView to false, but checkElementInViewport returns true
|
||||
fireEvent.click(inViewToggleButton)
|
||||
|
||||
expect(mockSetActivePath).toHaveBeenCalledWith(mockPath)
|
||||
})
|
||||
|
||||
test("does not update when not in browser", () => {
|
||||
mockUseIsBrowser.mockReturnValue({ isBrowser: false })
|
||||
const mockPath = "mock-schema-slug"
|
||||
mockGetSectionId.mockReturnValue(mockPath)
|
||||
mockUseSidebar.mockReturnValue({
|
||||
activePath: "different-path",
|
||||
setActivePath: mockSetActivePath,
|
||||
})
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TagSectionSchema schema={mockSchema} tagName={mockTagName} />
|
||||
)
|
||||
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
fireEvent.click(inViewToggleButton)
|
||||
|
||||
expect(mockSetActivePath).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("browser environment", () => {
|
||||
test("handles non-browser environment", () => {
|
||||
mockUseIsBrowser.mockReturnValue({ isBrowser: false })
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TagSectionSchema schema={mockSchema} tagName={mockTagName} />
|
||||
)
|
||||
|
||||
const inViewElement = getByTestId("in-view")
|
||||
expect(inViewElement).toBeInTheDocument()
|
||||
expect(inViewElement).not.toHaveAttribute("data-root")
|
||||
})
|
||||
|
||||
test("uses document.body as root when scrollableElement is window", () => {
|
||||
mockIsElmWindow.mockReturnValue(true)
|
||||
mockUseScrollController.mockReturnValue({
|
||||
scrollableElement: window as unknown as HTMLElement,
|
||||
scrollToElement: mockScrollToElement,
|
||||
})
|
||||
mockUseIsBrowser.mockReturnValue({ isBrowser: true })
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TagSectionSchema schema={mockSchema} tagName={mockTagName} />
|
||||
)
|
||||
|
||||
const inViewElement = getByTestId("in-view")
|
||||
expect(inViewElement).toBeInTheDocument()
|
||||
expect(inViewElement).toHaveAttribute("data-root", "BODY")
|
||||
})
|
||||
|
||||
test("uses scrollableElement as root when it is not window", () => {
|
||||
const mockScrollableElement = document.createElement("div")
|
||||
mockIsElmWindow.mockReturnValue(false)
|
||||
mockUseIsBrowser.mockReturnValue({ isBrowser: true })
|
||||
mockUseScrollController.mockReturnValue({
|
||||
scrollableElement: mockScrollableElement,
|
||||
scrollToElement: mockScrollToElement,
|
||||
})
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TagSectionSchema schema={mockSchema} tagName={mockTagName} />
|
||||
)
|
||||
|
||||
const inViewElement = getByTestId("in-view")
|
||||
expect(inViewElement).toBeInTheDocument()
|
||||
expect(inViewElement).toHaveAttribute("data-root", "DIV")
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { Suspense, useEffect, useMemo } from "react"
|
||||
import { OpenAPI } from "types"
|
||||
import TagOperationParameters from "../../Operation/Parameters"
|
||||
@@ -28,7 +29,7 @@ export type TagSectionSchemaProps = {
|
||||
}
|
||||
|
||||
const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
|
||||
const { setActivePath, activePath, shownSidebar, updateItems } = useSidebar()
|
||||
const { setActivePath, activePath } = useSidebar()
|
||||
const { displayedArea } = useArea()
|
||||
const formattedName = useMemo(
|
||||
() => singular(tagName).replaceAll(" ", ""),
|
||||
|
||||
@@ -0,0 +1,830 @@
|
||||
import React, { useState } from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render, waitFor, fireEvent } from "@testing-library/react"
|
||||
import { OpenAPI } from "types"
|
||||
|
||||
// mock data
|
||||
const mockTag: OpenAPI.TagObject = {
|
||||
name: "mockTag",
|
||||
description: "Mock Tag Description",
|
||||
}
|
||||
|
||||
const mockTagWithExternalDocs: OpenAPI.TagObject = {
|
||||
name: "mockTag",
|
||||
description: "Mock Tag Description",
|
||||
externalDocs: {
|
||||
url: "https://example.com/guide",
|
||||
description: "Read the guide",
|
||||
},
|
||||
}
|
||||
|
||||
const mockTagWithSchema: OpenAPI.TagObject = {
|
||||
name: "mockTag",
|
||||
description: "Mock Tag Description",
|
||||
"x-associatedSchema": {
|
||||
$ref: "#/components/schemas/MockSchema",
|
||||
},
|
||||
}
|
||||
|
||||
const mockSchemaData = {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
},
|
||||
} as OpenAPI.SchemaObject,
|
||||
}
|
||||
|
||||
const mockPathsData = {
|
||||
paths: {
|
||||
"/mock-path": {
|
||||
get: {
|
||||
operationId: "mockOperation",
|
||||
summary: "Mock Operation",
|
||||
description: "Mock Operation",
|
||||
"x-authenticated": false,
|
||||
"x-codeSamples": [],
|
||||
requestBody: { content: {} },
|
||||
parameters: [],
|
||||
responses: {
|
||||
"200": {
|
||||
description: "OK",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string", properties: {} },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenAPI.PathsObject,
|
||||
}
|
||||
|
||||
// mock functions
|
||||
const mockIsElmWindow = vi.fn(() => false)
|
||||
const mockUseIsBrowser = vi.fn(() => ({
|
||||
isBrowser: true,
|
||||
}))
|
||||
const mockScrollToTop = vi.fn()
|
||||
const mockUseScrollController = vi.fn(() => ({
|
||||
scrollableElement: null as HTMLElement | null,
|
||||
scrollToTop: mockScrollToTop,
|
||||
}))
|
||||
const mockSetActivePath = vi.fn()
|
||||
const mockUseSidebar = vi.fn(() => ({
|
||||
activePath: null as string | null,
|
||||
setActivePath: mockSetActivePath,
|
||||
}))
|
||||
const mockUseArea = vi.fn(() => ({
|
||||
area: "store",
|
||||
}))
|
||||
const mockGetSectionId = vi.fn((options: unknown) => "mock-slug-tag-name")
|
||||
const mockCheckElementInViewport = vi.fn(
|
||||
(_element: HTMLElement, _threshold: number) => true
|
||||
)
|
||||
const mockBasePathUrl = vi.fn((url: string) => url)
|
||||
const mockSwrFetcher = vi.fn()
|
||||
const mockPush = vi.fn()
|
||||
const mockReplace = vi.fn()
|
||||
const mockUseRouter = vi.fn(() => ({
|
||||
push: mockPush,
|
||||
replace: mockReplace,
|
||||
}))
|
||||
const mockUseSWR = vi.fn((key: string | null, fetcher: unknown, options: unknown) => ({
|
||||
data: undefined as {paths: OpenAPI.PathsObject} | {schema: OpenAPI.SchemaObject} | undefined,
|
||||
error: undefined,
|
||||
isLoading: false,
|
||||
}))
|
||||
|
||||
// mock components
|
||||
vi.mock("react-intersection-observer", () => ({
|
||||
InView: ({
|
||||
children,
|
||||
onChange,
|
||||
root,
|
||||
id,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
onChange: (inView: boolean) => void
|
||||
root?: HTMLElement | null
|
||||
id?: string
|
||||
className?: string
|
||||
}) => {
|
||||
const [inView, setInView] = useState(false)
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="in-view"
|
||||
data-root={root?.tagName}
|
||||
id={id}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
<button
|
||||
type="button"
|
||||
data-testid="in-view-toggle-button"
|
||||
onClick={() => {
|
||||
const newInView = !inView
|
||||
setInView(newInView)
|
||||
onChange(newInView)
|
||||
}}
|
||||
>
|
||||
{inView.toString()}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
vi.mock("docs-ui", () => ({
|
||||
isElmWindow: () => mockIsElmWindow(),
|
||||
useIsBrowser: () => mockUseIsBrowser(),
|
||||
useScrollController: () => mockUseScrollController(),
|
||||
useSidebar: () => mockUseSidebar(),
|
||||
H2: ({ children }: { children: React.ReactNode }) => (
|
||||
<h2 data-testid="h2">{children}</h2>
|
||||
),
|
||||
Link: ({
|
||||
href,
|
||||
children,
|
||||
target,
|
||||
variant,
|
||||
}: {
|
||||
href: string
|
||||
children: React.ReactNode
|
||||
target?: string
|
||||
variant?: string
|
||||
}) => (
|
||||
<a data-testid="link" href={href} target={target} data-variant={variant}>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
Loading: () => <div data-testid="loading">Loading...</div>,
|
||||
swrFetcher: () => mockSwrFetcher(),
|
||||
}))
|
||||
vi.mock("@/providers/area", () => ({
|
||||
useArea: () => mockUseArea(),
|
||||
}))
|
||||
vi.mock("@/components/Section/Container", () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-testid="section-container">{children}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/layouts/Divided", () => ({
|
||||
default: ({
|
||||
mainContent,
|
||||
codeContent,
|
||||
}: {
|
||||
mainContent: React.ReactNode
|
||||
codeContent: React.ReactNode
|
||||
}) => (
|
||||
<div data-testid="divided">
|
||||
<div data-testid="divided-main-content">{mainContent}</div>
|
||||
<div data-testid="divided-code-content">{codeContent}</div>
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Section/Schema", () => ({
|
||||
default: ({
|
||||
schema,
|
||||
tagName,
|
||||
}: {
|
||||
schema: OpenAPI.SchemaObject
|
||||
tagName: string
|
||||
}) => (
|
||||
<div data-testid="tag-section-schema" data-tag-name={tagName}>
|
||||
{JSON.stringify(schema)}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Paths", () => ({
|
||||
default: ({
|
||||
tag,
|
||||
paths,
|
||||
}: {
|
||||
tag: OpenAPI.TagObject
|
||||
paths: OpenAPI.PathsObject
|
||||
}) => (
|
||||
<div data-testid="tag-paths" data-tag-name={tag.name}>
|
||||
{JSON.stringify(paths)}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Tags/Section/RoutesSummary", () => ({
|
||||
RoutesSummary: ({
|
||||
tagName,
|
||||
paths,
|
||||
}: {
|
||||
tagName: string
|
||||
paths: OpenAPI.PathsObject
|
||||
}) => (
|
||||
<div data-testid="routes-summary" data-tag-name={tagName}>
|
||||
{JSON.stringify(paths)}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Section/Divider", () => ({
|
||||
default: ({ className }: { className?: string }) => (
|
||||
<div data-testid="section-divider" className={className} />
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Feedback", () => ({
|
||||
Feedback: ({
|
||||
question,
|
||||
extraData,
|
||||
}: {
|
||||
question: string
|
||||
extraData: { section: string }
|
||||
}) => (
|
||||
<div data-testid="feedback" data-section={extraData.section}>
|
||||
{question}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/providers/loading", () => ({
|
||||
default: ({
|
||||
children,
|
||||
initialLoading,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
initialLoading?: boolean
|
||||
}) => (
|
||||
<div data-testid="loading-provider" data-initial-loading={initialLoading?.toString()}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/MDXContent/Client", () => ({
|
||||
default: ({
|
||||
content,
|
||||
scope,
|
||||
}: {
|
||||
content: string
|
||||
scope: { addToSidebar: boolean }
|
||||
}) => (
|
||||
<div data-testid="mdx-content-client" data-add-to-sidebar={scope.addToSidebar.toString()}>
|
||||
{content}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Section/Divider", () => ({
|
||||
default: ({ className }: { className?: string }) => (
|
||||
<div data-testid="section-divider" className={className} />
|
||||
),
|
||||
}))
|
||||
vi.mock("@/components/Section", () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => (
|
||||
<div data-testid="section">{children}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("next/navigation", () => ({
|
||||
useRouter: () => mockUseRouter(),
|
||||
}))
|
||||
vi.mock("docs-utils", () => ({
|
||||
getSectionId: (options: unknown) => mockGetSectionId(options),
|
||||
}))
|
||||
vi.mock("@/utils/check-element-in-viewport", () => ({
|
||||
default: (element: HTMLElement, threshold: number) =>
|
||||
mockCheckElementInViewport(element, threshold),
|
||||
}))
|
||||
vi.mock("@/utils/base-path-url", () => ({
|
||||
default: (url: string) => mockBasePathUrl(url),
|
||||
}))
|
||||
vi.mock("swr", () => ({
|
||||
default: (key: string | null, fetcher: unknown, options: unknown) =>
|
||||
mockUseSWR(key, fetcher, options),
|
||||
}))
|
||||
|
||||
import TagSectionComponent from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
window.location.hash = ""
|
||||
// Reset mocks to default values
|
||||
mockIsElmWindow.mockReturnValue(false)
|
||||
mockUseIsBrowser.mockReturnValue({ isBrowser: true })
|
||||
mockUseScrollController.mockReturnValue({
|
||||
scrollableElement: null,
|
||||
scrollToTop: mockScrollToTop,
|
||||
})
|
||||
mockUseSidebar.mockReturnValue({
|
||||
activePath: null,
|
||||
setActivePath: mockSetActivePath,
|
||||
})
|
||||
mockUseArea.mockReturnValue({
|
||||
area: "store",
|
||||
})
|
||||
mockGetSectionId.mockReturnValue("mock-slug-tag-name")
|
||||
mockCheckElementInViewport.mockReturnValue(true)
|
||||
mockBasePathUrl.mockImplementation((url: string) => url)
|
||||
mockUseSWR.mockReturnValue({
|
||||
data: undefined,
|
||||
error: undefined,
|
||||
isLoading: false,
|
||||
})
|
||||
})
|
||||
|
||||
describe("rendering", () => {
|
||||
test("renders InView wrapper with correct props", () => {
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const inViewElement = getByTestId("in-view")
|
||||
expect(inViewElement).toBeInTheDocument()
|
||||
expect(inViewElement).toHaveAttribute("id", "mock-slug-tag-name")
|
||||
expect(inViewElement).toHaveClass("min-h-screen", "relative")
|
||||
})
|
||||
|
||||
test("renders SectionContainer", () => {
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const sectionContainerElement = getByTestId("section-container")
|
||||
expect(sectionContainerElement).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders DividedLayout with mainContent and codeContent", () => {
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const dividedElement = getByTestId("divided")
|
||||
expect(dividedElement).toBeInTheDocument()
|
||||
expect(getByTestId("divided-main-content")).toBeInTheDocument()
|
||||
expect(getByTestId("divided-code-content")).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders H2 with tag name", () => {
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const h2Element = getByTestId("h2")
|
||||
expect(h2Element).toBeInTheDocument()
|
||||
expect(h2Element).toHaveTextContent(mockTag.name)
|
||||
})
|
||||
|
||||
test("renders MDXContentClient when tag has description", async () => {
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
await waitFor(() => {
|
||||
const mdxContentElement = getByTestId("mdx-content-client")
|
||||
expect(mdxContentElement).toBeInTheDocument()
|
||||
expect(mdxContentElement).toHaveTextContent(mockTag.description!)
|
||||
expect(mdxContentElement).toHaveAttribute("data-add-to-sidebar", "false")
|
||||
})
|
||||
})
|
||||
|
||||
test("does not render MDXContentClient when tag has no description", () => {
|
||||
const tagWithoutDescription: OpenAPI.TagObject = {
|
||||
name: "mockTag",
|
||||
}
|
||||
const { queryByTestId } = render(
|
||||
<TagSectionComponent tag={tagWithoutDescription} />
|
||||
)
|
||||
const mdxContentElement = queryByTestId("mdx-content-client")
|
||||
expect(mdxContentElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders external docs link when tag has externalDocs", () => {
|
||||
const { getByTestId } = render(
|
||||
<TagSectionComponent tag={mockTagWithExternalDocs} />
|
||||
)
|
||||
const linkElement = getByTestId("link")
|
||||
expect(linkElement).toBeInTheDocument()
|
||||
expect(linkElement).toHaveAttribute(
|
||||
"href",
|
||||
mockTagWithExternalDocs.externalDocs!.url
|
||||
)
|
||||
expect(linkElement).toHaveAttribute("target", "_blank")
|
||||
expect(linkElement).toHaveTextContent(
|
||||
mockTagWithExternalDocs.externalDocs!.description!
|
||||
)
|
||||
})
|
||||
|
||||
test("renders 'Read More' when externalDocs has no description", () => {
|
||||
const tagWithExternalDocsNoDescription: OpenAPI.TagObject = {
|
||||
name: "mockTag",
|
||||
externalDocs: {
|
||||
url: "https://example.com/guide",
|
||||
},
|
||||
}
|
||||
const { getByTestId } = render(
|
||||
<TagSectionComponent tag={tagWithExternalDocsNoDescription} />
|
||||
)
|
||||
const linkElement = getByTestId("link")
|
||||
expect(linkElement).toHaveTextContent("Read More")
|
||||
})
|
||||
|
||||
test("does not render external docs link when tag has no externalDocs", () => {
|
||||
const { queryByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const linkElement = queryByTestId("link")
|
||||
expect(linkElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders Feedback component", () => {
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const feedbackElement = getByTestId("feedback")
|
||||
expect(feedbackElement).toBeInTheDocument()
|
||||
expect(feedbackElement).toHaveAttribute("data-section", mockTag.name)
|
||||
expect(feedbackElement).toHaveTextContent("Was this section helpful?")
|
||||
})
|
||||
|
||||
test("renders RoutesSummary with empty paths initially", () => {
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const routesSummaryElement = getByTestId("routes-summary")
|
||||
expect(routesSummaryElement).toBeInTheDocument()
|
||||
expect(routesSummaryElement).toHaveAttribute("data-tag-name", mockTag.name)
|
||||
expect(routesSummaryElement).toHaveTextContent("{}")
|
||||
})
|
||||
|
||||
test("renders SectionDivider when loadData is false", () => {
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const dividerElement = getByTestId("section-divider")
|
||||
expect(dividerElement).toBeInTheDocument()
|
||||
expect(dividerElement).toHaveClass("lg:!-left-1")
|
||||
})
|
||||
|
||||
test("does not render SectionDivider when loadData is true", () => {
|
||||
mockUseSWR.mockReturnValue({
|
||||
data: { paths: mockPathsData },
|
||||
error: undefined,
|
||||
isLoading: false,
|
||||
})
|
||||
const { getByTestId, queryByTestId } = render(
|
||||
<TagSectionComponent tag={mockTag} />
|
||||
)
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
fireEvent.click(inViewToggleButton)
|
||||
waitFor(() => {
|
||||
const dividerElement = queryByTestId("section-divider")
|
||||
expect(dividerElement).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("slug generation", () => {
|
||||
test("generates slug using getSectionId with tag name", () => {
|
||||
render(<TagSectionComponent tag={mockTag} />)
|
||||
expect(mockGetSectionId).toHaveBeenCalledWith([mockTag.name])
|
||||
})
|
||||
})
|
||||
|
||||
describe("useSWR hooks", () => {
|
||||
test("does not fetch schema data when loadData is false", () => {
|
||||
render(<TagSectionComponent tag={mockTagWithSchema} />)
|
||||
expect(mockUseSWR).not.toHaveBeenCalledWith(
|
||||
`/schema?name=${mockTagWithSchema["x-associatedSchema"]!.$ref}&area=store`,
|
||||
)
|
||||
})
|
||||
|
||||
test("fetches schema data when loadData is true and tag has x-associatedSchema", () => {
|
||||
mockUseSWR.mockReturnValue({
|
||||
data: mockSchemaData,
|
||||
error: undefined,
|
||||
isLoading: false,
|
||||
})
|
||||
const { getByTestId } = render(
|
||||
<TagSectionComponent tag={mockTagWithSchema} />
|
||||
)
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
fireEvent.click(inViewToggleButton)
|
||||
expect(mockBasePathUrl).toHaveBeenCalledWith(
|
||||
`/schema?name=${mockTagWithSchema["x-associatedSchema"]!.$ref}&area=store`
|
||||
)
|
||||
})
|
||||
|
||||
test("does not fetch paths data when loadData is false", () => {
|
||||
render(<TagSectionComponent tag={mockTag} />)
|
||||
expect(mockUseSWR).not.toHaveBeenCalledWith(`/tag?tagName=mock-slug-tag-name&area=store`)
|
||||
})
|
||||
|
||||
test("fetches paths data when loadData is true", () => {
|
||||
mockUseSWR.mockReturnValue({
|
||||
data: mockPathsData,
|
||||
error: undefined,
|
||||
isLoading: false,
|
||||
})
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
fireEvent.click(inViewToggleButton)
|
||||
expect(mockBasePathUrl).toHaveBeenCalledWith(
|
||||
`/tag?tagName=mock-slug-tag-name&area=store`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("conditional rendering", () => {
|
||||
test("renders TagSectionSchema when schemaData exists", async () => {
|
||||
mockUseSWR.mockImplementation((key: string | null) => {
|
||||
if (key?.includes("schema")) {
|
||||
return {
|
||||
data: mockSchemaData,
|
||||
error: undefined,
|
||||
isLoading: false,
|
||||
}
|
||||
}
|
||||
return {
|
||||
data: undefined,
|
||||
error: undefined,
|
||||
isLoading: false,
|
||||
}
|
||||
})
|
||||
const { getByTestId } = render(
|
||||
<TagSectionComponent tag={mockTagWithSchema} />
|
||||
)
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
fireEvent.click(inViewToggleButton)
|
||||
await waitFor(() => {
|
||||
const schemaElement = getByTestId("tag-section-schema")
|
||||
expect(schemaElement).toBeInTheDocument()
|
||||
expect(schemaElement).toHaveAttribute("data-tag-name", mockTagWithSchema.name)
|
||||
})
|
||||
})
|
||||
|
||||
test("does not render TagSectionSchema when schemaData does not exist", () => {
|
||||
const { queryByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const schemaElement = queryByTestId("tag-section-schema")
|
||||
expect(schemaElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders TagPaths when loadData is true and pathsData exists", async () => {
|
||||
mockUseSWR.mockReturnValue({
|
||||
data: mockPathsData,
|
||||
error: undefined,
|
||||
isLoading: false,
|
||||
})
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
fireEvent.click(inViewToggleButton)
|
||||
await waitFor(() => {
|
||||
const tagPathsElement = getByTestId("tag-paths")
|
||||
expect(tagPathsElement).toBeInTheDocument()
|
||||
expect(tagPathsElement).toHaveAttribute("data-tag-name", mockTag.name)
|
||||
})
|
||||
})
|
||||
|
||||
test("does not render TagPaths when loadData is false", () => {
|
||||
const { queryByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const tagPathsElement = queryByTestId("tag-paths")
|
||||
expect(tagPathsElement).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test("renders LoadingProvider with initialLoading when TagPaths is rendered", async () => {
|
||||
mockUseSWR.mockReturnValue({
|
||||
data: mockPathsData,
|
||||
error: undefined,
|
||||
isLoading: false,
|
||||
})
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
fireEvent.click(inViewToggleButton)
|
||||
await waitFor(() => {
|
||||
const loadingProviderElement = getByTestId("loading-provider")
|
||||
expect(loadingProviderElement).toBeInTheDocument()
|
||||
expect(loadingProviderElement).toHaveAttribute(
|
||||
"data-initial-loading",
|
||||
"true"
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("useEffect scrolling behavior", () => {
|
||||
test("scrolls to element when activePath matches slugTagName and element is not in viewport", () => {
|
||||
const mockPath = "mock-slug-tag-name"
|
||||
mockGetSectionId.mockReturnValue(mockPath)
|
||||
mockUseSidebar.mockReturnValue({
|
||||
activePath: mockPath,
|
||||
setActivePath: mockSetActivePath,
|
||||
})
|
||||
mockCheckElementInViewport.mockReturnValue(false)
|
||||
|
||||
const mockElement = document.createElement("div")
|
||||
mockElement.id = mockPath
|
||||
Object.defineProperty(mockElement, "offsetTop", { value: 100 })
|
||||
Object.defineProperty(mockElement, "offsetParent", { value: { offsetTop: 50 } as HTMLElement })
|
||||
document.body.appendChild(mockElement)
|
||||
|
||||
render(<TagSectionComponent tag={mockTag} />)
|
||||
|
||||
expect(mockScrollToTop).toHaveBeenCalledWith(150, 0)
|
||||
|
||||
document.body.removeChild(mockElement)
|
||||
})
|
||||
|
||||
test("does not scroll when element is already in viewport", () => {
|
||||
const mockPath = "mock-slug-tag-name"
|
||||
mockGetSectionId.mockReturnValue(mockPath)
|
||||
mockUseSidebar.mockReturnValue({
|
||||
activePath: mockPath,
|
||||
setActivePath: mockSetActivePath,
|
||||
})
|
||||
mockCheckElementInViewport.mockReturnValue(true)
|
||||
|
||||
const mockElement = document.createElement("div")
|
||||
mockElement.id = mockPath
|
||||
document.body.appendChild(mockElement)
|
||||
|
||||
render(<TagSectionComponent tag={mockTag} />)
|
||||
|
||||
expect(mockScrollToTop).not.toHaveBeenCalled()
|
||||
|
||||
document.body.removeChild(mockElement)
|
||||
})
|
||||
|
||||
test("sets loadData to true when activePath has multiple parts", () => {
|
||||
const mockPath = "mock-slug-tag-name_operation"
|
||||
mockGetSectionId.mockReturnValue("mock-slug-tag-name")
|
||||
mockUseSidebar.mockReturnValue({
|
||||
activePath: mockPath,
|
||||
setActivePath: mockSetActivePath,
|
||||
})
|
||||
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
// After useEffect runs, loadData should be true, so divider should not be visible
|
||||
waitFor(() => {
|
||||
const dividerElement = getByTestId("section-divider")
|
||||
expect(dividerElement).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
test("does not scroll when activePath does not include slugTagName", () => {
|
||||
mockUseSidebar.mockReturnValue({
|
||||
activePath: "different-path",
|
||||
setActivePath: mockSetActivePath,
|
||||
})
|
||||
|
||||
render(<TagSectionComponent tag={mockTag} />)
|
||||
|
||||
expect(mockScrollToTop).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("does not scroll when activePath is null", () => {
|
||||
mockUseSidebar.mockReturnValue({
|
||||
activePath: null,
|
||||
setActivePath: mockSetActivePath,
|
||||
})
|
||||
|
||||
render(<TagSectionComponent tag={mockTag} />)
|
||||
|
||||
expect(mockScrollToTop).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("does not scroll when not in browser", () => {
|
||||
mockUseIsBrowser.mockReturnValue({ isBrowser: false })
|
||||
const mockPath = "mock-slug-tag-name"
|
||||
mockGetSectionId.mockReturnValue(mockPath)
|
||||
mockUseSidebar.mockReturnValue({
|
||||
activePath: mockPath,
|
||||
setActivePath: mockSetActivePath,
|
||||
})
|
||||
|
||||
render(<TagSectionComponent tag={mockTag} />)
|
||||
|
||||
expect(mockScrollToTop).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("InView onChange behavior", () => {
|
||||
test("sets loadData to true when in view", () => {
|
||||
const { getByTestId, queryByTestId } = render(
|
||||
<TagSectionComponent tag={mockTag} />
|
||||
)
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
fireEvent.click(inViewToggleButton)
|
||||
// After loadData is set, divider should disappear
|
||||
waitFor(() => {
|
||||
const dividerElement = queryByTestId("section-divider")
|
||||
expect(dividerElement).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
test("updates router hash when in view and hash does not match", () => {
|
||||
window.location.hash = "#different-hash"
|
||||
mockUseSidebar.mockReturnValue({
|
||||
activePath: "different-path",
|
||||
setActivePath: mockSetActivePath,
|
||||
})
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
fireEvent.click(inViewToggleButton)
|
||||
|
||||
expect(mockPush).toHaveBeenCalledWith("#mock-slug-tag-name", {
|
||||
scroll: false,
|
||||
})
|
||||
})
|
||||
|
||||
test("sets active path when in view and activePath is different", () => {
|
||||
mockUseSidebar.mockReturnValue({
|
||||
activePath: "different-path",
|
||||
setActivePath: mockSetActivePath,
|
||||
})
|
||||
window.location.hash = "#mock-slug-tag-name"
|
||||
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
fireEvent.click(inViewToggleButton)
|
||||
|
||||
expect(mockSetActivePath).toHaveBeenCalledWith("mock-slug-tag-name")
|
||||
})
|
||||
|
||||
test("does not update hash when current hash links to inner path", () => {
|
||||
window.location.hash = "#mock-slug-tag-name_operation"
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
mockSetActivePath.mockClear()
|
||||
fireEvent.click(inViewToggleButton)
|
||||
|
||||
// Should not update because hash links to inner path
|
||||
expect(mockSetActivePath).not.toHaveBeenCalledWith("mock-slug-tag-name")
|
||||
})
|
||||
|
||||
test("does not update when hash already matches slugTagName", () => {
|
||||
window.location.hash = "#mock-slug-tag-name"
|
||||
mockUseSidebar.mockReturnValue({
|
||||
activePath: "mock-slug-tag-name",
|
||||
setActivePath: mockSetActivePath,
|
||||
})
|
||||
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
mockSetActivePath.mockClear()
|
||||
fireEvent.click(inViewToggleButton)
|
||||
|
||||
// Should not update because already matches
|
||||
expect(mockSetActivePath).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("does not update when not in view", () => {
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
// Click once to set inView to true, then click again to set to false
|
||||
fireEvent.click(inViewToggleButton)
|
||||
mockSetActivePath.mockClear()
|
||||
fireEvent.click(inViewToggleButton)
|
||||
|
||||
// Should not update when not in view
|
||||
expect(mockSetActivePath).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("className behavior", () => {
|
||||
test("applies 'relative' class when loadData is false", () => {
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const inViewElement = getByTestId("in-view")
|
||||
expect(inViewElement).toHaveClass("relative")
|
||||
})
|
||||
|
||||
test("does not apply 'relative' class when loadData is true", async () => {
|
||||
mockUseSWR.mockReturnValue({
|
||||
data: mockPathsData,
|
||||
error: undefined,
|
||||
isLoading: false,
|
||||
})
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const inViewToggleButton = getByTestId("in-view-toggle-button")
|
||||
fireEvent.click(inViewToggleButton)
|
||||
await waitFor(() => {
|
||||
const inViewElement = getByTestId("in-view")
|
||||
expect(inViewElement).not.toHaveClass("relative")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("browser environment", () => {
|
||||
test("handles non-browser environment", () => {
|
||||
mockUseIsBrowser.mockReturnValue({ isBrowser: false })
|
||||
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const inViewElement = getByTestId("in-view")
|
||||
expect(inViewElement).toBeInTheDocument()
|
||||
expect(inViewElement).not.toHaveAttribute("data-root")
|
||||
})
|
||||
|
||||
test("uses document.body as root when scrollableElement is window", () => {
|
||||
mockIsElmWindow.mockReturnValue(true)
|
||||
mockUseScrollController.mockReturnValue({
|
||||
scrollableElement: window as unknown as HTMLElement,
|
||||
scrollToTop: mockScrollToTop,
|
||||
})
|
||||
mockUseIsBrowser.mockReturnValue({ isBrowser: true })
|
||||
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const inViewElement = getByTestId("in-view")
|
||||
expect(inViewElement).toBeInTheDocument()
|
||||
expect(inViewElement).toHaveAttribute("data-root", "BODY")
|
||||
})
|
||||
|
||||
test("uses scrollableElement as root when it is not window", () => {
|
||||
const mockScrollableElement = document.createElement("div")
|
||||
mockIsElmWindow.mockReturnValue(false)
|
||||
mockUseIsBrowser.mockReturnValue({ isBrowser: true })
|
||||
mockUseScrollController.mockReturnValue({
|
||||
scrollableElement: mockScrollableElement,
|
||||
scrollToTop: mockScrollToTop,
|
||||
})
|
||||
|
||||
const { getByTestId } = render(<TagSectionComponent tag={mockTag} />)
|
||||
const inViewElement = getByTestId("in-view")
|
||||
expect(inViewElement).toBeInTheDocument()
|
||||
expect(inViewElement).toHaveAttribute("data-root", "DIV")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { InView } from "react-intersection-observer"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import {
|
||||
@@ -9,6 +10,8 @@ import {
|
||||
useIsBrowser,
|
||||
useScrollController,
|
||||
useSidebar,
|
||||
Loading,
|
||||
Link,
|
||||
} from "docs-ui"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { SectionProps } from "../../Section"
|
||||
@@ -19,7 +22,6 @@ import SectionContainer from "../../Section/Container"
|
||||
import { useArea } from "@/providers/area"
|
||||
import SectionDivider from "../../Section/Divider"
|
||||
import clsx from "clsx"
|
||||
import { Loading, Link } from "docs-ui"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { OpenAPI } from "types"
|
||||
import TagSectionSchema from "./Schema"
|
||||
@@ -36,11 +38,11 @@ export type TagSectionProps = {
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
const Section = dynamic<SectionProps>(
|
||||
async () => import("../../Section")
|
||||
async () => import("@/components/Section")
|
||||
) as React.FC<SectionProps>
|
||||
|
||||
const MDXContentClient = dynamic<MDXContentClientProps>(
|
||||
async () => import("../../MDXContent/Client"),
|
||||
async () => import("@/components/MDXContent/Client"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, render, waitFor } from "@testing-library/react"
|
||||
import { OpenAPI } from "types"
|
||||
|
||||
// mock data
|
||||
const mockTags: OpenAPI.TagObject[] = [
|
||||
{
|
||||
name: "mockTag",
|
||||
description: "Mock Tag",
|
||||
},
|
||||
]
|
||||
|
||||
// mock components
|
||||
vi.mock("@/components/Tags/Section", () => ({
|
||||
default: ({ tag }: { tag: OpenAPI.TagObject }) => (
|
||||
<div data-testid="tag-section">{tag.name}</div>
|
||||
),
|
||||
}))
|
||||
vi.mock("react", async () => {
|
||||
const actual = await vi.importActual<typeof React>("react")
|
||||
|
||||
return {
|
||||
...actual,
|
||||
Suspense: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
}
|
||||
})
|
||||
|
||||
import Tags from ".."
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
cleanup()
|
||||
})
|
||||
|
||||
describe("Tags", () => {
|
||||
test("does not render tags when tags is undefined", () => {
|
||||
const { container } = render(<Tags tags={undefined} />)
|
||||
expect(container).toBeEmptyDOMElement()
|
||||
})
|
||||
|
||||
test("renders tags when tags is defined", async () => {
|
||||
const { container } = render(<Tags tags={mockTags} />)
|
||||
await waitFor(() => {
|
||||
const tagSections = container.querySelectorAll("[data-testid='tag-section']")
|
||||
expect(tagSections).toHaveLength(mockTags.length)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { Suspense } from "react"
|
||||
import { OpenAPI } from "types"
|
||||
import { TagSectionProps } from "./Section"
|
||||
import dynamic from "next/dynamic"
|
||||
import { Suspense } from "react"
|
||||
|
||||
const TagSection = dynamic<TagSectionProps>(
|
||||
async () => import("./Section")
|
||||
|
||||
@@ -29,6 +29,8 @@ export default [
|
||||
"**/public",
|
||||
"**/.eslintrc.js",
|
||||
"**/generated",
|
||||
"**/__tests__",
|
||||
"**/__mocks__",
|
||||
],
|
||||
},
|
||||
...compat.extends(
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
"start": "next start",
|
||||
"start:monorepo": "yarn start -p 3000",
|
||||
"lint": "next lint --fix",
|
||||
"prep": "node ./scripts/prepare.mjs"
|
||||
"prep": "node ./scripts/prepare.mjs",
|
||||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdx-js/loader": "^3.1.0",
|
||||
@@ -64,7 +65,9 @@
|
||||
"eslint": "^9.13.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"types": "*"
|
||||
"types": "*",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^2.1.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
|
||||
182
www/apps/api-reference/providers/__tests__/area.test.tsx
Normal file
182
www/apps/api-reference/providers/__tests__/area.test.tsx
Normal file
@@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
363
www/apps/api-reference/providers/__tests__/base-specs.test.tsx
Normal file
363
www/apps/api-reference/providers/__tests__/base-specs.test.tsx
Normal file
@@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
95
www/apps/api-reference/providers/__tests__/loading.test.tsx
Normal file
95
www/apps/api-reference/providers/__tests__/loading.test.tsx
Normal file
@@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
80
www/apps/api-reference/providers/__tests__/main-nav.test.tsx
Normal file
80
www/apps/api-reference/providers/__tests__/main-nav.test.tsx
Normal file
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
206
www/apps/api-reference/providers/__tests__/page-title.test.tsx
Normal file
206
www/apps/api-reference/providers/__tests__/page-title.test.tsx
Normal file
@@ -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")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
162
www/apps/api-reference/providers/__tests__/search.test.tsx
Normal file
162
www/apps/api-reference/providers/__tests__/search.test.tsx
Normal file
@@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
237
www/apps/api-reference/providers/__tests__/sidebar.test.tsx
Normal file
237
www/apps/api-reference/providers/__tests__/sidebar.test.tsx
Normal file
@@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import type { OpenAPI } from "types"
|
||||
import { capitalize, usePrevious, useSidebar } from "docs-ui"
|
||||
import { createContext, useContext, useEffect, useMemo, useState } from "react"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { OpenAPI } from "types"
|
||||
import { ReactNode, createContext, useContext, useEffect, useMemo } from "react"
|
||||
import getTagChildSidebarItems from "../utils/get-tag-child-sidebar-items"
|
||||
@@ -68,7 +69,8 @@ const BaseSpecsProvider = ({ children, baseSpecs }: BaseSpecsProviderProps) => {
|
||||
children: childItems,
|
||||
loaded: childItems.length > 0,
|
||||
onOpen: () => {
|
||||
if (location.hash !== tagPathName) {
|
||||
const currentHash = location.hash.replace("#", "")
|
||||
if (currentHash !== tagPathName) {
|
||||
router.push(`#${tagPathName}`, {
|
||||
scroll: false,
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import React from "react"
|
||||
import { createContext, useContext, useState } from "react"
|
||||
|
||||
type LoadingContextType = {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import {
|
||||
getNavDropdownItems,
|
||||
MainNavProvider as UiMainNavProvider,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { createContext, useEffect } from "react"
|
||||
import { useSidebar } from "docs-ui"
|
||||
import { useArea } from "./area"
|
||||
@@ -30,6 +31,8 @@ const PageTitleProvider = ({ children }: PageTitleProviderProps) => {
|
||||
) as Sidebar.SidebarItemLink
|
||||
if (item) {
|
||||
document.title = `${item.title} - ${titleSuffix}`
|
||||
} else {
|
||||
document.title = titleSuffix
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { usePageLoading, SearchProvider as UiSearchProvider } from "docs-ui"
|
||||
import { config } from "../config"
|
||||
import basePathUrl from "../utils/base-path-url"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import {
|
||||
SidebarProvider as UiSidebarProvider,
|
||||
usePageLoading,
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
"**/*.mjs"
|
||||
],
|
||||
"exclude": [
|
||||
"specs"
|
||||
"specs",
|
||||
"**/__tests__",
|
||||
"**/__mocks__"
|
||||
]
|
||||
}
|
||||
|
||||
16
www/apps/api-reference/tsconfig.tests.json
Normal file
16
www/apps/api-reference/tsconfig.tests.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "./tsconfig.json",
|
||||
"include": [
|
||||
"**/*.tsx",
|
||||
"**/*.ts",
|
||||
"**/*.js",
|
||||
"src/**/*",
|
||||
"**/*.mjs",
|
||||
"__tests__/**/*",
|
||||
"__mocks__/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"specs"
|
||||
]
|
||||
}
|
||||
18
www/apps/api-reference/vitest.config.mts
Normal file
18
www/apps/api-reference/vitest.config.mts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { defineConfig } from 'vitest/config'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import tsconfigPaths from 'vite-tsconfig-paths'
|
||||
import { resolve } from 'path'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
tsconfigPaths({
|
||||
configNames: ["tsconfig.tests.json"]
|
||||
}),
|
||||
react()
|
||||
],
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
setupFiles: [resolve(__dirname, '../../vitest.setup.ts')],
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react"
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"
|
||||
import { cleanup, fireEvent, render, waitFor } from "@testing-library/react"
|
||||
import * as AiAssistantMocks from "@/components/AiAssistant/__mocks__"
|
||||
import * as AiAssistantMocks from "../../../components/AiAssistant/__mocks__"
|
||||
|
||||
// Mock dependencies
|
||||
const mockUseIsBrowser = vi.fn(() => ({
|
||||
@@ -297,6 +297,9 @@ describe("useAiAssistant hook", () => {
|
||||
|
||||
// Mock grecaptcha to be available after a delay
|
||||
setTimeout(() => {
|
||||
if (!window) {
|
||||
return
|
||||
}
|
||||
;(window as unknown as { grecaptcha?: unknown }).grecaptcha = {}
|
||||
}, 100)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user