mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-29 23:55:02 +00:00
feat(shadcn): handle nested paths for components (#6369)
* feat(shadcn): handle nested paths for components * chore: add changeset
This commit is contained in:
5
.changeset/silent-rules-cover.md
Normal file
5
.changeset/silent-rules-cover.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"shadcn": minor
|
||||
---
|
||||
|
||||
handle nested file path
|
||||
@@ -233,40 +233,6 @@ export async function fetchRegistry(paths: string[]) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getRegistryItemFileTargetPath(
|
||||
file: z.infer<typeof registryItemFileSchema>,
|
||||
config: Config,
|
||||
override?: string
|
||||
) {
|
||||
if (override) {
|
||||
return override
|
||||
}
|
||||
|
||||
if (file.type === "registry:ui") {
|
||||
return config.resolvedPaths.ui
|
||||
}
|
||||
|
||||
if (file.type === "registry:lib") {
|
||||
return config.resolvedPaths.lib
|
||||
}
|
||||
|
||||
if (file.type === "registry:block" || file.type === "registry:component") {
|
||||
return config.resolvedPaths.components
|
||||
}
|
||||
|
||||
if (file.type === "registry:hook") {
|
||||
return config.resolvedPaths.hooks
|
||||
}
|
||||
|
||||
// TODO: we put this in components for now.
|
||||
// We should move this to pages as per framework.
|
||||
if (file.type === "registry:page") {
|
||||
return config.resolvedPaths.components
|
||||
}
|
||||
|
||||
return config.resolvedPaths.components
|
||||
}
|
||||
|
||||
export async function registryResolveItemsTree(
|
||||
names: z.infer<typeof registryItemSchema>["name"][],
|
||||
config: Config
|
||||
|
||||
@@ -56,6 +56,8 @@ export async function transformCssVars(
|
||||
config: Config,
|
||||
options: {
|
||||
cleanupDefaultNextStyles?: boolean
|
||||
} = {
|
||||
cleanupDefaultNextStyles: false,
|
||||
}
|
||||
) {
|
||||
options = {
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { existsSync, promises as fs } from "fs"
|
||||
import path, { basename } from "path"
|
||||
import {
|
||||
getRegistryBaseColor,
|
||||
getRegistryItemFileTargetPath,
|
||||
} from "@/src/registry/api"
|
||||
import { RegistryItem } from "@/src/registry/schema"
|
||||
import { getRegistryBaseColor } from "@/src/registry/api"
|
||||
import { RegistryItem, registryItemFileSchema } from "@/src/registry/schema"
|
||||
import { Config } from "@/src/utils/get-config"
|
||||
import { getProjectInfo } from "@/src/utils/get-project-info"
|
||||
import { highlighter } from "@/src/utils/highlighter"
|
||||
@@ -17,19 +14,7 @@ import { transformImport } from "@/src/utils/transformers/transform-import"
|
||||
import { transformRsc } from "@/src/utils/transformers/transform-rsc"
|
||||
import { transformTwPrefixes } from "@/src/utils/transformers/transform-tw-prefix"
|
||||
import prompts from "prompts"
|
||||
|
||||
export function resolveTargetDir(
|
||||
projectInfo: Awaited<ReturnType<typeof getProjectInfo>>,
|
||||
config: Config,
|
||||
target: string
|
||||
) {
|
||||
if (target.startsWith("~/")) {
|
||||
return path.join(config.resolvedPaths.cwd, target.replace("~/", ""))
|
||||
}
|
||||
return projectInfo?.isSrcDir
|
||||
? path.join(config.resolvedPaths.cwd, "src", target)
|
||||
: path.join(config.resolvedPaths.cwd, target)
|
||||
}
|
||||
import { z } from "zod"
|
||||
|
||||
export async function updateFiles(
|
||||
files: RegistryItem["files"],
|
||||
@@ -74,14 +59,15 @@ export async function updateFiles(
|
||||
continue
|
||||
}
|
||||
|
||||
let targetDir = getRegistryItemFileTargetPath(file, config)
|
||||
let filePath = resolveFilePath(file, config, {
|
||||
isSrcDir: projectInfo?.isSrcDir,
|
||||
commonRoot: findCommonRoot(
|
||||
files.map((f) => f.path),
|
||||
file.path
|
||||
),
|
||||
})
|
||||
const fileName = basename(file.path)
|
||||
let filePath = path.join(targetDir, fileName)
|
||||
|
||||
if (file.target) {
|
||||
filePath = resolveTargetDir(projectInfo, config, file.target)
|
||||
targetDir = path.dirname(filePath)
|
||||
}
|
||||
const targetDir = path.dirname(filePath)
|
||||
|
||||
if (!config.tsx) {
|
||||
filePath = filePath.replace(/\.tsx?$/, (match) =>
|
||||
@@ -210,3 +196,113 @@ export async function updateFiles(
|
||||
filesSkipped,
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveFilePath(
|
||||
file: z.infer<typeof registryItemFileSchema>,
|
||||
config: Config,
|
||||
options: {
|
||||
isSrcDir?: boolean
|
||||
commonRoot?: string
|
||||
}
|
||||
) {
|
||||
if (file.target) {
|
||||
if (file.target.startsWith("~/")) {
|
||||
return path.join(config.resolvedPaths.cwd, file.target.replace("~/", ""))
|
||||
}
|
||||
|
||||
return options.isSrcDir
|
||||
? path.join(
|
||||
config.resolvedPaths.cwd,
|
||||
"src",
|
||||
file.target.replace("src/", "")
|
||||
)
|
||||
: path.join(config.resolvedPaths.cwd, file.target.replace("src/", ""))
|
||||
}
|
||||
|
||||
const targetDir = resolveFileTargetDirectory(file, config)
|
||||
|
||||
const relativePath = resolveNestedFilePath(file.path, targetDir)
|
||||
return path.join(targetDir, relativePath)
|
||||
}
|
||||
|
||||
function resolveFileTargetDirectory(
|
||||
file: z.infer<typeof registryItemFileSchema>,
|
||||
config: Config
|
||||
) {
|
||||
if (file.type === "registry:ui") {
|
||||
return config.resolvedPaths.ui
|
||||
}
|
||||
|
||||
if (file.type === "registry:lib") {
|
||||
return config.resolvedPaths.lib
|
||||
}
|
||||
|
||||
if (file.type === "registry:block" || file.type === "registry:component") {
|
||||
return config.resolvedPaths.components
|
||||
}
|
||||
|
||||
if (file.type === "registry:hook") {
|
||||
return config.resolvedPaths.hooks
|
||||
}
|
||||
|
||||
return config.resolvedPaths.components
|
||||
}
|
||||
|
||||
export function findCommonRoot(paths: string[], needle: string): string {
|
||||
// Remove leading slashes for consistent handling
|
||||
const normalizedPaths = paths.map((p) => p.replace(/^\//, ""))
|
||||
const normalizedNeedle = needle.replace(/^\//, "")
|
||||
|
||||
// Get the directory path of the needle by removing the file name
|
||||
const needleDir = normalizedNeedle.split("/").slice(0, -1).join("/")
|
||||
|
||||
// If needle is at root level, return empty string
|
||||
if (!needleDir) {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Split the needle directory into segments
|
||||
const needleSegments = needleDir.split("/")
|
||||
|
||||
// Start from the full path and work backwards
|
||||
for (let i = needleSegments.length; i > 0; i--) {
|
||||
const testPath = needleSegments.slice(0, i).join("/")
|
||||
// Check if this is a common root by verifying if any other paths start with it
|
||||
const hasRelatedPaths = normalizedPaths.some(
|
||||
(path) => path !== normalizedNeedle && path.startsWith(testPath + "/")
|
||||
)
|
||||
if (hasRelatedPaths) {
|
||||
return "/" + testPath // Add leading slash back for the result
|
||||
}
|
||||
}
|
||||
|
||||
// If no common root found with other files, return the parent directory of the needle
|
||||
return "/" + needleDir // Add leading slash back for the result
|
||||
}
|
||||
|
||||
export function resolveNestedFilePath(
|
||||
filePath: string,
|
||||
targetDir: string
|
||||
): string {
|
||||
// Normalize paths by removing leading/trailing slashes
|
||||
const normalizedFilePath = filePath.replace(/^\/|\/$/g, "")
|
||||
const normalizedTargetDir = targetDir.replace(/^\/|\/$/g, "")
|
||||
|
||||
// Split paths into segments
|
||||
const fileSegments = normalizedFilePath.split("/")
|
||||
const targetSegments = normalizedTargetDir.split("/")
|
||||
|
||||
// Find the last matching segment from targetDir in filePath
|
||||
const lastTargetSegment = targetSegments[targetSegments.length - 1]
|
||||
const commonDirIndex = fileSegments.findIndex(
|
||||
(segment) => segment === lastTargetSegment
|
||||
)
|
||||
|
||||
if (commonDirIndex === -1) {
|
||||
// Return just the filename if no common directory is found
|
||||
return fileSegments[fileSegments.length - 1]
|
||||
}
|
||||
|
||||
// Return everything after the common directory
|
||||
return fileSegments.slice(commonDirIndex + 1).join("/")
|
||||
}
|
||||
|
||||
@@ -1,65 +1,471 @@
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { resolveTargetDir } from "../../../src/utils/updaters/update-files"
|
||||
import {
|
||||
findCommonRoot,
|
||||
resolveFilePath,
|
||||
resolveNestedFilePath,
|
||||
} from "../../../src/utils/updaters/update-files"
|
||||
|
||||
describe("resolveTargetDir", () => {
|
||||
test("should handle a home target without a src directory", () => {
|
||||
const targetDir = resolveTargetDir(
|
||||
{
|
||||
describe("resolveFilePath", () => {
|
||||
test.each([
|
||||
{
|
||||
description: "should use target when provided",
|
||||
file: {
|
||||
path: "hello-world/ui/button.tsx",
|
||||
type: "registry:ui",
|
||||
target: "ui/button.tsx",
|
||||
},
|
||||
resolvedPath: "/foo/bar/ui/button.tsx",
|
||||
projectInfo: {
|
||||
isSrcDir: false,
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "should use nested target when provided",
|
||||
file: {
|
||||
path: "hello-world/components/example-card.tsx",
|
||||
type: "registry:component",
|
||||
target: "components/cards/example-card.tsx",
|
||||
},
|
||||
"~/.env"
|
||||
)
|
||||
expect(targetDir).toBe("/foo/bar/.env")
|
||||
})
|
||||
|
||||
test("should handle a home target even with a src directory", () => {
|
||||
const targetDir = resolveTargetDir(
|
||||
{
|
||||
isSrcDir: true,
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
},
|
||||
},
|
||||
"~/.env"
|
||||
)
|
||||
expect(targetDir).toBe("/foo/bar/.env")
|
||||
})
|
||||
|
||||
test("should handle a simple target", () => {
|
||||
const targetDir = resolveTargetDir(
|
||||
{
|
||||
resolvedPath: "/foo/bar/components/cards/example-card.tsx",
|
||||
projectInfo: {
|
||||
isSrcDir: false,
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "should use home target (~) when provided",
|
||||
file: {
|
||||
path: "hello-world/foo.json",
|
||||
type: "registry:lib",
|
||||
target: "~/foo.json",
|
||||
},
|
||||
"./components/ui/button.tsx"
|
||||
)
|
||||
expect(targetDir).toBe("/foo/bar/components/ui/button.tsx")
|
||||
resolvedPath: "/foo/bar/foo.json",
|
||||
projectInfo: {
|
||||
isSrcDir: false,
|
||||
},
|
||||
},
|
||||
])("$description", ({ file, resolvedPath, projectInfo }) => {
|
||||
expect(
|
||||
resolveFilePath(
|
||||
file,
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/components",
|
||||
ui: "/foo/bar/components/ui",
|
||||
lib: "/foo/bar/lib",
|
||||
hooks: "/foo/bar/hooks",
|
||||
},
|
||||
},
|
||||
projectInfo
|
||||
)
|
||||
).toBe(resolvedPath)
|
||||
})
|
||||
|
||||
test("should handle a simple target with src directory", () => {
|
||||
const targetDir = resolveTargetDir(
|
||||
{
|
||||
test.each([
|
||||
{
|
||||
description: "should use src directory when provided",
|
||||
file: {
|
||||
path: "hello-world/ui/button.tsx",
|
||||
type: "registry:ui",
|
||||
target: "design-system/ui/button.tsx",
|
||||
},
|
||||
resolvedPath: "/foo/bar/src/design-system/ui/button.tsx",
|
||||
projectInfo: {
|
||||
isSrcDir: true,
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "should NOT use src directory for root files",
|
||||
file: {
|
||||
path: "hello-world/.env",
|
||||
type: "registry:lib",
|
||||
target: "~/.env",
|
||||
},
|
||||
"./components/ui/button.tsx"
|
||||
)
|
||||
expect(targetDir).toBe("/foo/bar/src/components/ui/button.tsx")
|
||||
resolvedPath: "/foo/bar/.env",
|
||||
projectInfo: {
|
||||
isSrcDir: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "should use src directory when isSrcDir is true",
|
||||
file: {
|
||||
path: "hello-world/lib/foo.ts",
|
||||
type: "registry:lib",
|
||||
target: "lib/foo.ts",
|
||||
},
|
||||
resolvedPath: "/foo/bar/src/lib/foo.ts",
|
||||
projectInfo: {
|
||||
isSrcDir: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "should strip src directory when isSrcDir is false",
|
||||
file: {
|
||||
path: "hello-world/path/to/bar/baz.ts",
|
||||
type: "registry:lib",
|
||||
target: "src/path/to/bar/baz.ts",
|
||||
},
|
||||
resolvedPath: "/foo/bar/path/to/bar/baz.ts",
|
||||
projectInfo: {
|
||||
isSrcDir: false,
|
||||
},
|
||||
},
|
||||
])("$description", ({ file, resolvedPath, projectInfo }) => {
|
||||
expect(
|
||||
resolveFilePath(
|
||||
file,
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/src/components",
|
||||
ui: "/foo/bar/src/primitives",
|
||||
lib: "/foo/bar/src/lib",
|
||||
hooks: "/foo/bar/src/hooks",
|
||||
},
|
||||
},
|
||||
projectInfo
|
||||
)
|
||||
).toBe(resolvedPath)
|
||||
})
|
||||
|
||||
test("should resolve registry:ui file types", () => {
|
||||
expect(
|
||||
resolveFilePath(
|
||||
{
|
||||
path: "hello-world/ui/button.tsx",
|
||||
type: "registry:ui",
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/components",
|
||||
ui: "/foo/bar/components/ui",
|
||||
lib: "/foo/bar/lib",
|
||||
hooks: "/foo/bar/hooks",
|
||||
},
|
||||
},
|
||||
{
|
||||
isSrcDir: false,
|
||||
}
|
||||
)
|
||||
).toBe("/foo/bar/components/ui/button.tsx")
|
||||
|
||||
expect(
|
||||
resolveFilePath(
|
||||
{
|
||||
path: "hello-world/ui/button.tsx",
|
||||
type: "registry:ui",
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/src/components",
|
||||
ui: "/foo/bar/src/primitives",
|
||||
lib: "/foo/bar/src/lib",
|
||||
hooks: "/foo/bar/src/hooks",
|
||||
},
|
||||
},
|
||||
{
|
||||
isSrcDir: true,
|
||||
}
|
||||
)
|
||||
).toBe("/foo/bar/src/primitives/button.tsx")
|
||||
})
|
||||
|
||||
test("should resolve registry:component and registry:block file types", () => {
|
||||
expect(
|
||||
resolveFilePath(
|
||||
{
|
||||
path: "hello-world/components/example-card.tsx",
|
||||
type: "registry:component",
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/components",
|
||||
ui: "/foo/bar/components/ui",
|
||||
lib: "/foo/bar/lib",
|
||||
hooks: "/foo/bar/hooks",
|
||||
},
|
||||
},
|
||||
{
|
||||
isSrcDir: false,
|
||||
}
|
||||
)
|
||||
).toBe("/foo/bar/components/example-card.tsx")
|
||||
|
||||
expect(
|
||||
resolveFilePath(
|
||||
{
|
||||
path: "hello-world/components/example-card.tsx",
|
||||
type: "registry:block",
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/components",
|
||||
ui: "/foo/bar/components/ui",
|
||||
lib: "/foo/bar/lib",
|
||||
hooks: "/foo/bar/hooks",
|
||||
},
|
||||
},
|
||||
{
|
||||
isSrcDir: false,
|
||||
}
|
||||
)
|
||||
).toBe("/foo/bar/components/example-card.tsx")
|
||||
|
||||
expect(
|
||||
resolveFilePath(
|
||||
{
|
||||
path: "hello-world/components/example-card.tsx",
|
||||
type: "registry:component",
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/src/components",
|
||||
ui: "/foo/bar/src/primitives",
|
||||
lib: "/foo/bar/src/lib",
|
||||
hooks: "/foo/bar/src/hooks",
|
||||
},
|
||||
},
|
||||
{
|
||||
isSrcDir: true,
|
||||
}
|
||||
)
|
||||
).toBe("/foo/bar/src/components/example-card.tsx")
|
||||
|
||||
expect(
|
||||
resolveFilePath(
|
||||
{
|
||||
path: "hello-world/components/example-card.tsx",
|
||||
type: "registry:block",
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/src/components",
|
||||
ui: "/foo/bar/src/primitives",
|
||||
lib: "/foo/bar/src/lib",
|
||||
hooks: "/foo/bar/src/hooks",
|
||||
},
|
||||
},
|
||||
{
|
||||
isSrcDir: true,
|
||||
}
|
||||
)
|
||||
).toBe("/foo/bar/src/components/example-card.tsx")
|
||||
})
|
||||
|
||||
test("should resolve registry:lib file types", () => {
|
||||
expect(
|
||||
resolveFilePath(
|
||||
{
|
||||
path: "hello-world/lib/foo.ts",
|
||||
type: "registry:lib",
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/components",
|
||||
ui: "/foo/bar/components/ui",
|
||||
lib: "/foo/bar/lib",
|
||||
hooks: "/foo/bar/hooks",
|
||||
},
|
||||
},
|
||||
{
|
||||
isSrcDir: false,
|
||||
}
|
||||
)
|
||||
).toBe("/foo/bar/lib/foo.ts")
|
||||
|
||||
expect(
|
||||
resolveFilePath(
|
||||
{
|
||||
path: "hello-world/lib/foo.ts",
|
||||
type: "registry:lib",
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/src/components",
|
||||
ui: "/foo/bar/src/primitives",
|
||||
lib: "/foo/bar/src/lib",
|
||||
hooks: "/foo/bar/src/hooks",
|
||||
},
|
||||
},
|
||||
{
|
||||
isSrcDir: true,
|
||||
}
|
||||
)
|
||||
).toBe("/foo/bar/src/lib/foo.ts")
|
||||
})
|
||||
|
||||
test("should resolve registry:hook file types", () => {
|
||||
expect(
|
||||
resolveFilePath(
|
||||
{
|
||||
path: "hello-world/hooks/use-foo.ts",
|
||||
type: "registry:hook",
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/components",
|
||||
ui: "/foo/bar/components/ui",
|
||||
lib: "/foo/bar/lib",
|
||||
hooks: "/foo/bar/hooks",
|
||||
},
|
||||
},
|
||||
{
|
||||
isSrcDir: false,
|
||||
}
|
||||
)
|
||||
).toBe("/foo/bar/hooks/use-foo.ts")
|
||||
|
||||
expect(
|
||||
resolveFilePath(
|
||||
{
|
||||
path: "hello-world/hooks/use-foo.ts",
|
||||
type: "registry:hook",
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/src/components",
|
||||
ui: "/foo/bar/src/primitives",
|
||||
lib: "/foo/bar/src/lib",
|
||||
hooks: "/foo/bar/src/hooks",
|
||||
},
|
||||
},
|
||||
{
|
||||
isSrcDir: true,
|
||||
}
|
||||
)
|
||||
).toBe("/foo/bar/src/hooks/use-foo.ts")
|
||||
})
|
||||
|
||||
test("should resolve nested files", () => {
|
||||
expect(
|
||||
resolveFilePath(
|
||||
{
|
||||
path: "hello-world/components/path/to/example-card.tsx",
|
||||
type: "registry:component",
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/components",
|
||||
ui: "/foo/bar/components/ui",
|
||||
lib: "/foo/bar/lib",
|
||||
hooks: "/foo/bar/hooks",
|
||||
},
|
||||
},
|
||||
{
|
||||
isSrcDir: false,
|
||||
}
|
||||
)
|
||||
).toBe("/foo/bar/components/path/to/example-card.tsx")
|
||||
|
||||
expect(
|
||||
resolveFilePath(
|
||||
{
|
||||
path: "hello-world/design-system/primitives/button.tsx",
|
||||
type: "registry:ui",
|
||||
},
|
||||
{
|
||||
resolvedPaths: {
|
||||
cwd: "/foo/bar",
|
||||
components: "/foo/bar/components",
|
||||
ui: "/foo/bar/components/ui",
|
||||
lib: "/foo/bar/lib",
|
||||
hooks: "/foo/bar/hooks",
|
||||
},
|
||||
},
|
||||
{
|
||||
isSrcDir: false,
|
||||
}
|
||||
)
|
||||
).toBe("/foo/bar/components/ui/button.tsx")
|
||||
})
|
||||
})
|
||||
|
||||
describe("findCommonRoot", () => {
|
||||
test.each([
|
||||
{
|
||||
description: "should find the common root of sibling files",
|
||||
paths: ["/foo/bar/baz/qux", "/foo/bar/baz/quux"],
|
||||
needle: "/foo/bar/baz/qux",
|
||||
expected: "/foo/bar/baz",
|
||||
},
|
||||
{
|
||||
description: "should find common root with nested structures",
|
||||
paths: [
|
||||
"/app/components/header/nav.tsx",
|
||||
"/app/components/header/logo.tsx",
|
||||
"/app/components/header/menu/item.tsx",
|
||||
],
|
||||
needle: "/app/components/header/nav.tsx",
|
||||
expected: "/app/components/header",
|
||||
},
|
||||
{
|
||||
description: "should handle single file in paths",
|
||||
paths: ["/foo/bar/baz/single.tsx"],
|
||||
needle: "/foo/bar/baz/single.tsx",
|
||||
expected: "/foo/bar/baz",
|
||||
},
|
||||
{
|
||||
description: "should handle root level files",
|
||||
paths: ["root.tsx", "config.ts", "package.json"],
|
||||
needle: "root.tsx",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
description: "should handle unrelated paths",
|
||||
paths: ["/foo/bar/baz", "/completely/different/path"],
|
||||
needle: "/foo/bar/baz",
|
||||
expected: "/foo/bar",
|
||||
},
|
||||
])("$description", ({ paths, needle, expected }) => {
|
||||
expect(findCommonRoot(paths, needle)).toBe(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe("resolveNestedFilePath", () => {
|
||||
test.each([
|
||||
{
|
||||
description: "should resolve path after common components directory",
|
||||
filePath: "hello-world/components/path/to/example-card.tsx",
|
||||
targetDir: "/foo/bar/components",
|
||||
expected: "path/to/example-card.tsx",
|
||||
},
|
||||
{
|
||||
description: "should handle different directory depths",
|
||||
filePath: "/foo-bar/components/ui/button.tsx",
|
||||
targetDir: "/src/ui",
|
||||
expected: "button.tsx",
|
||||
},
|
||||
{
|
||||
description: "should handle nested component paths",
|
||||
filePath: "blocks/sidebar/components/nav/item.tsx",
|
||||
targetDir: "/app/components",
|
||||
expected: "nav/item.tsx",
|
||||
},
|
||||
{
|
||||
description: "should return the file path if no common directory",
|
||||
filePath: "something/else/file.tsx",
|
||||
targetDir: "/foo/bar/components",
|
||||
expected: "file.tsx",
|
||||
},
|
||||
{
|
||||
description: "should handle paths with multiple common directories",
|
||||
filePath: "ui/shared/components/utils/button.tsx",
|
||||
targetDir: "/src/components/utils",
|
||||
expected: "button.tsx",
|
||||
},
|
||||
])("$description", ({ filePath, targetDir, expected }) => {
|
||||
expect(resolveNestedFilePath(filePath, targetDir)).toBe(expected)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user