import { writeFileSync } from 'fs';
import { transform } from './transform';
// Mock out FS to avoid writing to disk
// We aren't processing the result anyway, so no need for specifying the response
jest.mock('fs');
/** Returns the CSS content written to app.compiled.css by the last transform call. */
const getExtractedCss = (): string => {
// writeFileSync(filePath, cssContent) — find the call that wrote to app.compiled.css
const match = (writeFileSync as jest.Mock).mock.calls.find(([filePath]) =>
(filePath as string).includes('app.compiled.css')
);
const [, cssContent] = match ?? [];
return cssContent as string;
};
describe('babel-plugin-strip-runtime with stylesheet extraction (extractStylesToDirectory)', () => {
describe('with the classic runtime', () => {
const runtime = 'classic';
describe('without JSX pragma', () => {
const code = `
import '@compiled/react';
const Component = () => (
hello world
);
`;
it('adds styles to directory', () => {
const actual = transform(code, {
run: 'both',
runtime,
extractStylesToDirectory: { source: 'src/', dest: 'dist/' },
});
expect(actual).toMatchInlineSnapshot(`
"/* app.tsx generated by @compiled/babel-plugin v0.0.0 */
import './app.compiled.css';
import * as React from 'react';
import { ax, ix } from '@compiled/react/runtime';
const Component = () =>
/*#__PURE__*/ React.createElement(
'div',
{
className: ax(['_4ya3eErjyG _1UtDYzynoA']),
},
'hello world'
);
"
`);
expect(writeFileSync).toBeCalledWith(
expect.stringContaining('app.compiled.css'),
'._1UtDYzynoA{color:blue}\n._4ya3eErjyG{font-size:12px}'
);
});
it('error when source directory is not found', () => {
expect(() =>
transform(code, {
run: 'both',
runtime,
extractStylesToDirectory: { source: 'not-existing-src/', dest: 'dist/' },
})
).toThrowWithMessage(
Error,
`/base/src/app.tsx: Source directory 'not-existing-src/' was not found relative to source file ('../src/app.tsx')`
);
});
});
describe('with JSX pragma', () => {
it('extracts styles into app.compiled.css', () => {
const codeWithPragma = `
/** @jsx myJsx */
import { css, jsx as myJsx } from '@compiled/react';
const Component = () => (
hello world 2
);
const Component2 = () => (
hello world 2
);
`;
const actual = transform(codeWithPragma, {
run: 'both',
runtime: 'classic',
extractStylesToDirectory: { source: 'src/', dest: 'dist/' },
});
expect(actual).toMatchInlineSnapshot(`
"/* app.tsx generated by @compiled/babel-plugin v0.0.0 */
import './app.compiled.css';
import * as React from 'react';
import { ax, ix } from '@compiled/react/runtime';
const Component = () =>
/*#__PURE__*/ React.createElement(
'div',
{
className: ax(['_4ya3eErjyG _1UtDYzynoA']),
},
'hello world 2'
);
const Component2 = () =>
/*#__PURE__*/ React.createElement(
'div',
{
className: ax(['_4ya3eErjyG _1UtDYzy8mA']),
},
'hello world 2'
);
"
`);
});
});
});
describe('with cssMapScoped (non-atomic)', () => {
it('extracts atomic cssMap styles into app.compiled.css', () => {
(writeFileSync as jest.Mock).mockClear();
const code = `
/** @jsxImportSource @compiled/react */
import { cssMap } from '@compiled/react';
const styles = cssMap({
panel: { color: 'blue', padding: '8px' },
danger: { color: 'red' },
});
const Component = ({ isDanger }) => (
);
`;
const actual = transform(code, {
run: 'both',
runtime: 'automatic',
extractStylesToDirectory: { source: 'src/', dest: 'dist/' },
});
// JS output: atomic _xxx class names
expect(actual).toMatchInlineSnapshot(`
"/* app.tsx generated by @compiled/babel-plugin v0.0.0 */
import './app.compiled.css';
import { ax, ix } from '@compiled/react/runtime';
import { jsxs as _jsxs, jsx as _jsx } from 'react/jsx-runtime';
const styles = {
panel: '_0Of8r2Jg58 _1ZnuxbJg58 _1wydGWJg58 _2Zuz6QJg58 _1UtDYzynoA',
danger: '_1UtDYzGowl',
};
const Component = ({ isDanger }) =>
/*#__PURE__*/ _jsx('div', {
className: ax([styles.panel, isDanger && styles.danger]),
});
"
`);
// CSS output: atomic rules lexically sorted in the compiled.css file
expect(getExtractedCss()).toMatchInlineSnapshot(`
"._0Of8r2Jg58{padding-top:8px}
._1UtDYzGowl{color:red}
._1UtDYzynoA{color:blue}
._1ZnuxbJg58{padding-right:8px}
._1wydGWJg58{padding-bottom:8px}
._2Zuz6QJg58{padding-left:8px}"
`);
});
it('extracts mixed atomic cssMap and non-atomic cssMapScoped styles into app.compiled.css', () => {
const code = `
/** @jsxImportSource @compiled/react */
import { cssMap, cssMapScoped } from '@compiled/react';
const atomicStyles = cssMap({
base: { color: 'blue' },
});
const scopedStyles = cssMapScoped({
panel: { '.panel': { padding: '8px' } },
});
const Component = ({ variant }) => (
);
`;
const actual = transform(code, {
run: 'both',
runtime: 'automatic',
extractStylesToDirectory: { source: 'src/', dest: 'dist/' },
});
// JS output: both _xxx atomic and cc- non-atomic class names
expect(actual).toMatchInlineSnapshot(`
"/* app.tsx generated by @compiled/babel-plugin v0.0.0 */
import './app.compiled.css';
import { ax, ix } from '@compiled/react/runtime';
import { jsxs as _jsxs, jsx as _jsx } from 'react/jsx-runtime';
const atomicStyles = {
base: '_1UtDYzynoA',
};
const scopedStyles = {
panel: 'cc-1m0qvev',
};
const Component = ({ variant }) =>
/*#__PURE__*/ _jsx('div', {
className: ax([atomicStyles.base, scopedStyles.panel]),
});
"
`);
// CSS output: both atomic and cc- scoped rules
expect(writeFileSync).toBeCalledWith(
expect.stringContaining('app.compiled.css'),
expect.stringContaining('._')
);
expect(writeFileSync).toBeCalledWith(
expect.stringContaining('app.compiled.css'),
expect.stringContaining('.cc-')
);
});
it('preserves non-atomic rule source order in extracted CSS', () => {
(writeFileSync as jest.Mock).mockClear();
// Use variable names that produce hashes where the override (zzOverride)
// lexically sorts BEFORE the base (aaBase) — proving that source order
// is preserved even when lexical sort would reorder them.
const code = `
/** @jsxImportSource @compiled/react */
import { cssMapScoped } from '@compiled/react';
const aaBase = cssMapScoped({
default: { '.editor .panel': { backgroundColor: 'gray' } },
});
const zzOverride = cssMapScoped({
default: { '.editor .panel': { backgroundColor: 'pink' } },
});
const Component = () => (
);
`;
transform(code, {
run: 'both',
runtime: 'automatic',
extractStylesToDirectory: { source: 'src/', dest: 'dist/' },
});
// Snapshot the extracted CSS to lock down rule ordering.
// baseStyles (gray) must appear BEFORE overrideStyles (pink).
expect(getExtractedCss()).toMatchInlineSnapshot(`
".cc-z3r7a0 .editor .panel{background-color:gray}
.cc-qr51l7 .editor .panel{background-color:pink}"
`);
});
it('extracts cc- scoped styles into app.compiled.css', () => {
const code = `
/** @jsxImportSource @compiled/react */
import { cssMapScoped } from '@compiled/react';
const styles = cssMapScoped({
panel: { '.panel': { color: 'blue', padding: '8px' } },
danger: { '.panel': { color: 'red' } },
});
const Component = ({ isDanger }) => (
);
`;
const actual = transform(code, {
run: 'both',
runtime: 'automatic',
extractStylesToDirectory: { source: 'src/', dest: 'dist/' },
});
// JS output: cc- class names, not atomic _xxx classes
expect(actual).toMatchInlineSnapshot(`
"/* app.tsx generated by @compiled/babel-plugin v0.0.0 */
import './app.compiled.css';
import { ax, ix } from '@compiled/react/runtime';
import { jsxs as _jsxs, jsx as _jsx } from 'react/jsx-runtime';
const styles = {
panel: 'cc-16etyda',
danger: 'cc-1fdnvw3',
};
const Component = ({ isDanger }) =>
/*#__PURE__*/ _jsx('div', {
className: ax([styles.panel, isDanger && styles.danger]),
});
"
`);
// CSS output: cc- scoped rules written to the compiled.css file
expect(writeFileSync).toBeCalledWith(
expect.stringContaining('app.compiled.css'),
expect.stringContaining('.cc-')
);
});
});
describe('with the automatic runtime', () => {
describe('with JSX pragma', () => {
it('extracts styles into app.compiled.css', () => {
const codeWithPragma = `
/** @jsxImportSource @compiled/react */
import { css } from '@compiled/react';
const Component = () => (
hello world 2
);
const Component2 = () => (
hello world 2
);
`;
const actual = transform(codeWithPragma, {
run: 'both',
runtime: 'automatic',
extractStylesToDirectory: { source: 'src/', dest: 'dist/' },
});
expect(actual).toMatchInlineSnapshot(`
"/* app.tsx generated by @compiled/babel-plugin v0.0.0 */
import './app.compiled.css';
import { ax, ix } from '@compiled/react/runtime';
import { jsxs as _jsxs, jsx as _jsx } from 'react/jsx-runtime';
const Component = () =>
/*#__PURE__*/ _jsx('div', {
className: ax(['_4ya3eErjyG _1UtDYzynoA']),
children: 'hello world 2',
});
const Component2 = () =>
/*#__PURE__*/ _jsx('div', {
className: ax(['_4ya3eErjyG _1UtDYzy8mA']),
children: 'hello world 2',
});
"
`);
});
});
});
});