mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-25 21:56:08 +00:00
test: update to use mock server
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import os from "os"
|
||||
import path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
import fs from "fs-extra"
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest"
|
||||
|
||||
@@ -10,13 +11,29 @@ import {
|
||||
} from "../utils/helpers"
|
||||
import { createRegistryServer } from "../utils/registry"
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const PUBLIC_R_DIR = path.join(__dirname, "../../../../apps/v4/public/r")
|
||||
const MOCK_PORT = 4001
|
||||
const MOCK_ENV = { REGISTRY_URL: `http://localhost:${MOCK_PORT}/r` }
|
||||
|
||||
// Mock registry server that handles /init requests and serves static files.
|
||||
const mockRegistry = await createRegistryServer([], {
|
||||
port: MOCK_PORT,
|
||||
publicDir: PUBLIC_R_DIR,
|
||||
})
|
||||
|
||||
beforeAll(async () => {
|
||||
await mockRegistry.start()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await mockRegistry.stop()
|
||||
})
|
||||
|
||||
describe("shadcn init - next-app", () => {
|
||||
it("should init with default configuration", async () => {
|
||||
// Sleep for 1 second to avoid race condition with the registry server.
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
|
||||
const fixturePath = await createFixtureTestDirectory("next-app")
|
||||
await npxShadcn(fixturePath, ["init", "--defaults"])
|
||||
await npxShadcn(fixturePath, ["init", "--defaults"], { env: MOCK_ENV })
|
||||
|
||||
const componentsJsonPath = path.join(fixturePath, "components.json")
|
||||
expect(await fs.pathExists(componentsJsonPath)).toBe(true)
|
||||
@@ -57,7 +74,9 @@ describe("shadcn init - next-app", () => {
|
||||
|
||||
it("should init without CSS variables", async () => {
|
||||
const fixturePath = await createFixtureTestDirectory("next-app")
|
||||
await npxShadcn(fixturePath, ["init", "--defaults", "--no-css-variables"])
|
||||
await npxShadcn(fixturePath, ["init", "--defaults", "--no-css-variables"], {
|
||||
env: MOCK_ENV,
|
||||
})
|
||||
|
||||
const componentsJson = await fs.readJson(
|
||||
path.join(fixturePath, "components.json")
|
||||
@@ -67,7 +86,9 @@ describe("shadcn init - next-app", () => {
|
||||
|
||||
it("should init with components", async () => {
|
||||
const fixturePath = await createFixtureTestDirectory("next-app")
|
||||
await npxShadcn(fixturePath, ["init", "--defaults", "button"])
|
||||
await npxShadcn(fixturePath, ["init", "--defaults", "button"], {
|
||||
env: MOCK_ENV,
|
||||
})
|
||||
|
||||
expect(
|
||||
await fs.pathExists(path.join(fixturePath, "components/ui/button.tsx"))
|
||||
@@ -78,7 +99,9 @@ describe("shadcn init - next-app", () => {
|
||||
describe("shadcn init - vite-app", () => {
|
||||
it("should init with custom alias and src", async () => {
|
||||
const fixturePath = await createFixtureTestDirectory("vite-app")
|
||||
await npxShadcn(fixturePath, ["init", "--defaults", "alert-dialog"])
|
||||
await npxShadcn(fixturePath, ["init", "--defaults", "alert-dialog"], {
|
||||
env: MOCK_ENV,
|
||||
})
|
||||
|
||||
const componentsJson = await fs.readJson(
|
||||
path.join(fixturePath, "components.json")
|
||||
@@ -212,7 +235,13 @@ describe("shadcn init - custom style", async () => {
|
||||
|
||||
it("should init with style that extends shadcn", async () => {
|
||||
const fixturePath = await createFixtureTestDirectory("next-app")
|
||||
await npxShadcn(fixturePath, ["init", "http://localhost:4445/r/style.json"])
|
||||
await npxShadcn(
|
||||
fixturePath,
|
||||
["init", "http://localhost:4445/r/style.json"],
|
||||
{
|
||||
env: MOCK_ENV,
|
||||
}
|
||||
)
|
||||
|
||||
const componentsJson = await fs.readJson(
|
||||
path.join(fixturePath, "components.json")
|
||||
@@ -273,10 +302,11 @@ describe("shadcn init - custom style", async () => {
|
||||
|
||||
it("should init with style that extends another style", async () => {
|
||||
const fixturePath = await createFixtureTestDirectory("next-app")
|
||||
await npxShadcn(fixturePath, [
|
||||
"init",
|
||||
"http://localhost:4445/r/style-extended.json",
|
||||
])
|
||||
await npxShadcn(
|
||||
fixturePath,
|
||||
["init", "http://localhost:4445/r/style-extended.json"],
|
||||
{ env: MOCK_ENV }
|
||||
)
|
||||
|
||||
const componentsJson = await fs.readJson(
|
||||
path.join(fixturePath, "components.json")
|
||||
@@ -341,10 +371,11 @@ describe("shadcn init - custom style", async () => {
|
||||
|
||||
it("should init with custom style extended none", async () => {
|
||||
const fixturePath = await createFixtureTestDirectory("next-app")
|
||||
await npxShadcn(fixturePath, [
|
||||
"init",
|
||||
"http://localhost:4445/r/style-extend-none.json",
|
||||
])
|
||||
await npxShadcn(
|
||||
fixturePath,
|
||||
["init", "http://localhost:4445/r/style-extend-none.json"],
|
||||
{ env: MOCK_ENV }
|
||||
)
|
||||
|
||||
// We still expect components.json to be created.
|
||||
// With some defaults.
|
||||
@@ -398,7 +429,7 @@ describe("shadcn init - custom style", async () => {
|
||||
describe("shadcn init - unsupported framework", () => {
|
||||
it("should init with --defaults on unsupported framework", async () => {
|
||||
const fixturePath = await createFixtureTestDirectory("remix-app")
|
||||
await npxShadcn(fixturePath, ["init", "--defaults"])
|
||||
await npxShadcn(fixturePath, ["init", "--defaults"], { env: MOCK_ENV })
|
||||
|
||||
const componentsJsonPath = path.join(fixturePath, "components.json")
|
||||
expect(await fs.pathExists(componentsJsonPath)).toBe(true)
|
||||
@@ -419,7 +450,9 @@ describe("shadcn init - unsupported framework", () => {
|
||||
|
||||
it("should init with --defaults and components on unsupported framework", async () => {
|
||||
const fixturePath = await createFixtureTestDirectory("remix-app")
|
||||
await npxShadcn(fixturePath, ["init", "--defaults", "button"])
|
||||
await npxShadcn(fixturePath, ["init", "--defaults", "button"], {
|
||||
env: MOCK_ENV,
|
||||
})
|
||||
|
||||
expect(
|
||||
await fs.pathExists(path.join(fixturePath, "components/ui/button.tsx"))
|
||||
@@ -444,7 +477,9 @@ describe("shadcn init - template flag", () => {
|
||||
|
||||
it("should accept valid template with --defaults", async () => {
|
||||
const fixturePath = await createFixtureTestDirectory("next-app")
|
||||
await npxShadcn(fixturePath, ["init", "-t", "next", "--defaults"])
|
||||
await npxShadcn(fixturePath, ["init", "-t", "next", "--defaults"], {
|
||||
env: MOCK_ENV,
|
||||
})
|
||||
|
||||
const componentsJsonPath = path.join(fixturePath, "components.json")
|
||||
expect(await fs.pathExists(componentsJsonPath)).toBe(true)
|
||||
@@ -476,6 +511,7 @@ describe("shadcn init - --name flag", () => {
|
||||
|
||||
await npxShadcn(emptyDir, ["init", "--defaults", "--name", projectName], {
|
||||
timeout: 120000,
|
||||
env: MOCK_ENV,
|
||||
})
|
||||
|
||||
const projectPath = path.join(emptyDir, projectName)
|
||||
@@ -506,7 +542,7 @@ describe("shadcn init - --name flag", () => {
|
||||
await npxShadcn(
|
||||
emptyDir,
|
||||
["init", "--defaults", "--name", projectName, "-t", "vite"],
|
||||
{ timeout: 120000 }
|
||||
{ timeout: 120000, env: MOCK_ENV }
|
||||
)
|
||||
|
||||
const projectPath = path.join(emptyDir, projectName)
|
||||
@@ -551,7 +587,7 @@ describe("shadcn init - next-monorepo", () => {
|
||||
"--preset",
|
||||
"radix-nova",
|
||||
],
|
||||
{ timeout: 300000 }
|
||||
{ timeout: 300000, env: MOCK_ENV }
|
||||
)
|
||||
expect(result.exitCode).toBe(0)
|
||||
|
||||
@@ -599,10 +635,9 @@ describe("shadcn init - next-monorepo", () => {
|
||||
it("should create a monorepo with custom preset url", async () => {
|
||||
const projectName = `test-monorepo-url-${process.pid}`
|
||||
|
||||
// Build a custom init URL with specific options.
|
||||
const registryUrl = process.env.REGISTRY_URL || "http://localhost:4000/r"
|
||||
const baseUrl = registryUrl.replace(/\/r\/?$/, "")
|
||||
const initUrl = `${baseUrl}/init?base=radix&style=nova&baseColor=zinc&theme=zinc&iconLibrary=lucide&font=inter&rtl=false&menuAccent=subtle&menuColor=default&radius=default&template=next`
|
||||
// Build a custom init URL with specific options using the mock server.
|
||||
const mockBaseUrl = `http://localhost:${MOCK_PORT}`
|
||||
const initUrl = `${mockBaseUrl}/init?base=radix&style=nova&baseColor=zinc&theme=zinc&iconLibrary=lucide&font=inter&rtl=false&menuAccent=subtle&menuColor=default&radius=default&template=next`
|
||||
|
||||
const result = await npxShadcn(
|
||||
testBaseDir,
|
||||
@@ -615,7 +650,7 @@ describe("shadcn init - next-monorepo", () => {
|
||||
"--preset",
|
||||
initUrl,
|
||||
],
|
||||
{ timeout: 300000 }
|
||||
{ timeout: 300000, env: MOCK_ENV }
|
||||
)
|
||||
expect(result.exitCode).toBe(0)
|
||||
|
||||
@@ -659,11 +694,11 @@ describe("shadcn init - next-monorepo", () => {
|
||||
describe("shadcn init - deprecated --src-dir", () => {
|
||||
it("should reject --src-dir as unknown option", async () => {
|
||||
const fixturePath = await createFixtureTestDirectory("next-app")
|
||||
const result = await npxShadcn(fixturePath, [
|
||||
"init",
|
||||
"--defaults",
|
||||
"--src-dir",
|
||||
])
|
||||
const result = await npxShadcn(
|
||||
fixturePath,
|
||||
["init", "--defaults", "--src-dir"],
|
||||
{ env: MOCK_ENV }
|
||||
)
|
||||
|
||||
expect(result.exitCode).toBe(1)
|
||||
})
|
||||
@@ -676,7 +711,7 @@ describe("shadcn init - existing components.json", () => {
|
||||
const fixturePath = await createFixtureTestDirectory("next-app")
|
||||
|
||||
// Run init with default configuration.
|
||||
await npxShadcn(fixturePath, ["init", "--defaults"])
|
||||
await npxShadcn(fixturePath, ["init", "--defaults"], { env: MOCK_ENV })
|
||||
|
||||
// Override style in components.json.
|
||||
const componentsJsonPath = path.join(fixturePath, "components.json")
|
||||
@@ -685,7 +720,9 @@ describe("shadcn init - existing components.json", () => {
|
||||
await fs.writeJson(componentsJsonPath, config)
|
||||
|
||||
// Reinit with --force.
|
||||
await npxShadcn(fixturePath, ["init", "--force", "--defaults"])
|
||||
await npxShadcn(fixturePath, ["init", "--force", "--defaults"], {
|
||||
env: MOCK_ENV,
|
||||
})
|
||||
|
||||
const newConfig = await fs.readJson(componentsJsonPath)
|
||||
expect(newConfig.style).toBe("new-york")
|
||||
@@ -730,7 +767,7 @@ describe("shadcn init - existing components.json", () => {
|
||||
const fixturePath = await createFixtureTestDirectory("next-app")
|
||||
|
||||
// Run init with default configuration.
|
||||
await npxShadcn(fixturePath, ["init", "--defaults"])
|
||||
await npxShadcn(fixturePath, ["init", "--defaults"], { env: MOCK_ENV })
|
||||
|
||||
// Inject a custom registries object into components.json.
|
||||
const componentsJsonPath = path.join(fixturePath, "components.json")
|
||||
@@ -741,7 +778,9 @@ describe("shadcn init - existing components.json", () => {
|
||||
await fs.writeJson(componentsJsonPath, config)
|
||||
|
||||
// Reinit with --force.
|
||||
await npxShadcn(fixturePath, ["init", "--force", "--defaults"])
|
||||
await npxShadcn(fixturePath, ["init", "--force", "--defaults"], {
|
||||
env: MOCK_ENV,
|
||||
})
|
||||
|
||||
// components.json should exist with no .bak leftover.
|
||||
expect(await fs.pathExists(componentsJsonPath)).toBe(true)
|
||||
|
||||
@@ -72,15 +72,18 @@ export async function npxShadcn(
|
||||
{
|
||||
debug = false,
|
||||
timeout,
|
||||
env,
|
||||
}: {
|
||||
debug?: boolean
|
||||
timeout?: number
|
||||
env?: Record<string, string>
|
||||
} = {}
|
||||
) {
|
||||
const result = await runCommand(cwd, args, {
|
||||
env: {
|
||||
REGISTRY_URL: getRegistryUrl(),
|
||||
SHADCN_TEMPLATE_DIR: TEMPLATES_DIR,
|
||||
...env,
|
||||
},
|
||||
timeout,
|
||||
})
|
||||
|
||||
@@ -2,19 +2,178 @@ import { createServer } from "http"
|
||||
import path from "path"
|
||||
import fs from "fs-extra"
|
||||
|
||||
// Neutral theme CSS vars used by the mock /init handler.
|
||||
const NEUTRAL_THEME_LIGHT: Record<string, string> = {
|
||||
background: "oklch(1 0 0)",
|
||||
foreground: "oklch(0.145 0 0)",
|
||||
card: "oklch(1 0 0)",
|
||||
"card-foreground": "oklch(0.145 0 0)",
|
||||
popover: "oklch(1 0 0)",
|
||||
"popover-foreground": "oklch(0.145 0 0)",
|
||||
primary: "oklch(0.205 0 0)",
|
||||
"primary-foreground": "oklch(0.985 0 0)",
|
||||
secondary: "oklch(0.97 0 0)",
|
||||
"secondary-foreground": "oklch(0.205 0 0)",
|
||||
muted: "oklch(0.97 0 0)",
|
||||
"muted-foreground": "oklch(0.556 0 0)",
|
||||
accent: "oklch(0.97 0 0)",
|
||||
"accent-foreground": "oklch(0.205 0 0)",
|
||||
destructive: "oklch(0.58 0.22 27)",
|
||||
border: "oklch(0.922 0 0)",
|
||||
input: "oklch(0.922 0 0)",
|
||||
ring: "oklch(0.708 0 0)",
|
||||
"chart-1": "oklch(0.809 0.105 251.813)",
|
||||
"chart-2": "oklch(0.623 0.214 259.815)",
|
||||
"chart-3": "oklch(0.546 0.245 262.881)",
|
||||
"chart-4": "oklch(0.488 0.243 264.376)",
|
||||
"chart-5": "oklch(0.424 0.199 265.638)",
|
||||
radius: "0.625rem",
|
||||
sidebar: "oklch(0.985 0 0)",
|
||||
"sidebar-foreground": "oklch(0.145 0 0)",
|
||||
"sidebar-primary": "oklch(0.205 0 0)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.97 0 0)",
|
||||
"sidebar-accent-foreground": "oklch(0.205 0 0)",
|
||||
"sidebar-border": "oklch(0.922 0 0)",
|
||||
"sidebar-ring": "oklch(0.708 0 0)",
|
||||
}
|
||||
|
||||
const NEUTRAL_THEME_DARK: Record<string, string> = {
|
||||
background: "oklch(0.145 0 0)",
|
||||
foreground: "oklch(0.985 0 0)",
|
||||
card: "oklch(0.205 0 0)",
|
||||
"card-foreground": "oklch(0.985 0 0)",
|
||||
popover: "oklch(0.205 0 0)",
|
||||
"popover-foreground": "oklch(0.985 0 0)",
|
||||
primary: "oklch(0.87 0.00 0)",
|
||||
"primary-foreground": "oklch(0.205 0 0)",
|
||||
secondary: "oklch(0.269 0 0)",
|
||||
"secondary-foreground": "oklch(0.985 0 0)",
|
||||
muted: "oklch(0.269 0 0)",
|
||||
"muted-foreground": "oklch(0.708 0 0)",
|
||||
accent: "oklch(0.371 0 0)",
|
||||
"accent-foreground": "oklch(0.985 0 0)",
|
||||
destructive: "oklch(0.704 0.191 22.216)",
|
||||
border: "oklch(1 0 0 / 10%)",
|
||||
input: "oklch(1 0 0 / 15%)",
|
||||
ring: "oklch(0.556 0 0)",
|
||||
"chart-1": "oklch(0.809 0.105 251.813)",
|
||||
"chart-2": "oklch(0.623 0.214 259.815)",
|
||||
"chart-3": "oklch(0.546 0.245 262.881)",
|
||||
"chart-4": "oklch(0.488 0.243 264.376)",
|
||||
"chart-5": "oklch(0.424 0.199 265.638)",
|
||||
sidebar: "oklch(0.205 0 0)",
|
||||
"sidebar-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
||||
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-accent": "oklch(0.269 0 0)",
|
||||
"sidebar-accent-foreground": "oklch(0.985 0 0)",
|
||||
"sidebar-border": "oklch(1 0 0 / 10%)",
|
||||
"sidebar-ring": "oklch(0.556 0 0)",
|
||||
}
|
||||
|
||||
// Base dependencies by base name.
|
||||
const BASE_DEPENDENCIES: Record<string, string[]> = {
|
||||
radix: ["radix-ui"],
|
||||
base: ["@base-ui/react"],
|
||||
}
|
||||
|
||||
// Build a mock /init response matching buildRegistryBase() output.
|
||||
function buildMockInitResponse(params: URLSearchParams) {
|
||||
const base = params.get("base") ?? "base"
|
||||
const style = params.get("style") ?? "nova"
|
||||
const iconLibrary = params.get("iconLibrary") ?? "lucide"
|
||||
const baseColor = params.get("baseColor") ?? "neutral"
|
||||
const font = params.get("font") ?? "geist"
|
||||
const rtl = params.get("rtl") === "true"
|
||||
const menuAccent = params.get("menuAccent") ?? "subtle"
|
||||
const menuColor = params.get("menuColor") ?? "default"
|
||||
const template = params.get("template") ?? undefined
|
||||
|
||||
const dependencies = [
|
||||
"shadcn@latest",
|
||||
"class-variance-authority",
|
||||
"tw-animate-css",
|
||||
...(BASE_DEPENDENCIES[base] ?? []),
|
||||
"lucide-react",
|
||||
]
|
||||
|
||||
const registryDependencies = ["utils"]
|
||||
if (font) {
|
||||
registryDependencies.push(`font-${font}`)
|
||||
}
|
||||
|
||||
return {
|
||||
name: `${base}-${style}`,
|
||||
extends: "none",
|
||||
type: "registry:base",
|
||||
config: {
|
||||
style: `${base}-${style}`,
|
||||
iconLibrary,
|
||||
rtl,
|
||||
menuColor,
|
||||
menuAccent,
|
||||
tailwind: {
|
||||
baseColor,
|
||||
},
|
||||
},
|
||||
dependencies,
|
||||
registryDependencies,
|
||||
cssVars: {
|
||||
light: { ...NEUTRAL_THEME_LIGHT },
|
||||
dark: { ...NEUTRAL_THEME_DARK },
|
||||
},
|
||||
css: {
|
||||
'@import "tw-animate-css"': {},
|
||||
'@import "shadcn/tailwind.css"': {},
|
||||
"@layer base": {
|
||||
"*": { "@apply border-border outline-ring/50": {} },
|
||||
body: { "@apply bg-background text-foreground": {} },
|
||||
},
|
||||
},
|
||||
...(rtl && {
|
||||
docs: `To learn how to set up the RTL provider and fonts for your app, see https://ui.shadcn.com/docs/rtl/${
|
||||
template === "next-monorepo" ? "next" : template ?? "next"
|
||||
}`,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
export async function createRegistryServer(
|
||||
items: Array<{ name: string; type: string } & Record<string, unknown>>,
|
||||
{
|
||||
port = 4444,
|
||||
path = "/r",
|
||||
path: basePath = "/r",
|
||||
publicDir,
|
||||
}: {
|
||||
port?: number
|
||||
path?: string
|
||||
publicDir?: string
|
||||
}
|
||||
) {
|
||||
const server = createServer((request, response) => {
|
||||
const urlRaw = request.url?.split("?")[0]
|
||||
|
||||
// Handle /init endpoint.
|
||||
if (request.url?.startsWith("/init")) {
|
||||
const params = new URLSearchParams(request.url.split("?")[1] ?? "")
|
||||
const initResponse = buildMockInitResponse(params)
|
||||
response.writeHead(200, { "Content-Type": "application/json" })
|
||||
response.end(JSON.stringify(initResponse))
|
||||
return
|
||||
}
|
||||
|
||||
// When publicDir is set, try serving static JSON files first.
|
||||
if (publicDir && urlRaw?.startsWith(basePath + "/")) {
|
||||
const relativePath = urlRaw.slice(basePath.length + 1)
|
||||
const filePath = path.join(publicDir, relativePath)
|
||||
if (fs.pathExistsSync(filePath)) {
|
||||
response.writeHead(200, { "Content-Type": "application/json" })
|
||||
response.end(fs.readFileSync(filePath, "utf-8"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Handle registries.json endpoint (don't strip .json for this one).
|
||||
if (urlRaw?.endsWith("/registries.json")) {
|
||||
response.writeHead(200, { "Content-Type": "application/json" })
|
||||
@@ -177,7 +336,7 @@ export async function createRegistryServer(
|
||||
}
|
||||
|
||||
const match = urlWithoutQuery?.match(
|
||||
new RegExp(`^${path}/(?:.*/)?([^/]+)$`)
|
||||
new RegExp(`^${basePath}/(?:.*/)?([^/]+)$`)
|
||||
)
|
||||
const itemName = match?.[1]
|
||||
const item = itemName
|
||||
|
||||
Reference in New Issue
Block a user