/* eslint-disable unicorn/string-content */ import { promises as fs } from "node:fs"; import { resolve } from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import * as esbuild from "esbuild"; import dedent from "dedent"; import { name } from "../package.json" with { type: "json" }; import { type TailwindPluginOptions, babelPlugin, createPostCSS, getClassName, getTailwindPlugins, } from "./index"; const folder = resolve(import.meta.dirname, "temp"); describe("babel-tailwind", () => { beforeEach(async () => { await fs.mkdir(folder, { recursive: true }); }); afterEach(async () => { await fs.rm(folder, { recursive: true, force: true }); }); it("supports ESBuild", async () => { const { files } = await compileESBuild({ clsx: "emotion", expectFiles: 2, javascript: /* tsx */ ` export function Hello() { return (
Hello, world!
); } `, }); const clsName = getClassName("text-center"); expect(files.js.text).toContain(`className: "${clsName}"`); expect(files.css.text).toMatch(`.${clsName} {\n text-align: center;\n}`); }); it("does not remove the attribute if `preserveAttribute` is true", async () => { const { files } = await compileESBuild({ clsx: "emotion", jsxAttributeAction: "preserve", expectFiles: 2, javascript: /* tsx */ ` export function Hello() { return (
Hello, world!
); } `, }); expect(files.js.text).toContain(`css: "text-center"`); }); describe('merges with existing "className" attribute', () => { it("string literal", async () => { const { files } = await compileESBuild({ clsx: "emotion", expectFiles: 2, javascript: /* tsx */ ` export function Hello() { return (
Hello, world!
); } `, }); const clsName = getClassName("text-center"); expect(files.js.text).toContain(`className: "text-center ${clsName}"`); expect(files.css.text).toMatch(`.${clsName} {\n text-align: center;\n}`); }); it("existing function", async () => { const { files } = await compileESBuild({ clsx: "emotion", expectFiles: 2, javascript: /* tsx */ ` export function Hello() { return (
isEntering ? "enter" : "exit"} css="text-center"> Hello, world!
); } `, }); const clsName = getClassName("text-center"); expect(files.js.text).toContain( `className: ({\n isEntering\n }) => _cx(isEntering ? "enter" : "exit", "${clsName}")` ); expect(files.css.text).toMatch(`.${clsName} {\n text-align: center;\n}`); }); }); it("reports errors with correct position", async () => { try { await compileESBuild({ clsx: "emotion", jsxAttributeAction: "preserve", esbuild: { logLevel: "silent", }, javascript: /* tsx */ ` export function Hello() { return (
Hello, world!
); } `, }); throw new Error("Expected an error"); } catch (e) { expect(e.errors).toHaveLength(1); const [error] = e.errors; expect(error.location).toMatchObject({ column: 14, length: 12, line: 3, lineText: '
', }); } }); it("supports custom jsxAttributeName", async () => { const { files } = await compileESBuild({ clsx: "emotion", jsxAttributeName: "tw", expectFiles: 2, javascript: /* tsx */ ` export function Hello() { return (
Hello, world!
); } `, }); const clsName = getClassName("text-center"); expect(files.js.text).toContain(`className: "${clsName}"`); expect(files.css.text).toMatch(`.${clsName} {\n text-align: center;\n}`); }); it("supports importing tailwind/base", async () => { const postcss = createPostCSS({ tailwindConfig: {}, postCSSPlugins: [], }); const base = await postcss("@tailwind base;"); const { files } = await compileESBuild({ clsx: "emotion", expectFiles: 2, javascript: /* tsx */ ` import "${name}/base"; `, }); expect(files.js.text).toBe(""); expect(minCSS(files.css.text)).toContain(minCSS(base)); }); }); async function write(path: string, content: string) { const resolved = resolve(folder, path); await fs.writeFile(resolved, content); return resolved; } const minCSS = (text: string) => esbuild.transformSync(text, { minify: true, loader: "css" }).code; const findByExt = (outputFiles: esbuild.OutputFile[], ext: string) => outputFiles.find(file => file.path.endsWith(ext))!; async function compileESBuild({ javascript, esbuild: esbuildOptions, expectFiles, ...options }: Omit & { esbuild?: esbuild.BuildOptions; javascript: string; expectFiles?: number; }) { const tailwind = getTailwindPlugins({ tailwindConfig: {}, ...options, }); const result = await esbuild.build({ bundle: true, write: false, external: ["react/jsx-runtime", "@emotion/css", "clsx"], outdir: "dist", format: "esm", entryPoints: [await write("index.tsx", dedent(javascript))], plugins: [babelPlugin({ plugins: [tailwind.babel()] }), tailwind.esbuild()], ...esbuildOptions, }); const { errors, warnings, outputFiles } = result; expect(errors).toHaveLength(0); expect(warnings).toHaveLength(0); if (expectFiles != null) { expect(outputFiles).toHaveLength(expectFiles); } return { outputFiles: outputFiles!, files: new Proxy({} as Record, { get: (_, ext: string) => findByExt(outputFiles!, ext), }), }; }