import { transform as transformCode } from '../../test-utils';
import type { TransformOptions } from '../../test-utils';
describe('styled object call expression', () => {
const transform = (code: string, opts: TransformOptions = {}) =>
transformCode(code, { snippet: true, ...opts });
it('only transforms @compiled/react usages', () => {
const actual = transform(`
import { styled as styled2 } from '@compiled/react';
import styled from 'styled-components';
const StyledComponent = styled.div({
color: 'blue',
});
const CompiledComponent = styled2.div({
color: 'blue',
});
`);
expect(actual).toMatchInlineSnapshot(`
"const _ = "._syaz13q2{color:blue}";
const StyledComponent = styled.div({
color: "blue",
});
const CompiledComponent = forwardRef(
({ as: C = "div", style: __cmpls, ...__cmplp }, __cmplr) => {
if (__cmplp.innerRef) {
throw new Error("Please use 'ref' instead of 'innerRef'.");
}
return (
{[_]}
);
}
);
"
`);
});
it('should respect the definition of pseudo element content ala emotion with double quotes', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const ListItem = styled.div({
':after': {
content: '""',
},
});
`);
expect(actual).toInclude(':after{content:""}');
});
it('should respect the definition of pseudo element content ala emotion with single quotes', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const ListItem = styled.div({
':after': {
content: "''",
},
});
`);
expect(actual).toInclude(":after{content:''}");
});
it('should respect the definition of pseudo element content ala styled components with no content', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const ListItem = styled.div({
':after': {
content: '',
},
});
`);
expect(actual).toInclude(':after{content:""}');
});
it('should respect the definition of pseudo element content ala styled components with content', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const ListItem = styled.div({
':after': {
content: '😎',
},
});
`);
expect(actual).toInclude(':after{content:"\\uD83D\\uDE0E"}');
});
it('should append "px" on numeric literals if missing', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const ListItem = styled.div({
fontSize: 12,
});
`);
expect(actual).toInclude('{font-size:12px}');
});
it('should reference property access expression', () => {
const actual = transform(`
import { styled } from '@compiled/react';
let color = { blue: 'red' };
color = {};
styled.div({
background: color.blue,
});
`);
expect(actual).toIncludeMultiple([
'{background:var(--_1mkyvve)}',
'"--_1mkyvve": ix(color.blue)',
]);
});
it('should not pass down invalid html attributes to the node when property has a suffix', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const ListItem = styled.div({
fontSize: props => \`\${props.textSize}px\`,
});
`);
expect(actual).toIncludeMultiple([
'{font-size:var(--_450x70)}',
'const { textSize, ...__cmpldp } = __cmplp;',
'"--_450x70": ix(`${__cmplp.textSize}px`)',
]);
});
it('should not pass down invalid html attributes to the node when property has a suffix when func in template literal', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const ListItem = styled.div({
fontSize: \`\${props => props.textSize}px\`,
});
`);
expect(actual).toIncludeMultiple([
'{font-size:var(--_8t6091)}',
'const { textSize, ...__cmpldp } = __cmplp;',
'"--_8t6091": ix(__cmplp.textSize, "px")',
]);
});
it('should transform object with simple values', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const ListItem = styled.div({
color: 'blue',
marginLeft: 0,
});
`);
expect(actual).toIncludeMultiple(['{color:blue}', '{margin-left:0}']);
});
it('should transform object with nested object into a selector', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const ListItem = styled.div({
':hover': {
color: 'blue',
marginLeft: 0,
},
});
`);
expect(actual).toIncludeMultiple([':hover{color:blue}', ':hover{margin-left:0}']);
});
it('should resolve identifier pointing to a call expression if it returns simple value', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const em = (str) => str;
const color = em('blue');
const ListItem = styled.div({
color,
});
`);
expect(actual).toInclude('{color:blue}');
});
it('should inline call if it returns simple value', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const em = (str) => str;
const ListItem = styled.div({
color: em('blue'),
});
`);
expect(actual).toInclude('{color:blue}');
});
it('should inline constant string literal', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const color = 'blue';
const ListItem = styled.div({
color,
});
`);
expect(actual).toInclude('{color:blue}');
});
it('should transform template object with prop reference', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const ListItem = styled.div({
color: props => props.color,
});
`);
expect(actual).toIncludeMultiple(['{color:var(--_xexnhp)}', '"--_xexnhp": ix(__cmplp.color)']);
});
it('should transform object spread from variable', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const h100 = { fontSize: '12px' };
const ListItem = styled.div({
...h100,
color: 'red',
});
`);
expect(actual).toIncludeMultiple(['{font-size:12px}', '{color:red}']);
});
it('should transform object with mutable identifier', () => {
const actual = transform(`
import { styled } from '@compiled/react';
let color = 'blue';
color = 'red';
const ListItem = styled.div({
color: color,
});
`);
expect(actual).toIncludeMultiple(['{color:var(--_1ylxx6h)}', '"--_1ylxx6h": ix(color)']);
});
it('should transform object with obj variable', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const hover = { color: 'red' };
const ListItem = styled.div({
fontSize: '20px',
':hover': hover,
});
`);
expect(actual).toIncludeMultiple(['{font-size:20px}', ':hover{color:red}']);
});
it('should transform object with no argument arrow function variable', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const mixin = () => ({ color: 'red' });
const ListItem = styled.div({
...mixin(),
});
`);
expect(actual).toInclude('{color:red}');
});
it('should transform object with no argument function variable', () => {
const actual = transform(`
import { styled } from '@compiled/react';
function mixin() {
return { color: 'red' };
}
const ListItem = styled.div({
...mixin(),
});
`);
expect(actual).toInclude('{color:red}');
});
it('should transform object with no argument functions', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const bgColor = 'blue';
const fontStyling = {
style: 'italic',
family: 'sans-serif',
};
const mixin1 = () => ({ color: 'red', backgroundColor: bgColor });
const mixin2 = function() { return { fontStyle: fontStyling.style } };
function mixin3() { return { fontFamily: fontStyling.family } };
const ListItem = styled.div({
color: 'blue',
':hover': mixin1(),
...mixin2(),
...mixin3(),
});
`);
expect(actual).toIncludeMultiple([
'{color:blue}',
'{font-style:italic}',
'{font-family:sans-serif}',
':hover{color:red}',
':hover{background-color:blue}',
]);
});
it('should transform object with no argument function properties belonging to a variable', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const bgColor = 'blue';
const fontSize = 12;
const fontStyling = {
weight: 500
};
const sizes = {
mixin1: () => \`1px solid \${bgColor}\`,
mixin2: () => ({ fontSize }),
mixin3: function() {return {fontWeight: fontStyling.weight};}
};
const ListItem = styled.div({
color: 'blue',
border: sizes.mixin1(),
...sizes.mixin2(),
...sizes.mixin3()
});
`);
expect(actual).toIncludeMultiple([
'{color:blue}',
'{border:1px solid blue}',
'{font-size:12px}',
'{font-weight:500}',
]);
});
it('should transform object with argument arrow function variable', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const color1 = 'black';
const mixin = ({ color1, color2: c }, color3, radius) => ({
color: color1,
backgroundColor: c,
borderColor: color3 ,
borderRadius: radius,
});
const color = { red: 'red' };
const greenColor = 'green';
const ListItem = styled.div({
...mixin({ color1: color.red, color2: 'blue' }, greenColor, 10)
});
`);
expect(actual).toIncludeMultiple([
'{color:red}',
'{background-color:blue}',
'{border-color:green}',
'{border-radius:10px}',
]);
});
it('should transform object with unresolved argument arrow function variable', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const radius = 10;
const mixin = (color1, radius, size, weight) => ({
color: color1,
borderRadius: radius,
fontSize: size,
fontWeight: weight
});
const ListItem = styled.div({
...mixin(props.color1, radius)
});
`);
expect(actual).toIncludeMultiple([
'{color:var(--_zo7lop)}',
'"--_zo7lop": ix(props.color1)',
'{border-radius:10px}',
'{font-weight:var(--_u6vle4)}',
'"--_u6vle4": ix()',
'{font-size:var(--_kre2x8)}',
'"--_kre2x8": ix()',
]);
});
it('should transform object with argument arrow function variable inside member expression', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const mixin = {
value: (color1, r, color2) => ({
color: color1,
borderRadius: r,
borderColor: color2,
})
}
const radius = 10;
const ListItem = styled.div({
...mixin.value(props.color1, radius, 'red')
});
`);
expect(actual).toIncludeMultiple([
'"--_zo7lop": ix(props.color1)',
'{border-radius:10px}',
'{border-color:red}',
]);
});
it('should transform function returning an object', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const color = 'red';
const mixin = () => ({ color });
const ListItem = styled.div({
color: mixin().color,
});
`);
expect(actual).toInclude('{color:red}');
});
it('should transform member expression referencing a function which returns an object', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const color = 'red';
const mixin = () => ({ color });
const colors = mixin();
const ListItem = styled.div({
color: colors.color,
});
`);
expect(actual).toInclude('{color:red}');
});
it('should handle destructuring in interpolation functions', () => {
const actual = transform(`
import { styled } from '@compiled/react';
import colors from 'colors';
export const BadgeSkeleton = styled.span({
width: ({ width }) => width ,
minWidth: ({ width: w }) => w,
maxWidth: (propz) => propz.width,
});
`);
expect(actual).toInclude('ix(__cmplp.width)');
expect(actual).not.toIncludeMultiple(['ix(propz.width)', 'ix(w)']);
});
it('should handle member expression pointing to a CSS call expression', () => {
const actual = transform(`
import { styled, css } from '@compiled/react';
const styles = {
default: css({
color: 'black',
fontWeight: 400
}),
success: css({
color: 'green',
fontWeight: 600
}),
fail: css({
color: 'red',
fontWeight: 600
}),
bg: css({
background: 'white'
}),
};
const Component = styled.div({
...styles.default,
...styles.bg
});
`);
expect(actual).toIncludeMultiple(['color:black', 'font-weight:400', 'background-color:white']);
});
it('should transform variable in a nested template literal', () => {
// this is the output of applying @atlaskit/tokens babel plugin
// (as of v1.0.0) to some code similar to this:
// styled.div({
// backgroundColor: token('some.token', color),
// })
const actual = transform(`
import { styled } from '@compiled/react';
const color = 'red';
const Component = styled.div({
backgroundColor: \`var(--my-variable, \${color})\`,
});
`);
expect(actual).toInclude('{background-color:var(--my-variable,red)}');
});
it('should transform variable in a heavily nested template literal', () => {
// corresponds to
// styled.div({
// boxShadow: `0 8px ${token('some.token', color)}`
// })
const actual = transform(`
import { styled } from '@compiled/react';
const color = 'red';
const Component = styled.div({
boxShadow: \`0 8px \${\`var(--my-variable, \${color})\`}\`,
});
`);
expect(actual).toInclude('{box-shadow:0 8px var(--my-variable,red)}');
});
it('should transform variables within nested template literals that are all in an interpolation function', () => {
// corresponds to
// styled.div<{ isActive: boolean }>({
// boxShadow: (props) =>
// props.isActive
// ? `0 ${size}px ${token('some.token', color)}`
// : token('some.other.token', color2),
// })
const enableTypescript: TransformOptions = {
parserBabelPlugins: ['typescript', 'jsx'],
};
const actual = transform(
`
import { styled } from '@compiled/react';
const color = 'red';
const color2 = 'blue';
const size = 5;
const Component = styled.div<{ isActive: boolean }>({
boxShadow: (props) =>
props.isActive
? \`0 \${size}px \${\`var(--my-variable, \${color})\`}\`
: \`var(--my-other-variable, \${color2})\`,
});
`,
enableTypescript
);
// We currently don't statically evaluate color, color2, or size here
expect(actual).toMatchInlineSnapshot(`
"const _2 = "._16qs1j0n{box-shadow:var(--my-other-variable,blue)}";
const _ = "._16qslfrr{box-shadow:0 5px var(--my-variable,red)}";
const color = "red";
const color2 = "blue";
const size = 5;
const Component = forwardRef(
({ as: C = "div", style: __cmpls, ...__cmplp }, __cmplr) => {
if (__cmplp.innerRef) {
throw new Error("Please use 'ref' instead of 'innerRef'.");
}
const { isActive, ...__cmpldp } = __cmplp;
return (
{[_, _2]}
);
}
);
"
`);
});
it('should refuse to expand shorthand property when value is unknown at build time (arrow function)', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const Container = styled.div({
padding: ({ customPadding }) => customPadding,
});
`);
expect(actual).toMatchInlineSnapshot(`
"const _ = "._1yt414tu{padding:var(--_1hhnq9y)}";
const Container = forwardRef(
({ as: C = "div", style: __cmpls, ...__cmplp }, __cmplr) => {
if (__cmplp.innerRef) {
throw new Error("Please use 'ref' instead of 'innerRef'.");
}
const { customPadding, ...__cmpldp } = __cmplp;
return (
{[_]}
);
}
);
"
`);
});
it('should refuse to expand shorthand property when value is unknown at build time (ternary expression)', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const Container = styled.div({
padding: ({ morePadding }) => morePadding ? morePadding : '4px 8px',
});
`);
expect(actual).toMatchInlineSnapshot(`
"const _5 = "._19bvftgi{padding-left:8px}";
const _4 = "._n3td1y44{padding-bottom:4px}";
const _3 = "._u5f3ftgi{padding-right:8px}";
const _2 = "._ca0q1y44{padding-top:4px}";
const _ = "._1yt41v0o{padding:var(--_1dm0vu2)}";
const Container = forwardRef(
({ as: C = "div", style: __cmpls, ...__cmplp }, __cmplr) => {
if (__cmplp.innerRef) {
throw new Error("Please use 'ref' instead of 'innerRef'.");
}
const { morePadding, ...__cmpldp } = __cmplp;
return (
{[_, _2, _3, _4, _5]}
);
}
);
"
`);
});
});