/** * @jest-environment node */ import React from 'react' import { renderToString, renderToNodeStream } from 'react-dom/server' import ServerStyleSheet from '../models/ServerStyleSheet' import { resetStyled } from './utils' import _injectGlobal from '../constructors/injectGlobal' import _keyframes from '../constructors/keyframes' import stringifyRules from '../utils/stringifyRules' import css from '../constructors/css' jest.mock('../utils/nonce') const injectGlobal = _injectGlobal(stringifyRules, css) let index = 0 const keyframes = _keyframes(() => `keyframe_${index++}`, stringifyRules, css) let styled describe('ssr', () => { beforeEach(() => { // eslint-disable-next-line require('../utils/nonce').mockReset() styled = resetStyled(true) }) afterEach(() => { process.env.NODE_ENV = 'test' }) it('should extract the CSS in a simple case', () => { const Heading = styled.h1` color: red; ` const sheet = new ServerStyleSheet() const html = renderToString( sheet.collectStyles(Hello SSR!) ) const css = sheet.getStyleTags() expect(html).toMatchSnapshot() expect(css).toMatchSnapshot() }) it('should extract both global and local CSS', () => { injectGlobal` body { background: papayawhip; } ` const Heading = styled.h1` color: red; ` const sheet = new ServerStyleSheet() const html = renderToString( sheet.collectStyles(Hello SSR!) ) const css = sheet.getStyleTags() expect(html).toMatchSnapshot() expect(css).toMatchSnapshot() }) it('should not spill ServerStyleSheets into each other', () => { const A = styled.h1` color: red; ` const B = styled.h1` color: green; ` const sheetA = new ServerStyleSheet() renderToString(sheetA.collectStyles()) const cssA = sheetA.getStyleTags() const sheetB = new ServerStyleSheet() renderToString(sheetB.collectStyles()) const cssB = sheetB.getStyleTags() expect(cssA).toContain('red') expect(cssA).not.toContain('green') expect(cssB).not.toContain('red') expect(cssB).toContain('green') }) it('should add a nonce to the stylesheet if webpack nonce is detected in the global scope', () => { // eslint-disable-next-line require('../utils/nonce').mockImplementation(() => 'foo') injectGlobal` body { background: papayawhip; } ` const Heading = styled.h1` color: red; ` const sheet = new ServerStyleSheet() const html = renderToString( sheet.collectStyles(Hello SSR!) ) const css = sheet.getStyleTags() expect(html).toMatchSnapshot() expect(css).toMatchSnapshot() }) it('should render CSS in the order the components were defined, not rendered', () => { const ONE = styled.h1.withConfig({ componentId: 'ONE' })` color: red; ` const TWO = styled.h2.withConfig({ componentId: 'TWO' })` color: blue; ` const sheet = new ServerStyleSheet() const html = renderToString( sheet.collectStyles(
) ) const css = sheet.getStyleTags() expect(html).toMatchSnapshot() expect(css).toMatchSnapshot() }) it('should share global styles but keep renders separate', () => { injectGlobal` body { background: papayawhip; } ` const PageOne = styled.h1.withConfig({ componentId: 'PageOne' })` color: red; ` const PageTwo = styled.h2.withConfig({ componentId: 'PageTwo' })` color: blue; ` const sheetOne = new ServerStyleSheet() const htmlOne = renderToString( sheetOne.collectStyles(Camera One!) ) const cssOne = sheetOne.getStyleTags() const sheetTwo = new ServerStyleSheet() const htmlTwo = renderToString( sheetTwo.collectStyles(Camera Two!) ) const cssTwo = sheetTwo.getStyleTags() expect(htmlOne).toMatchSnapshot() expect(cssOne).toMatchSnapshot() expect(htmlTwo).toMatchSnapshot() expect(cssTwo).toMatchSnapshot() }) it('should allow global styles to be injected during rendering', () => { injectGlobal`html::before { content: 'Before both renders'; }` const PageOne = styled.h1.withConfig({ componentId: 'PageOne' })` color: red; ` const PageTwo = styled.h2.withConfig({ componentId: 'PageTwo' })` color: blue; ` const sheetOne = new ServerStyleSheet() const htmlOne = renderToString( sheetOne.collectStyles(Camera One!) ) injectGlobal`html::before { content: 'During first render'; }` const cssOne = sheetOne.getStyleTags() injectGlobal`html::before { content: 'Between renders'; }` const sheetTwo = new ServerStyleSheet() injectGlobal`html::before { content: 'During second render'; }` const htmlTwo = renderToString( sheetTwo.collectStyles(Camera Two!) ) const cssTwo = sheetTwo.getStyleTags() injectGlobal`html::before { content: 'After both renders'; }` expect(htmlOne).toMatchSnapshot() expect(cssOne).toMatchSnapshot() expect(htmlTwo).toMatchSnapshot() expect(cssTwo).toMatchSnapshot() }) it('should dispatch global styles to each ServerStyleSheet', () => { injectGlobal` body { background: papayawhip; } ` const Header = styled.h1.withConfig({ componentId: 'Header' })` animation: ${props => props.animation} 1s both; ` const sheet = new ServerStyleSheet() const html = renderToString( sheet.collectStyles(
) ) const css = sheet.getStyleTags() expect(html).toMatchSnapshot() expect(css).toMatchSnapshot() }) it('should return a generated React style element', () => { injectGlobal` body { background: papayawhip; } ` const Heading = styled.h1` color: red; ` const sheet = new ServerStyleSheet() const html = renderToString( sheet.collectStyles(Hello SSR!) ) const elements = sheet.getStyleElement() expect(elements).toHaveLength(1) /* I know this looks pointless, but apparently I have the feeling we'll need this */ expect(elements[0].props.dangerouslySetInnerHTML).toBeDefined() expect(elements[0].props.children).not.toBeDefined() expect(elements[0].props).toMatchSnapshot() }) it('should return a generated React style element with nonce if webpack nonce is preset in the global scope', () => { // eslint-disable-next-line require('../utils/nonce').mockImplementation(() => 'foo') injectGlobal` body { background: papayawhip; } ` const Heading = styled.h1` color: red; ` const sheet = new ServerStyleSheet() const html = renderToString( sheet.collectStyles(Hello SSR!) ) const elements = sheet.getStyleElement() expect(elements).toHaveLength(1) expect(elements[0].props.nonce).toBe('foo') }) it('should interleave styles with rendered HTML when utilitizing streaming', () => { injectGlobal` body { background: papayawhip; } ` const Heading = styled.h1` color: red; ` const sheet = new ServerStyleSheet() const jsx = sheet.collectStyles(Hello SSR!) const stream = sheet.interleaveWithNodeStream(renderToNodeStream(jsx)) return new Promise((resolve, reject) => { let received = '' stream.on('data', chunk => { received += chunk }) stream.on('end', () => { expect(received).toMatchSnapshot() expect(sheet.closed).toBe(true) resolve() }) stream.on('error', reject) }) }) it('should interleave styles with rendered HTML when chunked streaming', () => { const Heading = styled.h1` color: red; ` const Body = styled.div` color: blue; ` const SideBar = styled.div` color: yellow; ` const Footer = styled.div` color: green; ` const sheet = new ServerStyleSheet() const jsx = sheet.collectStyles( Hello SSR! {new Array(1000).fill(0).map(() => (
*************************
))} SideBar
) const stream = sheet.interleaveWithNodeStream(renderToNodeStream(jsx)) return new Promise((resolve, reject) => { let received = '' stream.on('data', chunk => { received += chunk }) stream.on('end', () => { expect(received).toMatchSnapshot() expect(sheet.closed).toBe(true) expect(received).toMatch(/yellow/) expect(received).toMatch(/green/) resolve() }) stream.on('error', reject) }) }) it('should handle errors while streaming', () => { injectGlobal` body { background: papayawhip; } ` const Heading = styled.h1` color: red; ` const sheet = new ServerStyleSheet() const jsx = sheet.collectStyles(null) const stream = sheet.interleaveWithNodeStream(renderToNodeStream(jsx)) return new Promise((resolve, reject) => { stream.on('data', function noop() {}) stream.on('error', err => { expect(err).toMatchSnapshot() expect(sheet.closed).toBe(true) resolve() }) }) }) })