233 lines
6.2 KiB
TypeScript
233 lines
6.2 KiB
TypeScript
/* 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 (
|
|
<div css="text-center">
|
|
Hello, world!
|
|
</div>
|
|
);
|
|
}
|
|
`,
|
|
});
|
|
|
|
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 (
|
|
<div css="text-center">
|
|
Hello, world!
|
|
</div>
|
|
);
|
|
}
|
|
`,
|
|
});
|
|
|
|
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 (
|
|
<div className="text-center" css="text-center">
|
|
Hello, world!
|
|
</div>
|
|
);
|
|
}
|
|
`,
|
|
});
|
|
|
|
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 (
|
|
<div className={({ isEntering }) => isEntering ? "enter" : "exit"} css="text-center">
|
|
Hello, world!
|
|
</div>
|
|
);
|
|
}
|
|
`,
|
|
});
|
|
|
|
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 (
|
|
<div css="text-center2 m-0">
|
|
Hello, world!
|
|
</div>
|
|
);
|
|
}
|
|
`,
|
|
});
|
|
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: ' <div css="text-center2 m-0">',
|
|
});
|
|
}
|
|
});
|
|
|
|
it("supports custom jsxAttributeName", async () => {
|
|
const { files } = await compileESBuild({
|
|
clsx: "emotion",
|
|
jsxAttributeName: "tw",
|
|
expectFiles: 2,
|
|
javascript: /* tsx */ `
|
|
export function Hello() {
|
|
return (
|
|
<div tw="text-center">
|
|
Hello, world!
|
|
</div>
|
|
);
|
|
}
|
|
`,
|
|
});
|
|
|
|
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<TailwindPluginOptions, "compile"> & {
|
|
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<string, esbuild.OutputFile>, {
|
|
get: (_, ext: string) => findByExt(outputFiles!, ext),
|
|
}),
|
|
};
|
|
}
|