react-email
Version:
A live preview of your emails right in your browser.
125 lines (114 loc) • 3.85 kB
text/typescript
import { parse, type StyleSheet } from 'css-tree';
import { getCustomProperties } from './get-custom-properties.js';
import { makeInlineStylesFor } from './make-inline-styles-for.js';
describe('makeInlineStylesFor()', async () => {
it('works in simple use case', () => {
const tailwindStyles = parse(`
.bg-red-500 { background-color: #f56565; }
.w-full { width: 100%; }
`) as StyleSheet;
expect(
makeInlineStylesFor(
tailwindStyles.children.toArray(),
getCustomProperties(tailwindStyles),
),
).toMatchInlineSnapshot(`
{
"backgroundColor": "#f56565",
"width": "100%",
}
`);
});
it('does basic local variable resolution', () => {
const tailwindStyles = parse(`
.btn {
--btn-bg: #3490dc;
--btn-text: #fff;
background-color: var(--btn-bg);
color: var(--btn-text);
padding: 0.5rem 1rem;
border-radius: 0.25rem;
}
`) as StyleSheet;
expect(
makeInlineStylesFor(
tailwindStyles.children.toArray(),
getCustomProperties(tailwindStyles),
),
).toMatchInlineSnapshot(`
{
"backgroundColor": "#3490dc",
"borderRadius": "0.25rem",
"color": "#fff",
"padding": "0.5rem 1rem",
}
`);
});
it('strips Tailwind v4 variant-stacking var() refs with empty fallbacks', () => {
// Tailwind v4 compiles `tabular-nums` to a font-variant-numeric value
// where every optional variant slot is represented by an unresolved
// var(--tw-..., ) with an empty fallback. Email clients do not support
// CSS custom properties reliably, so these must collapse at inline time
// (per CSS spec, an empty fallback resolves to empty string).
const tailwindStyles = parse(`
.tabular-nums {
font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) tabular-nums var(--tw-numeric-fraction,);
}
`) as StyleSheet;
expect(
makeInlineStylesFor(
tailwindStyles.children.toArray(),
getCustomProperties(tailwindStyles),
),
).toMatchInlineSnapshot(`
{
"fontVariantNumeric": "tabular-nums",
}
`);
});
it('preserves user-authored empty-fallback var() refs (non --tw- prefix)', () => {
// The collapse is scoped to Tailwind's --tw-* variant-stacking idiom.
// A user-authored var(--my-color,) with an empty fallback must pass
// through unchanged even though it syntactically matches the idiom --
// the user opted into that semantic and the render target may define
// --my-color at a higher scope.
const userStyles = parse(`
.thing {
color: var(--my-color,);
background: var(--brand,) var(--tw-custom,);
}
`) as StyleSheet;
expect(
makeInlineStylesFor(
userStyles.children.toArray(),
getCustomProperties(userStyles),
),
).toMatchInlineSnapshot(`
{
"background": "var(--brand,)",
"color": "var(--my-color,)",
}
`);
});
it('collapses outer --tw-* var() that becomes empty after inner --tw-* var() collapses', () => {
// Regression test for cubic-dev-ai P2 review on PR #3359:
// pre-order traversal misses outer var(--tw-X, var(--tw-Y,)) because the
// outer's fallback only becomes empty AFTER the inner collapses. Post-order
// traversal fixes this.
const tailwindStyles = parse(`
.nested {
font-variant-numeric: var(--tw-outer, var(--tw-inner,)) tabular-nums;
}
`) as StyleSheet;
expect(
makeInlineStylesFor(
tailwindStyles.children.toArray(),
getCustomProperties(tailwindStyles),
),
).toMatchInlineSnapshot(`
{
"fontVariantNumeric": "tabular-nums",
}
`);
});
});