feat(cli): update tests

This commit is contained in:
shadcn
2024-08-18 23:35:19 +04:00
parent 30d47cab2f
commit b95ffc2168
5 changed files with 214 additions and 122 deletions

View File

@@ -25,20 +25,14 @@ export async function updateTailwindConfig(
config: Config
) {
const raw = await fs.readFile(config.resolvedPaths.tailwindConfig, "utf8")
const output = await transformTailwindConfig(raw, tailwindConfig, {
config,
})
const output = await transformTailwindConfig(raw, tailwindConfig, config)
await fs.writeFile(config.resolvedPaths.tailwindConfig, output, "utf8")
}
export async function transformTailwindConfig(
input: string,
tailwindConfig: UpdaterTailwindConfig,
{
config,
}: {
config: Config
}
config: Config
) {
const sourceFile = await _createSourceFile(input, config)
// Find the object with content property.
@@ -231,7 +225,7 @@ function addTailwindConfigPlugin(
async function _createSourceFile(input: string, config: Config | null) {
const dir = await fs.mkdtemp(path.join(tmpdir(), "shadcn-"))
const resolvedPath =
config?.resolvedPaths.tailwindConfig || "tailwind.config.ts"
config?.resolvedPaths?.tailwindConfig || "tailwind.config.ts"
const tempFile = path.join(dir, `shadcn-${path.basename(resolvedPath)}`)
const project = new Project({

View File

@@ -3,7 +3,6 @@ import { Config } from "@/src/utils/get-config"
import { registryCssVarsSchema } from "@/src/utils/registry/schema"
import postcss from "postcss"
import AtRule from "postcss/lib/at-rule"
import Node from "postcss/lib/node"
import Root from "postcss/lib/root"
import Rule from "postcss/lib/rule"
import { z } from "zod"
@@ -13,7 +12,8 @@ export async function updateTailwindCss(
config: Config
) {
const raw = await fs.readFile(config.resolvedPaths.tailwindCss, "utf8")
const output = await transformTailwindCss(raw, cssVars)
let output = await transformTailwindCss(raw, cssVars)
await fs.writeFile(config.resolvedPaths.tailwindCss, output, "utf8")
}
@@ -21,107 +21,137 @@ export async function transformTailwindCss(
input: string,
cssVars: z.infer<typeof registryCssVarsSchema>
) {
const insertCssVarsPlugin = () => {
return {
postcssPlugin: "insert-css-vars",
Once(root: Root) {
let baseLayer = root.nodes.find(
(node) =>
node.type === "atrule" &&
node.name === "layer" &&
node.params === "base"
) as AtRule | undefined
if (!(baseLayer instanceof AtRule)) {
baseLayer = postcss.atRule({
name: "layer",
params: "base",
nodes: [],
raws: { semicolon: true },
})
root.append(baseLayer)
}
// First pass: Add or update variables
if (cssVars.light) {
let lightVars = baseLayer.nodes?.find(
(node: Node) => node instanceof Rule && node.selector === ":root"
) as Rule | undefined
if (!lightVars) {
lightVars = postcss.rule({ selector: ":root" })
baseLayer.append(lightVars)
}
Object.entries(cssVars.light).forEach(([key, value]) => {
const existingDecl = lightVars.nodes.find(
(node) => node.type === "decl" && node.prop === `--${key}`
)
if (existingDecl) {
existingDecl.replaceWith(
postcss.decl({
prop: `--${key}`,
value,
raws: { semicolon: true },
})
)
} else {
lightVars.append({
prop: `--${key}`,
value,
raws: { semicolon: true },
})
}
})
}
if (cssVars.dark) {
let darkVars = baseLayer.nodes?.find(
(node: Node) => node instanceof Rule && node.selector === ".dark"
) as Rule | undefined
if (!darkVars) {
darkVars = postcss.rule({ selector: ".dark" })
baseLayer.append(darkVars)
}
Object.entries(cssVars.dark).forEach(([key, value]) => {
const existingDecl = darkVars.nodes.find(
(node) => node.type === "decl" && node.prop === `--${key}`
)
if (existingDecl) {
existingDecl.replaceWith(
postcss.decl({
prop: `--${key}`,
value,
raws: { semicolon: true },
})
)
} else {
darkVars.append({
prop: `--${key}`,
value,
raws: { semicolon: true },
})
}
})
}
// Second pass: Add missing semicolons
// baseLayer.walkRules((rule) => {
// if (rule.selector === ":root" || rule.selector === ".dark") {
// rule.walkDecls((decl) => {
// decl.value = decl.value.replace(/;$/, "") + ";"
// })
// }
// })
},
}
}
const result = await postcss([insertCssVarsPlugin()]).process(input, {
const result = await postcss([
updateCssVarsPlugin(cssVars),
updateBaseLayerPlugin(),
]).process(input, {
from: undefined,
})
return result.css
}
function updateBaseLayerPlugin() {
return {
postcssPlugin: "update-base-layer",
Once(root: Root) {
const requiredRules = [
{ selector: "*", apply: "border-border" },
{ selector: "body", apply: "bg-background text-foreground" },
]
let baseLayer = root.nodes.find(
(node): node is AtRule =>
node.type === "atrule" &&
node.name === "layer" &&
node.params === "base" &&
requiredRules.every(({ selector, apply }) =>
node.nodes?.some(
(rule): rule is Rule =>
rule.type === "rule" &&
rule.selector === selector &&
rule.nodes.some(
(applyRule): applyRule is AtRule =>
applyRule.type === "atrule" &&
applyRule.name === "apply" &&
applyRule.params === apply
)
)
)
) as AtRule | undefined
if (!baseLayer) {
baseLayer = postcss.atRule({
name: "layer",
params: "base",
raws: { semicolon: true, between: " " },
})
root.append(baseLayer)
}
requiredRules.forEach(({ selector, apply }) => {
const existingRule = baseLayer.nodes?.find(
(node): node is Rule =>
node.type === "rule" && node.selector === selector
)
if (!existingRule) {
baseLayer.append(
postcss.rule({
selector,
nodes: [
postcss.atRule({
name: "apply",
params: apply,
}),
],
raws: { semicolon: true, between: " " },
})
)
}
})
},
}
}
function updateCssVarsPlugin(cssVars: z.infer<typeof registryCssVarsSchema>) {
return {
postcssPlugin: "update-css-vars",
Once(root: Root) {
let baseLayer = root.nodes.find(
(node) =>
node.type === "atrule" &&
node.name === "layer" &&
node.params === "base"
) as AtRule | undefined
if (!(baseLayer instanceof AtRule)) {
baseLayer = postcss.atRule({
name: "layer",
params: "base",
nodes: [],
raws: { semicolon: true },
})
root.append(baseLayer)
}
// Add variables for each key in cssVars
Object.entries(cssVars).forEach(([key, vars]) => {
const selector = key === "light" ? ":root" : `.${key}`
addOrUpdateVars(baseLayer, selector, vars)
})
},
}
}
// Function to add or update variables for a given selector
function addOrUpdateVars(
baseLayer: AtRule,
selector: string,
vars: Record<string, string>
) {
let ruleNode = baseLayer.nodes?.find(
(node): node is Rule => node.type === "rule" && node.selector === selector
)
if (!ruleNode) {
ruleNode = postcss.rule({ selector })
baseLayer.append(ruleNode)
}
Object.entries(vars).forEach(([key, value]) => {
const prop = `--${key}`
const newDecl = postcss.decl({
prop,
value,
raws: { semicolon: true },
})
const existingDecl = ruleNode.nodes.find(
(node): node is postcss.Declaration =>
node.type === "decl" && node.prop === prop
)
existingDecl ? existingDecl.replaceWith(newDecl) : ruleNode.append(newDecl)
})
}

View File

@@ -70,16 +70,11 @@ test("init config-full", async () => {
expect(mockMkdir).toHaveBeenNthCalledWith(
1,
expect.stringMatching(/src\/app$/),
expect.anything()
)
expect(mockMkdir).toHaveBeenNthCalledWith(
2,
expect.stringMatching(/src\/lib$/),
expect.anything()
)
expect(mockMkdir).toHaveBeenNthCalledWith(
3,
2,
expect.stringMatching(/src\/components$/),
expect.anything()
)
@@ -184,12 +179,6 @@ test("init config-partial", async () => {
)
expect(mockWriteFile).toHaveBeenNthCalledWith(
2,
expect.stringMatching(/src\/assets\/css\/tailwind.css$/),
expect.stringContaining(`@tailwind base`),
"utf8"
)
expect(mockWriteFile).toHaveBeenNthCalledWith(
3,
expect.stringMatching(/utils.ts$/),
expect.stringContaining(`import { type ClassValue, clsx } from "clsx"`),
"utf8"

View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -34,6 +34,14 @@ describe("transformTailwindCss", () => {
--background: black;
--foreground: white
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
"
`)
@@ -82,6 +90,74 @@ describe("transformTailwindCss", () => {
--foreground: 60 9.1% 97.8%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
"
`)
})
test("should not add the base layer if it is already present", async () => {
expect(
await transformTailwindCss(
`@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base{
:root{
--background: 210 40% 98%;
}
.dark{
--background: 222.2 84% 4.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
`,
{}
)
).toMatchInlineSnapshot(`
"@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base{
:root{
--background: 210 40% 98%;
}
.dark{
--background: 222.2 84% 4.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
"
`)
})