diff --git a/packages/jest/src/__tests__/matchers.test.tsx b/packages/jest/src/__tests__/matchers.test.tsx
new file mode 100644
index 000000000..de1182ca2
--- /dev/null
+++ b/packages/jest/src/__tests__/matchers.test.tsx
@@ -0,0 +1,57 @@
+import { render } from '@testing-library/react';
+import React from 'react';
+import '@compiled/css-in-js';
+
+it('should detect styles', () => {
+ const { getByText } = render(
+
+ hello world
+
+ );
+
+ expect(getByText('hello world')).toHaveCompiledCss('font-size', '12px');
+});
+
+it('should detect missing styles', () => {
+ const { getByText } = render(hello world
);
+
+ expect(getByText('hello world')).not.toHaveCompiledCss('color', 'blue');
+});
+
+it('should detect multiple styles', () => {
+ const { getByText } = render(hello world
);
+
+ expect(getByText('hello world')).toHaveCompiledCss({
+ fontSize: '12px',
+ color: 'blue',
+ });
+});
+
+it('should detect single missing styles', () => {
+ const { getByText } = render(hello world
);
+
+ expect(getByText('hello world')).not.toHaveCompiledCss({
+ zindex: '9999',
+ });
+});
+
+it('should detect multiple missing styles', () => {
+ const { getByText } = render(hello world
);
+
+ expect(getByText('hello world')).not.toHaveCompiledCss({
+ backgroundColor: 'yellow',
+ zindex: '9999',
+ });
+});
+
+it('should detect evaluated rule from array styles', () => {
+ const base = { fontSize: 12 };
+ const next = ` font-size: 15px; `;
+
+ const { getByText } = render(hello world
);
+ expect(getByText('hello world')).toHaveCompiledCss('font-size', '15px');
+ expect(getByText('hello world')).toHaveCompiledCss('font-size', '12px');
+});
diff --git a/packages/jest/src/matchers.tsx b/packages/jest/src/matchers.tsx
index 11c8abad4..cef0b40c5 100644
--- a/packages/jest/src/matchers.tsx
+++ b/packages/jest/src/matchers.tsx
@@ -1,7 +1,14 @@
-export const toHaveCompiledCss: jest.CustomMatcher = (
+const kebabCase = (str: string) =>
+ str
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
+ .replace(/\s+/g, '-')
+ .toLowerCase();
+
+export function toHaveCompiledCss(
+ this: jest.MatcherUtils,
element: HTMLElement,
...args: [{ [key: string]: string } | string, string]
-) => {
+): jest.CustomMatcherResult {
const [property, value] = args;
const properties = typeof property === 'string' ? { [property]: value } : property;
let styleElement = element.parentElement && element.parentElement.querySelector('style');
@@ -17,10 +24,6 @@ export const toHaveCompiledCss: jest.CustomMatcher = (
}
}
- const stylesToFind = Object.keys(properties).map(
- property => `${property}:${properties[property]}`
- );
-
if (!styleElement) {
return {
pass: false,
@@ -36,18 +39,34 @@ export const toHaveCompiledCss: jest.CustomMatcher = (
let css = styleElement.textContent || '';
if (styles && Object.keys(styles).length > 0) {
- Object.entries(styles).forEach(([key, value]: any) => {
+ Object.entries(styles).forEach(([key, value]: [string, any]) => {
// Replace all instances of var with the value.
// We split and join to replace all instances without needing to jump into a dynamic regex.
css = css.split(`var(${key})`).join(value);
});
}
+
+ const stylesToFind = Object.keys(properties).map(
+ property => `${kebabCase(property)}:${properties[property]}`
+ );
+ const foundStyles = stylesToFind.filter(styleToFind => css.includes(styleToFind));
const notFoundStyles = stylesToFind.filter(styleToFind => !css.includes(styleToFind));
+ const includedSelector = css.includes(`.${element.className}`);
- if (css.includes(`.${element.className}`) && notFoundStyles.length === 0) {
+ if (includedSelector && foundStyles.length > 0 && notFoundStyles.length === 0) {
return {
pass: true,
- message: () => '',
+ message: !this.isNot
+ ? () => ''
+ : () => `Found "${foundStyles.join(', ')}" on <${element.nodeName.toLowerCase()} ${styles &&
+ `style={${JSON.stringify(styles)}}`}> element.
+
+ Reconciled css (css variables replaced with actual values):
+ ${css}
+
+ Original css:
+ ${(styleElement && styleElement.textContent) || ''}
+ `,
};
}
@@ -58,11 +77,11 @@ export const toHaveCompiledCss: jest.CustomMatcher = (
)}" on <${element.nodeName.toLowerCase()} ${styles &&
`style={${JSON.stringify(styles)}}`}> element.
-Reconciled css (css variables were replaced with actual values):
+Reconciled css (css variables replaced with actual values):
${css}
Original css:
${(styleElement && styleElement.textContent) || ''}
`,
};
-};
+}