import { render } from '@testing-library/react';
import React from 'react';
import type { ComponentType } from 'react';
import StyleWithContainer from '../style';
import { StyleContainerProvider } from '../style-container';
import type * as StyleContainerModule from '../style-container';
jest.mock('../is-server-environment', () => ({
isServerEnvironment: () => false,
}));
describe('', () => {
let consoleErrorSpy: jest.SpyInstance;
beforeEach(() => {
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
});
afterEach(() => {
consoleErrorSpy.mockRestore();
document.head.innerHTML = '';
});
// We want to isolate the test to correctly mimic the environment being loaded in once
const createIsolatedTest = (
callback: (Style: ComponentType<{ children: string[]; nonce?: string }>) => void
) => {
jest.isolateModules(() => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Style = require('../style');
callback(Style.default);
});
};
it('should render nothing on the client', () => {
createIsolatedTest((Style) => {
const { baseElement } = render();
expect(baseElement.getElementsByTagName('style')).toHaveLength(0);
expect(console.error).not.toHaveBeenCalled();
});
});
it('should add style to the head on the client', () => {
createIsolatedTest((Style) => {
render();
expect(document.head.innerHTML).toInclude('');
expect(console.error).not.toHaveBeenCalled();
});
});
it('should only add one style if it was already added', () => {
createIsolatedTest((Style) => {
render();
render();
expect(document.head.innerHTML).toIncludeRepeated('', 1);
expect(console.error).not.toHaveBeenCalled();
});
});
it('should noop in prod', () => {
createIsolatedTest((Style) => {
process.env.NODE_ENV = 'production';
render();
expect(console.error).not.toHaveBeenCalled();
});
});
it('should warn in dev when using a dangerous pseudo selector', () => {
createIsolatedTest((Style) => {
process.env.NODE_ENV = 'development';
render();
expect(console.error).toHaveBeenCalledTimes(1);
});
});
it('should warn in dev only once', () => {
createIsolatedTest((Style) => {
process.env.NODE_ENV = 'development';
render();
render();
expect(console.error).toHaveBeenCalledTimes(1);
expect(console.error).toHaveBeenCalledWith(
expect.stringMatching('Selectors ":first-child, :nth-child" are dangerous to use')
);
});
});
it('should render style tags in buckets', () => {
createIsolatedTest((Style) => {
render(
);
expect(document.head.innerHTML.split('').join('\n')).toMatchInlineSnapshot(`
"
"
`);
expect(console.error).not.toHaveBeenCalled();
});
});
it('should render shorthands in buckets', () => {
// Our buckets don't actually support mixing pseudo-selectors with shorthand
// properties, so the pseudo-selector buckets don't have correct shorthand
// property order...
createIsolatedTest((Style) => {
render(
);
expect(document.head.innerHTML.split('').join('\n')).toMatchInlineSnapshot(`
"
"
`);
expect(console.error).not.toHaveBeenCalled();
});
});
it('should update styles', () => {
createIsolatedTest((Style) => {
const { rerender } = render();
rerender();
expect(document.head.innerHTML).toInclude('.second-render { display: block; }');
expect(console.error).not.toHaveBeenCalled();
});
});
describe('StyleContainerProvider', () => {
let container: HTMLDivElement;
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
document.body.removeChild(container);
});
it('should insert styles into the provided container instead of document.head', () => {
render(
{[`.a { color: red; }`]}
);
expect(container.innerHTML).toInclude('.a { color: red; }');
expect(document.head.innerHTML).not.toInclude('.a { color: red; }');
expect(console.error).not.toHaveBeenCalled();
});
it('should maintain bucket ordering within the container', () => {
render(
{[
`._a1234567:hover{ color: red; }`,
`._b1234567:active{ color: blue; }`,
`._c1234567{ display: block; }`,
`@media (max-width: 800px){ ._d1234567{ color: yellow; } }`,
]}
);
expect(container.innerHTML.split('').join('\n')).toMatchInlineSnapshot(`
"
"
`);
expect(console.error).not.toHaveBeenCalled();
});
it('should not insert duplicate styles into the container', () => {
render(
{[`.b { color: blue; }`]}
{[`.b { color: blue; }`]}
);
expect(container.innerHTML).toIncludeRepeated('.b { color: blue; }', 1);
expect(console.error).not.toHaveBeenCalled();
});
it('should track container and document.head caches independently using cacheKey', () => {
// Render the same style into both the main document and a container.
// Each should receive its own copy since they are independent targets.
render({[`.c { color: green; }`]});
render(
{[`.c { color: green; }`]}
);
expect(document.head.innerHTML).toInclude('.c { color: green; }');
expect(container.innerHTML).toInclude('.c { color: green; }');
expect(console.error).not.toHaveBeenCalled();
});
it('should track two containers independently using different cacheKeys', () => {
const container2 = document.createElement('div');
document.body.appendChild(container2);
render(
{[`.d { color: pink; }`]}
);
render(
{[`.d { color: pink; }`]}
);
expect(container.innerHTML).toInclude('.d { color: pink; }');
expect(container2.innerHTML).toInclude('.d { color: pink; }');
document.body.removeChild(container2);
expect(console.error).not.toHaveBeenCalled();
});
it('should forward nonce to style elements in the container', () => {
render(
{[`.e { color: orange; }`]}
);
expect(container.innerHTML).toInclude('nonce="abc123"');
expect(console.error).not.toHaveBeenCalled();
});
it('should warn in dev when used in a server environment', () => {
jest.resetModules();
jest.doMock('../is-server-environment', () => ({
isServerEnvironment: () => true,
}));
// Re-require to pick up the new mock
const { StyleContainerProvider: ServerStyleContainerProvider } =
jest.requireActual('../style-container');
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
const { container: renderContainer } = render(
);
expect(warnSpy).toHaveBeenCalledWith(
expect.stringContaining(
'@compiled/react: StyleContainerProvider has no effect in server environments.'
)
);
// Children should still be rendered
expect(renderContainer.querySelector('div')).not.toBeNull();
warnSpy.mockRestore();
jest.resetModules();
});
});
});