diff --git a/.changeset/silver-foxes-double.md b/.changeset/silver-foxes-double.md new file mode 100644 index 000000000..576d0a7f5 --- /dev/null +++ b/.changeset/silver-foxes-double.md @@ -0,0 +1,6 @@ +--- +'@emotion/react': minor +'@emotion/styled': minor +--- + +Refactored code to use the upcoming `React.useInsertionEffect` when it's available (this is a new hook that is going to be introduced in React 18). This shouldn't have any effect on existing codebases and the change should be transparent. diff --git a/.changeset/tricky-cameras-fold.md b/.changeset/tricky-cameras-fold.md new file mode 100644 index 000000000..38bdc86a9 --- /dev/null +++ b/.changeset/tricky-cameras-fold.md @@ -0,0 +1,5 @@ +--- +'@emotion/utils': minor +--- + +Introduced `registerStyles` helper that is shared across between other packages. This is just an internal util that shouldn't be used by packages other than `@emotion/*` packages. diff --git a/.changeset/wet-bikes-explain.md b/.changeset/wet-bikes-explain.md new file mode 100644 index 000000000..751a46a47 --- /dev/null +++ b/.changeset/wet-bikes-explain.md @@ -0,0 +1,5 @@ +--- +'@emotion/jest': patch +--- + +Adjusted Enzyme-related code path to accomodate for changes related to the refactor around using `React.useInsertionEffect`. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index da30222c6..27fd0a735 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -44,7 +44,7 @@ jobs: run: yarn - name: Run Tests - run: yarn coverage --color + run: yarn test:ci --color - name: Upload coverage to Codecov uses: codecov/codecov-action@v1 @@ -81,6 +81,37 @@ jobs: - name: Check Types run: yarn flow:check + test_react18: + name: Test React 18 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@main + + - name: Set Node.js 12.x + uses: actions/setup-node@main + with: + node-version: 12.x + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + + - uses: actions/cache@v2 + id: yarn-cache + with: + path: | + ${{ steps.yarn-cache-dir-path.outputs.dir }} + node_modules + key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Install Dependencies + run: yarn + + - name: Run Tests with React 18 + run: yarn test:react18:ci + test_dist: name: Test Dist runs-on: ubuntu-latest diff --git a/jest-react18.config.js b/jest-react18.config.js new file mode 100644 index 000000000..119490a9b --- /dev/null +++ b/jest-react18.config.js @@ -0,0 +1,9 @@ +const baseConfig = require('./jest.config.js') + +module.exports = Object.assign({}, baseConfig, { + moduleNameMapper: { + '^react($|\\/.+)': 'react18$1', + '^react-dom($|\\/.+)': 'react18-dom$1', + '^react-test-renderer($|\\/.+)': 'react18-test-renderer$1' + } +}) diff --git a/jest.config.js b/jest.config.js index 79dda87bf..e07058c11 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,7 +1,6 @@ module.exports = { testEnvironment: 'jsdom', transform: { - '\\.css$': '/test/styleTransform.js', '^.+\\.js?$': 'babel-jest' }, watchPlugins: [ diff --git a/jest.dist.js b/jest.dist.js index a34e1460e..29c7b1017 100644 --- a/jest.dist.js +++ b/jest.dist.js @@ -3,5 +3,3 @@ const baseConfig = require('./jest.config.js') module.exports = Object.assign({}, baseConfig, { transformIgnorePatterns: ['dist', 'node_modules'] }) - -delete module.exports.moduleNameMapper diff --git a/package.json b/package.json index 2fe2e9280..e73e52749 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,10 @@ "test:size": "npm-run-all build size", "test:debug": "node --inspect-brk ./node_modules/jest/bin/jest.js --runInBand --watch", "test": "jest", + "test:react18": "jest -c jest-react18.config.js", "test:typescript": "yarn workspaces run test:typescript", - "coverage": "jest --coverage --no-cache --ci --runInBand", + "test:ci": "jest --coverage --no-cache --ci --runInBand", + "test:react18:ci": "yarn test:react18 --coverage --no-cache --ci --runInBand", "test:prod": "yarn build && jest -c jest.dist.js --no-cache --ci --runInBand", "lint:check": "eslint .", "test:watch": "jest --watch", @@ -193,7 +195,7 @@ "@mdx-js/mdx": "^1.6.22", "@mdx-js/react": "^1.6.22", "@preconstruct/cli": "1.1.34", - "@testing-library/react": "^12.1.2", + "@testing-library/react": "13.0.0-alpha.5", "@types/jest": "^27.0.3", "@types/node": "^10.11.4", "@types/react": "^16.9.11", @@ -237,7 +239,6 @@ "jest-junit": "^13.0.0", "jest-serializer-html": "^7.1.0", "jest-watch-typeahead": "^1.0.0", - "jsdom": "^16.6.0", "lint-staged": "^7.2.0", "module-alias": "^2.0.1", "multipipe": "^1.0.2", @@ -257,8 +258,9 @@ "react-primitives": "^0.8.1", "react-router-dom": "^4.2.2", "react-test-renderer": "16.8.6", - "react18": "npm:react@alpha", - "react18-dom": "npm:react-dom@alpha", + "react18": "npm:react@18.0.0-rc.0-next-aa8f2bdbc-20211215", + "react18-dom": "npm:react-dom@18.0.0-rc.0-next-aa8f2bdbc-20211215", + "react18-test-renderer": "npm:react-test-renderer@18.0.0-rc.0-next-aa8f2bdbc-20211215", "svg-tag-names": "^1.1.1", "through": "^2.3.8", "unified": "^6.1.6", diff --git a/packages/css/test/instance/__snapshots__/inline.test.js.snap b/packages/css/test/instance/__snapshots__/inline.test.js.snap index 886284570..a37cfa6b9 100644 --- a/packages/css/test/instance/__snapshots__/inline.test.js.snap +++ b/packages/css/test/instance/__snapshots__/inline.test.js.snap @@ -91,52 +91,6 @@ exports[`hydration only inserts rules that are not in the critical css 3`] = ` box-shadow: -15px -15px 0 0 aqua,-30px -30px 0 0 cornflowerblue; } -@font-face { - font-family: 'Patrick Hand SC'; - font-style: normal; - font-weight: 400; - src: local('Patrick Hand SC'),local('PatrickHandSC-Regular'),url(https://fonts.gstatic.com/s/patrickhandsc/v4/OYFWCgfCR-7uHIovjUZXsZ71Uis0Qeb9Gqo8IZV7ckE.woff2) format('woff2'); - unicode-range: U+0100-024f,U+1-1eff,U+20a0-20ab,U+20ad-20cf,U+2c60-2c7f,U+A720-A7FF; -} - -@keyframes animation-i9f7qw-bounce { - from, 20%, 53%, 80%, to { - animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); - transform: translate3d(0, 0, 0); - } - - 40%, 43% { - animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); - transform: translate3d(0, -30px, 0); - } - - 70% { - animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); - transform: translate3d(0, -15px, 0); - } - - 90% { - transform: translate3d(0, -4px, 0); - } -} - -.no-prefix { - display: flex; - justify-content: center; -} - -.some-key-14e1j2p-hoverStyles-Something_Main { - color: hotpink; - display: flex; -} - -.some-key-14e1j2p-hoverStyles-Something_Main:hover { - color: hotpink; - background-color: lightgray; - border-color: aqua; - box-shadow: -15px -15px 0 0 aqua,-30px -30px 0 0 cornflowerblue; -} - .some-key-1h1w8ez-Image { animation: animation-i9f7qw-bounce; border-radius: 50%; @@ -157,9 +111,7 @@ exports[`renderStylesToString renders large recursive component 1`] = ` > .some-key-127stik{color:hotpink;} -
+
woah there hello world diff --git a/packages/css/test/instance/__snapshots__/stream.test.js.snap b/packages/css/test/instance/__snapshots__/stream.test.js.snap index 3eb787809..9f65fc684 100644 --- a/packages/css/test/instance/__snapshots__/stream.test.js.snap +++ b/packages/css/test/instance/__snapshots__/stream.test.js.snap @@ -86,52 +86,6 @@ exports[`hydration only inserts rules that are not in the critical css 3`] = ` box-shadow: -15px -15px 0 0 aqua,-30px -30px 0 0 cornflowerblue; } -@font-face { - font-family: 'Patrick Hand SC'; - font-style: normal; - font-weight: 400; - src: local('Patrick Hand SC'),local('PatrickHandSC-Regular'),url(https://fonts.gstatic.com/s/patrickhandsc/v4/OYFWCgfCR-7uHIovjUZXsZ71Uis0Qeb9Gqo8IZV7ckE.woff2) format('woff2'); - unicode-range: U+0100-024f,U+1-1eff,U+20a0-20ab,U+20ad-20cf,U+2c60-2c7f,U+A720-A7FF; -} - -@keyframes animation-i9f7qw-bounce { - from, 20%, 53%, 80%, to { - animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); - transform: translate3d(0, 0, 0); - } - - 40%, 43% { - animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); - transform: translate3d(0, -30px, 0); - } - - 70% { - animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); - transform: translate3d(0, -15px, 0); - } - - 90% { - transform: translate3d(0, -4px, 0); - } -} - -.no-prefix { - display: flex; - justify-content: center; -} - -.some-key-14e1j2p-hoverStyles-Something_Main { - color: hotpink; - display: flex; -} - -.some-key-14e1j2p-hoverStyles-Something_Main:hover { - color: hotpink; - background-color: lightgray; - border-color: aqua; - box-shadow: -15px -15px 0 0 aqua,-30px -30px 0 0 cornflowerblue; -} - .some-key-1h1w8ez-Image { animation: animation-i9f7qw-bounce; border-radius: 50%; @@ -147,9 +101,7 @@ exports[`renderStylesToNodeStream renders large recursive component 1`] = ` > .some-global-200{padding:0;margin:200;}.some-key-127stik{color:hotpink;}.some-global-199{padding:0;margin:199;}.some-global-198{padding:0;margin:198;}.some-global-197{padding:0;margin:197;}.some-global-196{padding:0;margin:196;}.some-global-195{padding:0;margin:195;}.some-global-194{padding:0;margin:194;}.some-global-193{padding:0;margin:193;}.some-global-192{padding:0;margin:192;}.some-global-191{padding:0;margin:191;}.some-global-190{padding:0;margin:190;}.some-global-189{padding:0;margin:189;}.some-global-188{padding:0;margin:188;}.some-global-187{padding:0;margin:187;}.some-global-186{padding:0;margin:186;}.some-global-185{padding:0;margin:185;}.some-global-184{padding:0;margin:184;}.some-global-183{padding:0;margin:183;}.some-global-182{padding:0;margin:182;}.some-global-181{padding:0;margin:181;}.some-global-180{padding:0;margin:180;}.some-global-179{padding:0;margin:179;}.some-global-178{padding:0;margin:178;}.some-global-177{padding:0;margin:177;}.some-global-176{padding:0;margin:176;}.some-global-175{padding:0;margin:175;}.some-global-174{padding:0;margin:174;}.some-global-173{padding:0;margin:173;}.some-global-172{padding:0;margin:172;}.some-global-171{padding:0;margin:171;}.some-global-170{padding:0;margin:170;}.some-global-169{padding:0;margin:169;}.some-global-168{padding:0;margin:168;}.some-global-167{padding:0;margin:167;}.some-global-166{padding:0;margin:166;}.some-global-165{padding:0;margin:165;}.some-global-164{padding:0;margin:164;}.some-global-163{padding:0;margin:163;}.some-global-162{padding:0;margin:162;}.some-global-161{padding:0;margin:161;}.some-global-160{padding:0;margin:160;}.some-global-159{padding:0;margin:159;}.some-global-158{padding:0;margin:158;}.some-global-157{padding:0;margin:157;}.some-global-156{padding:0;margin:156;}.some-global-155{padding:0;margin:155;}.some-global-154{padding:0;margin:154;}.some-global-153{padding:0;margin:153;}.some-global-152{padding:0;margin:152;}.some-global-151{padding:0;margin:151;}.some-global-150{padding:0;margin:150;}.some-global-149{padding:0;margin:149;}.some-global-148{padding:0;margin:148;}.some-global-147{padding:0;margin:147;}.some-global-146{padding:0;margin:146;}.some-global-145{padding:0;margin:145;}.some-global-144{padding:0;margin:144;}.some-global-143{padding:0;margin:143;}.some-global-142{padding:0;margin:142;}.some-global-141{padding:0;margin:141;}.some-global-140{padding:0;margin:140;}.some-global-139{padding:0;margin:139;}.some-global-138{padding:0;margin:138;}.some-global-137{padding:0;margin:137;}.some-global-136{padding:0;margin:136;}.some-global-135{padding:0;margin:135;}.some-global-134{padding:0;margin:134;}.some-global-133{padding:0;margin:133;}.some-global-132{padding:0;margin:132;}.some-global-131{padding:0;margin:131;}.some-global-130{padding:0;margin:130;}.some-global-129{padding:0;margin:129;}.some-global-128{padding:0;margin:128;}.some-global-127{padding:0;margin:127;}.some-global-126{padding:0;margin:126;}.some-global-125{padding:0;margin:125;}.some-global-124{padding:0;margin:124;}.some-global-123{padding:0;margin:123;}.some-global-122{padding:0;margin:122;}.some-global-121{padding:0;margin:121;}.some-global-120{padding:0;margin:120;}.some-global-119{padding:0;margin:119;}.some-global-118{padding:0;margin:118;}.some-global-117{padding:0;margin:117;}.some-global-116{padding:0;margin:116;}.some-global-115{padding:0;margin:115;}.some-global-114{padding:0;margin:114;}.some-global-113{padding:0;margin:113;}.some-global-112{padding:0;margin:112;}.some-global-111{padding:0;margin:111;}.some-global-110{padding:0;margin:110;}.some-global-109{padding:0;margin:109;}.some-global-108{padding:0;margin:108;}.some-global-107{padding:0;margin:107;}.some-global-106{padding:0;margin:106;}.some-global-105{padding:0;margin:105;}.some-global-104{padding:0;margin:104;}.some-global-103{padding:0;margin:103;}.some-global-102{padding:0;margin:102;}.some-global-101{padding:0;margin:101;}.some-global-100{padding:0;margin:100;}.some-global-99{padding:0;margin:99;}.some-global-98{padding:0;margin:98;}.some-global-97{padding:0;margin:97;}.some-global-96{padding:0;margin:96;}.some-global-95{padding:0;margin:95;}.some-global-94{padding:0;margin:94;}.some-global-93{padding:0;margin:93;}.some-global-92{padding:0;margin:92;}.some-global-91{padding:0;margin:91;}.some-global-90{padding:0;margin:90;}.some-global-89{padding:0;margin:89;}.some-global-88{padding:0;margin:88;}.some-global-87{padding:0;margin:87;}.some-global-86{padding:0;margin:86;}.some-global-85{padding:0;margin:85;}.some-global-84{padding:0;margin:84;}.some-global-83{padding:0;margin:83;}.some-global-82{padding:0;margin:82;}.some-global-81{padding:0;margin:81;}.some-global-80{padding:0;margin:80;}.some-global-79{padding:0;margin:79;}.some-global-78{padding:0;margin:78;}.some-global-77{padding:0;margin:77;}.some-global-76{padding:0;margin:76;}.some-global-75{padding:0;margin:75;}.some-global-74{padding:0;margin:74;}.some-global-73{padding:0;margin:73;}.some-global-72{padding:0;margin:72;}.some-global-71{padding:0;margin:71;}.some-global-70{padding:0;margin:70;}.some-global-69{padding:0;margin:69;}.some-global-68{padding:0;margin:68;}.some-global-67{padding:0;margin:67;}.some-global-66{padding:0;margin:66;}.some-global-65{padding:0;margin:65;}.some-global-64{padding:0;margin:64;}.some-global-63{padding:0;margin:63;}.some-global-62{padding:0;margin:62;}.some-global-61{padding:0;margin:61;}.some-global-60{padding:0;margin:60;}.some-global-59{padding:0;margin:59;}.some-global-58{padding:0;margin:58;}.some-global-57{padding:0;margin:57;}.some-global-56{padding:0;margin:56;}.some-global-55{padding:0;margin:55;}.some-global-54{padding:0;margin:54;}.some-global-53{padding:0;margin:53;}.some-global-52{padding:0;margin:52;}.some-global-51{padding:0;margin:51;}.some-global-50{padding:0;margin:50;}.some-global-49{padding:0;margin:49;}.some-global-48{padding:0;margin:48;}.some-global-47{padding:0;margin:47;}.some-global-46{padding:0;margin:46;}.some-global-45{padding:0;margin:45;}.some-global-44{padding:0;margin:44;}.some-global-43{padding:0;margin:43;}.some-global-42{padding:0;margin:42;}.some-global-41{padding:0;margin:41;}.some-global-40{padding:0;margin:40;}.some-global-39{padding:0;margin:39;}.some-global-38{padding:0;margin:38;}.some-global-37{padding:0;margin:37;}.some-global-36{padding:0;margin:36;}.some-global-35{padding:0;margin:35;}.some-global-34{padding:0;margin:34;}.some-global-33{padding:0;margin:33;}.some-global-32{padding:0;margin:32;}.some-global-31{padding:0;margin:31;}.some-global-30{padding:0;margin:30;}.some-global-29{padding:0;margin:29;}.some-global-28{padding:0;margin:28;}.some-global-27{padding:0;margin:27;}.some-global-26{padding:0;margin:26;}.some-global-25{padding:0;margin:25;}.some-global-24{padding:0;margin:24;}.some-global-23{padding:0;margin:23;}.some-global-22{padding:0;margin:22;}.some-global-21{padding:0;margin:21;}.some-global-20{padding:0;margin:20;}.some-global-19{padding:0;margin:19;}.some-global-18{padding:0;margin:18;}.some-global-17{padding:0;margin:17;}.some-global-16{padding:0;margin:16;}.some-global-15{padding:0;margin:15;}.some-global-14{padding:0;margin:14;}.some-global-13{padding:0;margin:13;}.some-global-12{padding:0;margin:12;}.some-global-11{padding:0;margin:11;}.some-global-10{padding:0;margin:10;}.some-global-9{padding:0;margin:9;}.some-global-8{padding:0;margin:8;}.some-global-7{padding:0;margin:7;}.some-global-6{padding:0;margin:6;}.some-global-5{padding:0;margin:5;}.some-global-4{padding:0;margin:4;}.some-global-3{padding:0;margin:3;}.some-global-2{padding:0;margin:2;}.some-global-1{padding:0;margin:1;} -
+
woah there hello world diff --git a/packages/css/test/instance/inline.test.js b/packages/css/test/instance/inline.test.js index 7e1ccf2af..dd3be901a 100644 --- a/packages/css/test/instance/inline.test.js +++ b/packages/css/test/instance/inline.test.js @@ -1,6 +1,8 @@ -/** - * @jest-environment node - */ +import { + stripDataReactRoot, + disableBrowserEnvTemporarily, + safeQuerySelector +} from 'test-utils' import { getComponents, getInjectedRules, @@ -8,7 +10,6 @@ import { getCssFromChunks, setHtml } from '../../../server/test/util' -import { JSDOM } from 'jsdom' let React let renderToString @@ -28,52 +29,53 @@ const resetAllModules = () => { } describe('renderStylesToString', () => { - beforeEach(resetAllModules) - - test('renders styles with ids', () => { - const { Page1, Page2 } = getComponents(emotion, reactEmotion) - expect( - emotionServer.renderStylesToString(renderToString()) - ).toMatchSnapshot() - expect( - emotionServer.renderStylesToString(renderToString()) - ).toMatchSnapshot() + test('renders styles with ids', async () => { + await disableBrowserEnvTemporarily(() => { + resetAllModules() + const { Page1, Page2 } = getComponents(emotion, reactEmotion) + expect( + emotionServer.renderStylesToString(renderToString()) + ).toMatchSnapshot() + expect( + emotionServer.renderStylesToString(renderToString()) + ).toMatchSnapshot() + }) }) - test('renders large recursive component', () => { - const BigComponent = createBigComponent(emotion) - expect( - emotionServer.renderStylesToString( - renderToString() - ) - ).toMatchSnapshot() + test('renders large recursive component', async () => { + await disableBrowserEnvTemporarily(() => { + resetAllModules() + const BigComponent = createBigComponent(emotion) + expect( + stripDataReactRoot( + emotionServer.renderStylesToString( + renderToString() + ) + ) + ).toMatchSnapshot() + }) }) }) describe('hydration', () => { - beforeEach(resetAllModules) + test('only inserts rules that are not in the critical css', async () => { + const appHtml = await disableBrowserEnvTemporarily(() => { + resetAllModules() - afterEach(() => { - global.document = undefined - global.window = undefined - global.navigator = undefined - }) + const { Page1 } = getComponents(emotion, reactEmotion) + return emotionServer.renderStylesToString(renderToString()) + }) - test('only inserts rules that are not in the critical css', () => { - const { Page1 } = getComponents(emotion, reactEmotion) - const html = emotionServer.renderStylesToString(renderToString()) - expect(html).toMatchSnapshot() - const { window } = new JSDOM(html) - global.document = window.document - global.window = window - global.navigator = window.navigator - setHtml(html, document) + expect(appHtml).toMatchSnapshot() + document.body.innerHTML = `
${appHtml}
` resetAllModules() expect(emotion.cache.registered).toEqual({}) const { Page1: NewPage1 } = getComponents(emotion, reactEmotion) - render() - expect(getInjectedRules(document)).toMatchSnapshot() + render(, { + container: safeQuerySelector('#root') + }) + expect(getInjectedRules()).toMatchSnapshot() expect(getCssFromChunks(emotion, document)).toMatchSnapshot() }) }) diff --git a/packages/css/test/instance/stream.test.js b/packages/css/test/instance/stream.test.js index fe4aa4af3..c73e696c8 100644 --- a/packages/css/test/instance/stream.test.js +++ b/packages/css/test/instance/stream.test.js @@ -1,8 +1,8 @@ -/** - * @jest-environment node - * @flow - */ -import { JSDOM } from 'jsdom' +import { + stripDataReactRoot, + disableBrowserEnvTemporarily, + safeQuerySelector +} from 'test-utils' let React let renderToString @@ -24,52 +24,52 @@ const resetAllModules = () => { } describe('renderStylesToNodeStream', () => { - beforeEach(resetAllModules) - test('renders styles with ids', async () => { - const { Page1, Page2 } = util.getComponents(emotion, reactEmotion) - expect( - await util.renderToStringWithStream(, emotionServer) - ).toMatchSnapshot() - expect( - await util.renderToStringWithStream(, emotionServer) - ).toMatchSnapshot() + await disableBrowserEnvTemporarily(async () => { + resetAllModules() + const { Page1, Page2 } = util.getComponents(emotion, reactEmotion) + expect( + await util.renderToStringWithStream(, emotionServer) + ).toMatchSnapshot() + expect( + await util.renderToStringWithStream(, emotionServer) + ).toMatchSnapshot() + }) }) test('renders large recursive component', async () => { - const BigComponent = util.createBigComponent(emotion) - expect( - await util.renderToStringWithStream( - , - emotionServer - ) - ).toMatchSnapshot() + await disableBrowserEnvTemporarily(async () => { + resetAllModules() + const BigComponent = util.createBigComponent(emotion) + expect( + stripDataReactRoot( + await util.renderToStringWithStream( + , + emotionServer + ) + ) + ).toMatchSnapshot() + }) }) }) describe('hydration', () => { - beforeEach(resetAllModules) - - afterEach(() => { - global.document = undefined - global.window = undefined - global.navigator = undefined - }) - test('only inserts rules that are not in the critical css', async () => { - const { Page1 } = util.getComponents(emotion, reactEmotion) - const html = await util.renderToStringWithStream(, emotionServer) - expect(html).toMatchSnapshot() - const { window } = new JSDOM(html) - global.document = window.document - global.window = window - global.navigator = window.navigator - util.setHtml(html, document) + const appHtml = await disableBrowserEnvTemporarily(() => { + resetAllModules() + const { Page1 } = util.getComponents(emotion, reactEmotion) + return util.renderToStringWithStream(, emotionServer) + }) + + expect(appHtml).toMatchSnapshot() + document.body.innerHTML = `
${appHtml}
` resetAllModules() expect(emotion.cache.registered).toEqual({}) const { Page1: NewPage1 } = util.getComponents(emotion, reactEmotion) - render() + render(, { + container: safeQuerySelector('#root') + }) expect(util.getInjectedRules(document)).toMatchSnapshot() expect(util.getCssFromChunks(emotion, document)).toMatchSnapshot() }) diff --git a/packages/css/test/no-babel/__snapshots__/index.test.js.snap b/packages/css/test/no-babel/__snapshots__/index.test.js.snap index 59f6fe483..15f8e46f8 100644 --- a/packages/css/test/no-babel/__snapshots__/index.test.js.snap +++ b/packages/css/test/no-babel/__snapshots__/index.test.js.snap @@ -14,6 +14,8 @@ exports[`css @supports 1`] = ` exports[`css component as selectors (object syntax) 1`] = `"Component selectors can only be used in conjunction with @emotion/babel-plugin."`; +exports[`css component as selectors (object syntax) 2`] = `"The above error occurred in the component:"`; + exports[`css component selectors without target 1`] = `"Component selectors can only be used in conjunction with @emotion/babel-plugin."`; exports[`css composition 1`] = ` diff --git a/packages/css/test/no-babel/index.test.js b/packages/css/test/no-babel/index.test.js index 217a0b786..527671acc 100644 --- a/packages/css/test/no-babel/index.test.js +++ b/packages/css/test/no-babel/index.test.js @@ -5,6 +5,13 @@ import renderer from 'react-test-renderer' import { css } from '@emotion/css' import styled from '@emotion/styled' +let consoleError = console.error + +afterEach(() => { + // $FlowFixMe + console.error = consoleError +}) + describe('css', () => { test('random expression', () => { const cls2 = css` @@ -130,21 +137,29 @@ describe('css', () => { expect(tree).toMatchSnapshot() }) test('component as selectors (object syntax)', () => { - expect(() => { - const fontSize = '20px' - const H1 = styled('h1')({ fontSize }) - const Thing = styled('div')({ - display: 'flex', - [String(H1)]: { - color: 'green' - } - }) + const fontSize = '20px' + const H1 = styled('h1')({ fontSize }) + const Thing = styled('div')({ + display: 'flex', + [String(H1)]: { + color: 'green' + } + }) + + const spy = jest.fn() + // $FlowFixMe + console.error = spy + + expect(() => renderer.create( hello

This will be green

world
) - }).toThrowErrorMatchingSnapshot() + ).toThrowErrorMatchingSnapshot() + + expect(spy.mock.calls.length).toBe(1) + expect(spy.mock.calls[0][0].split('\n')[0]).toMatchSnapshot() }) test('component selectors without target', () => { const SomeComponent = styled('div')` diff --git a/packages/css/test/sheet.dom.test.js b/packages/css/test/sheet.dom.test.js index 98c75a680..127e6a8f3 100644 --- a/packages/css/test/sheet.dom.test.js +++ b/packages/css/test/sheet.dom.test.js @@ -1,6 +1,13 @@ // @flow import { sheet } from '@emotion/css' +const consoleError = console.error + +afterEach(() => { + // $FlowFixMe + console.error = consoleError +}) + describe('sheet', () => { beforeEach(() => { sheet.flush() @@ -31,9 +38,14 @@ describe('sheet', () => { test('throws', () => { sheet.speedy(true) - const spy = jest.spyOn(global.console, 'error') + const spy = jest.fn() + // $FlowFixMe + console.error = spy sheet.insert('.asdfasdf4###112121211{') - expect(spy).toHaveBeenCalled() + expect(spy.mock.calls.length).toBe(1) + expect(spy.mock.calls[0][0]).toMatchInlineSnapshot( + `"There was a problem inserting the following rule: \\".asdfasdf4###112121211{\\""` + ) }) test('.speedy throws when a rule has already been inserted', () => { diff --git a/packages/jest/src/utils.js b/packages/jest/src/utils.js index 6eb7dbd14..8a4cad723 100644 --- a/packages/jest/src/utils.js +++ b/packages/jest/src/utils.js @@ -60,6 +60,12 @@ function getClassNameProp(node) { export function unwrapFromPotentialFragment(node: *) { if (node.type() === Symbol.for('react.fragment')) { + const isShallow = !!node.dive + if (isShallow) { + // render the `` so it has a chance to insert rules in the JSDOM + node.children().first().dive() + } + return node.children().last() } return node diff --git a/packages/jest/test/matchers.test.js b/packages/jest/test/matchers.test.js index 3e3665e30..4f4f17769 100644 --- a/packages/jest/test/matchers.test.js +++ b/packages/jest/test/matchers.test.js @@ -1,11 +1,14 @@ import 'test-utils/legacy-env' import renderer from 'react-test-renderer' /** @jsx jsx */ +import * as React from 'react' import * as enzyme from 'enzyme' import { css, jsx } from '@emotion/react' import styled from '@emotion/styled' import { matchers } from '@emotion/jest' +const isReact16 = React.version.split('.')[0] === '16' + const { toHaveStyleRule } = matchers expect.extend(matchers) @@ -54,73 +57,6 @@ describe('toHaveStyleRule', () => { expect(svgNode).toHaveStyleRule('width', expect.stringMatching(/.*%$/)) }) - it('supports enzyme `mount` method', () => { - const Component = () => ( -
- -
- ) - - const wrapper = enzyme.mount() - expect(wrapper).toHaveStyleRule('color', 'red') - expect(wrapper).not.toHaveStyleRule('width', '100%') - const svgNode = wrapper.find('svg') - expect(svgNode).toHaveStyleRule('width', '100%') - expect(svgNode).not.toHaveStyleRule('color', 'red') - }) - - it('supports enzyme `render` method', () => { - const Component = () => ( -
- -
- ) - - const wrapper = enzyme.render() - expect(wrapper).toHaveStyleRule('color', 'red') - expect(wrapper).not.toHaveStyleRule('width', '100%') - const svgNode = wrapper.find('svg') - expect(svgNode).toHaveStyleRule('width', '100%') - expect(svgNode).not.toHaveStyleRule('color', 'red') - }) - - it('supports enzyme `shallow` method', () => { - const Component = () => ( -
- -
- ) - - const wrapper = enzyme.shallow() - expect(wrapper).toHaveStyleRule('color', 'red') - expect(wrapper).not.toHaveStyleRule('width', '100%') - const svgNode = wrapper.childAt(0) - expect(svgNode).toHaveStyleRule('width', '100%') - expect(svgNode).not.toHaveStyleRule('color', 'red') - }) - - it('supports styled components', () => { - const Div = styled('div')` - color: red; - ` - const Svg = styled('svg')` - width: 100%; - ` - ;['mount', 'render', 'shallow'].forEach(method => { - const wrapper = enzyme[method]( -
- -
- ) - expect(wrapper).toHaveStyleRule('color', 'red') - expect(wrapper).not.toHaveStyleRule('width', '100%') - const svgNode = - method === 'render' ? wrapper.find('svg') : wrapper.find(Svg) - expect(svgNode).toHaveStyleRule('width', '100%') - expect(svgNode).not.toHaveStyleRule('color', 'red') - }) - }) - it('fails if no styles are found', () => { const tree = renderer.create(
).toJSON() const result = toHaveStyleRule(tree, 'color', 'red') @@ -365,4 +301,72 @@ describe('toHaveStyleRule', () => { expect(tree.children[0]).toHaveStyleRule('color', 'hotpink') }) + ;(isReact16 ? describe : describe.skip)('enzyme', () => { + it('supports enzyme `mount` method', () => { + const Component = () => ( +
+ +
+ ) + + const wrapper = enzyme.mount() + expect(wrapper).toHaveStyleRule('color', 'red') + expect(wrapper).not.toHaveStyleRule('width', '100%') + const svgNode = wrapper.find('svg') + expect(svgNode).toHaveStyleRule('width', '100%') + expect(svgNode).not.toHaveStyleRule('color', 'red') + }) + + it('supports enzyme `render` method', () => { + const Component = () => ( +
+ +
+ ) + + const wrapper = enzyme.render() + expect(wrapper).toHaveStyleRule('color', 'red') + expect(wrapper).not.toHaveStyleRule('width', '100%') + const svgNode = wrapper.find('svg') + expect(svgNode).toHaveStyleRule('width', '100%') + expect(svgNode).not.toHaveStyleRule('color', 'red') + }) + + it('supports enzyme `shallow` method', () => { + const Component = () => ( +
+ +
+ ) + + const wrapper = enzyme.shallow() + expect(wrapper).toHaveStyleRule('color', 'red') + expect(wrapper).not.toHaveStyleRule('width', '100%') + const svgNode = wrapper.childAt(0) + expect(svgNode).toHaveStyleRule('width', '100%') + expect(svgNode).not.toHaveStyleRule('color', 'red') + }) + + it('supports styled components', () => { + const Div = styled('div')` + color: red; + ` + const Svg = styled('svg')` + width: 100%; + ` + ;['mount', 'render', 'shallow'].forEach(method => { + const wrapper = enzyme[method]( +
+ +
+ ) + expect(wrapper).toHaveStyleRule('color', 'red') + expect(wrapper).not.toHaveStyleRule('width', '100%') + const svgNode = + method === 'render' ? wrapper.find('svg') : wrapper.find(Svg) + expect(svgNode).toHaveStyleRule('width', '100%') + expect(svgNode).not.toHaveStyleRule('color', 'red') + }) + }) + }) }) diff --git a/packages/jest/test/printer.test.js b/packages/jest/test/printer.test.js index 645737a40..6152b35c9 100644 --- a/packages/jest/test/printer.test.js +++ b/packages/jest/test/printer.test.js @@ -1,6 +1,5 @@ // @flow import React from 'react' -import ReactDOM from 'react-dom' import 'test-utils/legacy-env' import renderer from 'react-test-renderer' import prettyFormat from 'pretty-format' @@ -8,6 +7,7 @@ import prettyFormat from 'pretty-format' import { css, jsx, CacheProvider } from '@emotion/react' import createCache from '@emotion/cache' import { createSerializer } from '@emotion/jest' +import { render } from '@testing-library/react' import { ignoreConsoleErrors } from 'test-utils' let emotionPlugin = createSerializer() @@ -41,11 +41,10 @@ describe('jest-emotion with dom elements', () => { it('replaces class names and inserts styles into DOM element snapshots', () => { const divRef = React.createRef() - ReactDOM.render( + render(
-
, - document.createElement('div') +
) const output = prettyFormat(divRef.current, { @@ -85,11 +84,10 @@ describe('jest-emotion with DOM elements disabled', () => { it('does not replace class names or insert styles into DOM element snapshots', () => { const divRef = React.createRef() - ReactDOM.render( + render(
-
, - document.createElement('div') +
) const output = prettyFormat(divRef.current, { diff --git a/packages/jest/test/react-enzyme.test.js b/packages/jest/test/react-enzyme.test.js index 76888ddad..e91e2e9f8 100644 --- a/packages/jest/test/react-enzyme.test.js +++ b/packages/jest/test/react-enzyme.test.js @@ -11,6 +11,8 @@ import toJson from 'enzyme-to-json' import { matchers } from '@emotion/jest' import * as serializer from '@emotion/jest/enzyme-serializer' +const isReact16 = React.version.split('.')[0] === '16' + expect.extend(matchers) expect.addSnapshotSerializer(serializer) @@ -349,7 +351,7 @@ const cases = { } } -describe('enzyme', () => { +;(isReact16 ? describe : describe.skip)('enzyme', () => { jestInCase( 'shallow', ({ render, selector = identity }) => { diff --git a/packages/primitives/test/emotion-primitives.test.js b/packages/primitives/test/emotion-primitives.test.js index 70d277b45..035b08c12 100644 --- a/packages/primitives/test/emotion-primitives.test.js +++ b/packages/primitives/test/emotion-primitives.test.js @@ -3,7 +3,7 @@ import * as React from 'react' import renderer from 'react-test-renderer' import { Text, StyleSheet } from 'react-primitives' import { ThemeProvider } from '@emotion/react' -import { render, unmountComponentAtNode } from 'react-dom' +import { render } from '@testing-library/react' import styled from '@emotion/primitives' @@ -60,20 +60,17 @@ describe('Emotion primitives', () => { display: ${props => props.theme.display}; ` - let mountNode = document.createElement('div') - - render( + const { container, unmount } = render( {/* $FlowFixMe */} Hello World - , - mountNode + ) - expect(mountNode).toMatchSnapshot() - unmountComponentAtNode(mountNode) - expect(mountNode.querySelector('#something')).toBe(null) + expect(container).toMatchSnapshot() + unmount() + expect(container.querySelector('#something')).toBe(null) }) test('should render the primitive on changing the props', () => { @@ -143,11 +140,11 @@ describe('Emotion primitives', () => { color: hotpink; ` let ref = React.createRef() - const rootNode = document.createElement('div') - // $FlowFixMe - render(, rootNode) - expect(ref.current).toBe(rootNode.firstElementChild) - unmountComponentAtNode(rootNode) + const { container, unmount } = render( + + ) + expect(ref.current).toBe(container.firstElementChild) + unmount() }) it('should pass props in withComponent', () => { diff --git a/packages/react/__tests__/__snapshots__/global-with-theme.js.snap b/packages/react/__tests__/__snapshots__/global-with-theme.js.snap index bf694978c..1e07f2ec6 100644 --- a/packages/react/__tests__/__snapshots__/global-with-theme.js.snap +++ b/packages/react/__tests__/__snapshots__/global-with-theme.js.snap @@ -19,9 +19,7 @@ exports[`array 1`] = ` -
+
`; @@ -30,9 +28,7 @@ exports[`array 2`] = ` -
+
`; @@ -49,9 +45,7 @@ exports[`basic 1`] = ` -
+
`; @@ -60,9 +54,7 @@ exports[`basic 2`] = ` -
+
`; diff --git a/packages/react/__tests__/__snapshots__/global.js.snap b/packages/react/__tests__/__snapshots__/global.js.snap index 74367e1b9..8f4a98b80 100644 --- a/packages/react/__tests__/__snapshots__/global.js.snap +++ b/packages/react/__tests__/__snapshots__/global.js.snap @@ -56,9 +56,7 @@ exports[`basic 1`] = ` exports[`basic 2`] = ` -
+
`; @@ -90,9 +88,7 @@ exports[`basic 3`] = ` exports[`basic 4`] = ` -
+
`; diff --git a/packages/react/__tests__/__snapshots__/globals-are-the-worst.js.snap b/packages/react/__tests__/__snapshots__/globals-are-the-worst.js.snap index 1a4bc2121..00a9d8daa 100644 --- a/packages/react/__tests__/__snapshots__/globals-are-the-worst.js.snap +++ b/packages/react/__tests__/__snapshots__/globals-are-the-worst.js.snap @@ -19,9 +19,7 @@ exports[`specificity with globals 1`] = ` -
+
@@ -56,9 +54,7 @@ exports[`specificity with globals 2`] = ` -
+
diff --git a/packages/react/__tests__/__snapshots__/theme-provider.dom.js.snap b/packages/react/__tests__/__snapshots__/theme-provider.dom.js.snap index 2667c5dd1..9d3a243eb 100644 --- a/packages/react/__tests__/__snapshots__/theme-provider.dom.js.snap +++ b/packages/react/__tests__/__snapshots__/theme-provider.dom.js.snap @@ -6,9 +6,7 @@ exports[`provider with theme value that changes 1`] = ` padding: 4px; } -
+
+
-
+
diff --git a/packages/react/__tests__/compat/browser.js b/packages/react/__tests__/compat/browser.js index a6c8c02d4..450a1d267 100644 --- a/packages/react/__tests__/compat/browser.js +++ b/packages/react/__tests__/compat/browser.js @@ -3,22 +3,17 @@ import 'test-utils/dev-mode' import { throwIfFalsy } from 'test-utils' import { jsx, CacheProvider } from '@emotion/react' -import { render } from 'react-dom' +import { render } from '@testing-library/react' import { css, cache } from '@emotion/css' -test('composition works from old emotion css calls', cb => { +test('composition works from old emotion css calls', () => { const cls = css` color: green; ` - throwIfFalsy(document.body).innerHTML = '
' render(
- , - throwIfFalsy(document.getElementById('root')), - () => { - expect(document.documentElement).toMatchSnapshot() - cb() - } + ) + expect(document.documentElement).toMatchSnapshot() }) diff --git a/packages/react/__tests__/element.js b/packages/react/__tests__/element.js index 38a99d1d5..f4a89dc89 100644 --- a/packages/react/__tests__/element.js +++ b/packages/react/__tests__/element.js @@ -1,7 +1,7 @@ // @flow /** @jsx jsx */ import 'test-utils/dev-mode' -import { render } from 'react-dom' +import { render } from '@testing-library/react' import { jsx, css, CacheProvider, ThemeProvider } from '@emotion/react' import createCache from '@emotion/cache' @@ -11,9 +11,6 @@ console.error = jest.fn() beforeEach(() => { // $FlowFixMe document.head.innerHTML = '' - // $FlowFixMe - document.body.innerHTML = `
` - jest.clearAllMocks() }) @@ -38,9 +35,9 @@ describe('EmotionElement', () => { ) - render(, document.getElementById('root')) + render() expect(console.error).not.toHaveBeenCalled() - render(, document.getElementById('root')) + render() expect(console.error).not.toHaveBeenCalled() }) }) diff --git a/packages/react/__tests__/global-with-theme.js b/packages/react/__tests__/global-with-theme.js index f8b66b430..32b0d46c7 100644 --- a/packages/react/__tests__/global-with-theme.js +++ b/packages/react/__tests__/global-with-theme.js @@ -1,18 +1,16 @@ // @flow import 'test-utils/dev-mode' import * as React from 'react' -import { render, unmountComponentAtNode } from 'react-dom' +import { render } from '@testing-library/react' import { Global, ThemeProvider } from '@emotion/react' beforeEach(() => { // $FlowFixMe document.head.innerHTML = '' - // $FlowFixMe - document.body.innerHTML = `
` }) test('basic', () => { - render( + const { unmount } = render( ({ @@ -21,17 +19,15 @@ test('basic', () => { } })} /> - , - // $FlowFixMe - document.getElementById('root') + ) expect(document.documentElement).toMatchSnapshot() - unmountComponentAtNode(document.getElementById('root')) + unmount() expect(document.documentElement).toMatchSnapshot() }) test('array', () => { - render( + const { unmount } = render( { theme => ({ html: { fontSize: theme.fontSize } }) ]} /> - , // $FlowFixMe - document.getElementById('root') + ) expect(document.documentElement).toMatchSnapshot() - unmountComponentAtNode(document.getElementById('root')) + unmount() expect(document.documentElement).toMatchSnapshot() }) diff --git a/packages/react/__tests__/global.js b/packages/react/__tests__/global.js index a624da805..2ecc07540 100644 --- a/packages/react/__tests__/global.js +++ b/packages/react/__tests__/global.js @@ -1,7 +1,7 @@ // @flow import 'test-utils/dev-mode' import * as React from 'react' -import { render, unmountComponentAtNode } from 'react-dom' +import { render } from '@testing-library/react' import { Global, keyframes, @@ -17,14 +17,11 @@ console.error = jest.fn() beforeEach(() => { // $FlowFixMe document.head.innerHTML = '' - // $FlowFixMe - document.body.innerHTML = `
` - jest.resetAllMocks() }) test('basic', () => { - render( + const { unmount } = render( { } ]} /> - , - // $FlowFixMe - document.getElementById('root') + ) expect(document.head).toMatchSnapshot() expect(document.body).toMatchSnapshot() - unmountComponentAtNode(document.getElementById('root')) + unmount() expect(document.head).toMatchSnapshot() expect(document.body).toMatchSnapshot() }) test('updating more than 1 global rule', () => { const cache = createCache({ key: 'global-multiple-rules' }) - const renderComponent = ({ background, color }) => - render( - - - , - // $FlowFixMe - document.getElementById('root') - ) - renderComponent({ background: 'white', color: 'black' }) + const Comp = ({ background, color }) => ( + + + + ) + + const { rerender } = render() expect(document.head).toMatchSnapshot() - renderComponent({ background: 'gray', color: 'white' }) + rerender() expect(document.head).toMatchSnapshot() }) @@ -99,8 +92,8 @@ test('no React hook order violations', () => { ) - render(, document.getElementById('root')) + render() expect(console.error).not.toHaveBeenCalled() - render(, document.getElementById('root')) + render() expect(console.error).not.toHaveBeenCalled() }) diff --git a/packages/react/__tests__/globals-are-the-worst.js b/packages/react/__tests__/globals-are-the-worst.js index 2d40fa367..33d7dd1bd 100644 --- a/packages/react/__tests__/globals-are-the-worst.js +++ b/packages/react/__tests__/globals-are-the-worst.js @@ -1,8 +1,7 @@ // @flow import 'test-utils/dev-mode' -import { throwIfFalsy } from 'test-utils' +import { render } from '@testing-library/react' import * as React from 'react' -import { render } from 'react-dom' import { Global } from '@emotion/react' import styled from '@emotion/styled' @@ -25,11 +24,9 @@ test('specificity with globals', () => {
) - throwIfFalsy(document.body).innerHTML = `
` - let root = throwIfFalsy(document.getElementById('root')) - render(, root) + const { rerender } = render() expect(document.documentElement).toMatchSnapshot() - render(, root) + rerender() expect(document.documentElement).toMatchSnapshot() }) diff --git a/packages/react/__tests__/import-prod.js b/packages/react/__tests__/import-prod.js index bf3bcb3a7..a6ca90651 100644 --- a/packages/react/__tests__/import-prod.js +++ b/packages/react/__tests__/import-prod.js @@ -1,9 +1,9 @@ // @flow import 'test-utils/prod-mode' import * as React from 'react' +import * as ReactDOM from 'react-dom' import { css, Global } from '@emotion/react' import styled from '@emotion/styled' -import { render } from '@testing-library/react' import prettify from '@emotion/css-prettifier' // using styled instead of the css prop because there was a really weird flow error @@ -21,8 +21,23 @@ expect.addSnapshotSerializer({ } }) -test('it works', () => { - render( +// can't use RTL as production React 18 throws when it's trying to use `act` +const render = children => + new Promise(resolve => { + const el = document.createElement('div') + // $FlowFixMe + document.body.appendChild(el) + + if ((ReactDOM: any).createRoot) { + const root = (ReactDOM: any).createRoot(el) + root.render(
{children}
) + } else { + ReactDOM.render(children, el, resolve) + } + }) + +test('it works', async () => { + await render(
something diff --git a/packages/react/__tests__/rehydration.js b/packages/react/__tests__/rehydration.js index 32d300afc..9b67b8b92 100644 --- a/packages/react/__tests__/rehydration.js +++ b/packages/react/__tests__/rehydration.js @@ -1,6 +1,6 @@ // @flow /** @jsx jsx */ -import { safeQuerySelector } from 'test-utils' +import { safeQuerySelector, disableBrowserEnvTemporarily } from 'test-utils' // $FlowFixMe console.error = jest.fn() @@ -11,9 +11,11 @@ afterEach(() => { jest.clearAllMocks() }) -let React +let React = require('react') let ReactDOM let ReactDOMServer +let render + let createCache let css let jsx @@ -30,6 +32,11 @@ const resetAllModules = () => { React = require('react') ReactDOM = require('react-dom') ReactDOMServer = require('react-dom/server') + // we can't use regular entrypoint here as it registers afterEach + // this means that we don't get auto-cleanup from RTL + // but it shouldn't be needed in this file + // we are resetting JSDOM's state on our own, and we don't depend on React's unmounting anyhow here + render = require('@testing-library/react/pure').render const emotionReact = require('@emotion/react') css = emotionReact.css @@ -41,30 +48,6 @@ const resetAllModules = () => { styled = require('@emotion/styled').default } -const removeGlobalProp = prop => { - let descriptor = Object.getOwnPropertyDescriptor(global, prop) - Object.defineProperty(global, prop, { - value: undefined, - writable: true, - configurable: true - }) - // $FlowFixMe - return () => Object.defineProperty(global, prop, descriptor) -} - -const disableBrowserEnvTemporarily = (fn: () => T): T => { - let restoreDocument = removeGlobalProp('document') - let restoreWindow = removeGlobalProp('window') - let restoreHTMLElement = removeGlobalProp('HTMLElement') - try { - return fn() - } finally { - restoreDocument() - restoreWindow() - restoreHTMLElement() - } -} - beforeEach(() => { safeQuerySelector('head').innerHTML = '' safeQuerySelector('body').innerHTML = '' @@ -99,7 +82,10 @@ test("cache created in render doesn't cause a hydration mismatch", () => { ) } - ReactDOM.hydrate(, safeQuerySelector('#root')) + render(, { + hydrate: true, + container: safeQuerySelector('#root') + }) expect((console.error: any).mock.calls).toMatchInlineSnapshot(`Array []`) expect((console.warn: any).mock.calls).toMatchInlineSnapshot(`Array []`) @@ -135,7 +121,10 @@ test('initializing another Emotion instance should not move already moved styles ) } - ReactDOM.hydrate(, safeQuerySelector('#root')) + render(, { + hydrate: true, + container: safeQuerySelector('#root') + }) resetAllModules() @@ -187,7 +176,9 @@ test('initializing another Emotion instance should not move already moved styles ) } - ReactDOM.render(, safeQuerySelector('#root')) + render(, { + container: safeQuerySelector('#root') + }) resetAllModules() @@ -208,8 +199,8 @@ test('initializing another Emotion instance should not move already moved styles `) }) -test('global styles can be removed individually after rehydrating HTML SSRed with extractCriticalToChunks', () => { - const { app, styles } = disableBrowserEnvTemporarily(() => { +test('global styles can be removed individually after rehydrating HTML SSRed with extractCriticalToChunks', async () => { + const { app, styles } = await disableBrowserEnvTemporarily(() => { resetAllModules() let cache = createCache({ key: 'mui' }) @@ -272,7 +263,7 @@ test('global styles can be removed individually after rehydrating HTML SSRed wit resetAllModules() const cache = createCache({ key: 'mui', speedy: true }) - ReactDOM.render( + render( @@ -280,7 +271,9 @@ test('global styles can be removed individually after rehydrating HTML SSRed wit
, - safeQuerySelector('#root') + { + container: safeQuerySelector('#root') + } ) expect(safeQuerySelector('head')).toMatchInlineSnapshot(` @@ -306,14 +299,16 @@ test('global styles can be removed individually after rehydrating HTML SSRed wit `) - ReactDOM.render( + render(
, - safeQuerySelector('#root') + { + container: safeQuerySelector('#root') + } ) expect(safeQuerySelector('head')).toMatchInlineSnapshot(` @@ -334,8 +329,8 @@ test('global styles can be removed individually after rehydrating HTML SSRed wit `) }) -test('duplicated global styles can be removed safely after rehydrating HTML SSRed with extractCriticalToChunks', () => { - const { app, styles } = disableBrowserEnvTemporarily(() => { +test('duplicated global styles can be removed safely after rehydrating HTML SSRed with extractCriticalToChunks', async () => { + const { app, styles } = await disableBrowserEnvTemporarily(() => { resetAllModules() let cache = createCache({ key: 'muii' }) @@ -387,13 +382,15 @@ test('duplicated global styles can be removed safely after rehydrating HTML SSRe resetAllModules() const cache = createCache({ key: 'muii', speedy: true }) - ReactDOM.render( + render(
, - safeQuerySelector('#root') + { + container: safeQuerySelector('#root') + } ) // it's expected that this contains 2 copies of the same global style @@ -422,12 +419,14 @@ test('duplicated global styles can be removed safely after rehydrating HTML SSRe `) - ReactDOM.render( + render(
, - safeQuerySelector('#root') + { + container: safeQuerySelector('#root') + } ) // this should still have a global style @@ -448,11 +447,13 @@ test('duplicated global styles can be removed safely after rehydrating HTML SSRe `) - ReactDOM.render( + render(
, - safeQuerySelector('#root') + { + container: safeQuerySelector('#root') + } ) // this should render without a crash @@ -468,8 +469,8 @@ test('duplicated global styles can be removed safely after rehydrating HTML SSRe `) }) -test('duplicated global styles can be removed safely after rehydrating HTML SSRed with zero config approach', () => { - const { app } = disableBrowserEnvTemporarily(() => { +test('duplicated global styles can be removed safely after rehydrating HTML SSRed with zero config approach', async () => { + const { app } = await disableBrowserEnvTemporarily(() => { resetAllModules() let cache = createCache({ key: 'globcop' }) @@ -521,13 +522,15 @@ test('duplicated global styles can be removed safely after rehydrating HTML SSRe resetAllModules() const cache = createCache({ key: 'globcop', speedy: true }) - ReactDOM.render( + render(
, - safeQuerySelector('#root') + { + container: safeQuerySelector('#root') + } ) // it's expected that this contains 2 copies of the same global style @@ -555,12 +558,14 @@ test('duplicated global styles can be removed safely after rehydrating HTML SSRe `) - ReactDOM.render( + render(
, - safeQuerySelector('#root') + { + container: safeQuerySelector('#root') + } ) // this should still have a global style @@ -581,11 +586,13 @@ test('duplicated global styles can be removed safely after rehydrating HTML SSRe `) - ReactDOM.render( + render(
, - safeQuerySelector('#root') + { + container: safeQuerySelector('#root') + } ) // this should render without a crash @@ -600,32 +607,9 @@ test('duplicated global styles can be removed safely after rehydrating HTML SSRe `) }) - -describe('react18', () => { - let previousIsReactActEnvironment - beforeAll(() => { - jest - .mock('react', () => { - return jest.requireActual('react18') - }) - .mock('react-dom', () => { - return jest.requireActual('react18-dom') - }) - .mock('react-dom/server', () => { - return jest.requireActual('react18-dom/server') - }) - - previousIsReactActEnvironment = global.IS_REACT_ACT_ENVIRONMENT - global.IS_REACT_ACT_ENVIRONMENT = true - }) - - afterAll(() => { - jest.clearAllMocks() - global.IS_REACT_ACT_ENVIRONMENT = previousIsReactActEnvironment - }) - - test('no hydration mismatch for styled when using useId', () => { - const finalHTML = disableBrowserEnvTemporarily(() => { +;((React: any).useId ? describe : describe.skip)('useId', () => { + test('no hydration mismatch for styled when using useId', async () => { + const finalHTML = await disableBrowserEnvTemporarily(() => { resetAllModules() const StyledDivWithId = styled(function DivWithId({ className }) { @@ -649,16 +633,17 @@ describe('react18', () => { border: '1px solid black' }) - ;(React: any).unstable_act(() => { - ReactDOM.hydrateRoot(safeQuerySelector('#root'), ) + render(, { + hydrate: true, + container: safeQuerySelector('#root') }) expect((console.error: any).mock.calls).toMatchInlineSnapshot(`Array []`) expect((console.warn: any).mock.calls).toMatchInlineSnapshot(`Array []`) }) - test('no hydration mismatch for css prop when using useId', () => { - const finalHTML = disableBrowserEnvTemporarily(() => { + test('no hydration mismatch for css prop when using useId', async () => { + const finalHTML = await disableBrowserEnvTemporarily(() => { resetAllModules() function DivWithId({ className }: { className?: string }) { @@ -684,23 +669,24 @@ describe('react18', () => { return
} - ;(React: any).unstable_act(() => { - ReactDOM.hydrateRoot( - safeQuerySelector('#root'), - - ) - }) + render( + , + { + hydrate: true, + container: safeQuerySelector('#root') + } + ) expect((console.error: any).mock.calls).toMatchInlineSnapshot(`Array []`) expect((console.warn: any).mock.calls).toMatchInlineSnapshot(`Array []`) }) - test('no hydration mismatch for ClassNames when using useId', () => { - const finalHTML = disableBrowserEnvTemporarily(() => { + test('no hydration mismatch for ClassNames when using useId', async () => { + const finalHTML = await disableBrowserEnvTemporarily(() => { resetAllModules() const DivWithId = ({ className }) => { @@ -732,22 +718,23 @@ describe('react18', () => { return
} - ;(React: any).unstable_act(() => { - ReactDOM.hydrateRoot( - safeQuerySelector('#root'), - - {({ css }) => { - return ( - - ) - }} - - ) - }) + render( + + {({ css }) => { + return ( + + ) + }} + , + { + hydrate: true, + container: safeQuerySelector('#root') + } + ) expect((console.error: any).mock.calls).toMatchInlineSnapshot(`Array []`) expect((console.warn: any).mock.calls).toMatchInlineSnapshot(`Array []`) diff --git a/packages/react/__tests__/theme-provider.dom.js b/packages/react/__tests__/theme-provider.dom.js index ef0a6deda..412539c75 100644 --- a/packages/react/__tests__/theme-provider.dom.js +++ b/packages/react/__tests__/theme-provider.dom.js @@ -2,10 +2,15 @@ /** @jsx jsx */ import 'test-utils/next-env' import 'test-utils/dev-mode' -import { throwIfFalsy, safeQuerySelector } from 'test-utils' +import { render, fireEvent } from '@testing-library/react' +import { safeQuerySelector } from 'test-utils' import * as React from 'react' import { jsx, ThemeProvider } from '@emotion/react' -import { render } from 'react-dom' + +beforeEach(() => { + // $FlowFixMe + document.head.innerHTML = '' +}) test('provider with theme value that changes', () => { class ThemeTest extends React.Component<*, *> { @@ -27,19 +32,9 @@ test('provider with theme value that changes', () => { ) } } - let head = throwIfFalsy(document.head) - let body = throwIfFalsy(document.body) - - head.innerHTML = '' - body.innerHTML = '
' - - let root = safeQuerySelector('#root') - - render(, root) - expect(root).toMatchSnapshot() - throwIfFalsy(safeQuerySelector('#the-thing')).click() - expect(root).toMatchSnapshot() - head.innerHTML = '' - body.innerHTML = '
' + const { container } = render() + expect(container).toMatchSnapshot() + fireEvent.click(safeQuerySelector('#the-thing')) + expect(container).toMatchSnapshot() }) diff --git a/packages/react/src/class-names.js b/packages/react/src/class-names.js index deabcdce7..c0ded58c0 100644 --- a/packages/react/src/class-names.js +++ b/packages/react/src/class-names.js @@ -1,9 +1,14 @@ // @flow import * as React from 'react' -import { getRegisteredStyles, insertStyles } from '@emotion/utils' +import { + getRegisteredStyles, + insertStyles, + registerStyles +} from '@emotion/utils' import { serializeStyles } from '@emotion/serialize' import { withEmotionCache } from './context' import { ThemeContext } from './theming' +import useInsertionEffectMaybe from './useInsertionEffectMaybe' import { isBrowser } from './utils' type ClassNameArg = @@ -88,30 +93,50 @@ type Props = { }) => React.Node } -const Noop = () => null +const Insertion = ({ cache, serializedArr }) => { + let rules = useInsertionEffectMaybe(() => { + let rules = '' + for (let i = 0; i < serializedArr.length; i++) { + let res = insertStyles(cache, serializedArr[i], false) + if (!isBrowser && res !== undefined) { + rules += res + } + } + if (!isBrowser) { + return rules + } + }) + + if (!isBrowser && rules.length !== 0) { + return ( + -
+
woah there hello world @@ -2105,12 +1997,11 @@ exports[`renderStylesToString renders styles with ids 2`] = ` `; exports[`renderStylesToString skip undefined styles 1`] = ` - `; diff --git a/packages/server/test/__snapshots__/stream.test.js.snap b/packages/server/test/__snapshots__/stream.test.js.snap index d7dc070ea..eb6097b39 100644 --- a/packages/server/test/__snapshots__/stream.test.js.snap +++ b/packages/server/test/__snapshots__/stream.test.js.snap @@ -142,112 +142,6 @@ exports[`hydration only inserts rules that are not in the critical css 3`] = ` box-shadow: -15px -15px 0 0 aqua,-30px -30px 0 0 cornflowerblue; } -@font-face { - font-family: 'Patrick Hand SC'; - font-style: normal; - font-weight: 400; - src: local('Patrick Hand SC'),local('PatrickHandSC-Regular'),url(https://fonts.gstatic.com/s/patrickhandsc/v4/OYFWCgfCR-7uHIovjUZXsZ71Uis0Qeb9Gqo8IZV7ckE.woff2) format('woff2'); - unicode-range: U+0100-024f,U+1-1eff,U+20a0-20ab,U+20ad-20cf,U+2c60-2c7f,U+A720-A7FF; -} - -@-webkit-keyframes animation-i9f7qw-bounce { - from, 20%, 53%, 80%, to { - -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); - animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); - -webkit-transform: translate3d(0, 0, 0); - -moz-transform: translate3d(0, 0, 0); - -ms-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - } - - 40%, 43% { - -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); - animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); - -webkit-transform: translate3d(0, -30px, 0); - -moz-transform: translate3d(0, -30px, 0); - -ms-transform: translate3d(0, -30px, 0); - transform: translate3d(0, -30px, 0); - } - - 70% { - -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); - animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); - -webkit-transform: translate3d(0, -15px, 0); - -moz-transform: translate3d(0, -15px, 0); - -ms-transform: translate3d(0, -15px, 0); - transform: translate3d(0, -15px, 0); - } - - 90% { - -webkit-transform: translate3d(0, -4px, 0); - -moz-transform: translate3d(0, -4px, 0); - -ms-transform: translate3d(0, -4px, 0); - transform: translate3d(0, -4px, 0); - } -} - -@keyframes animation-i9f7qw-bounce { - from, 20%, 53%, 80%, to { - -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); - animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); - -webkit-transform: translate3d(0, 0, 0); - -moz-transform: translate3d(0, 0, 0); - -ms-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); - } - - 40%, 43% { - -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); - animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); - -webkit-transform: translate3d(0, -30px, 0); - -moz-transform: translate3d(0, -30px, 0); - -ms-transform: translate3d(0, -30px, 0); - transform: translate3d(0, -30px, 0); - } - - 70% { - -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); - animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); - -webkit-transform: translate3d(0, -15px, 0); - -moz-transform: translate3d(0, -15px, 0); - -ms-transform: translate3d(0, -15px, 0); - transform: translate3d(0, -15px, 0); - } - - 90% { - -webkit-transform: translate3d(0, -4px, 0); - -moz-transform: translate3d(0, -4px, 0); - -ms-transform: translate3d(0, -4px, 0); - transform: translate3d(0, -4px, 0); - } -} - -.no-prefix { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-pack: center; - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; -} - -.css-14e1j2p-hoverStyles-Something_Main { - color: hotpink; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; -} - -.css-14e1j2p-hoverStyles-Something_Main:hover { - color: white; - background-color: lightgray; - border-color: aqua; - box-shadow: -15px -15px 0 0 aqua,-30px -30px 0 0 cornflowerblue; -} - .css-1h1w8ez-Image { -webkit-animation: animation-i9f7qw-bounce; animation: animation-i9f7qw-bounce; @@ -262,9 +156,7 @@ exports[`renderStylesToNodeStream renders large recursive component 1`] = ` -
+
woah there hello world diff --git a/packages/server/test/index.test.js b/packages/server/test/index.test.js index 9e655cc87..98f647c50 100644 --- a/packages/server/test/index.test.js +++ b/packages/server/test/index.test.js @@ -1,67 +1,88 @@ -/** - * @jest-environment node - * @flow - */ - -import React from 'react' -import { renderToString } from 'react-dom/server' import { getComponents, prettifyCritical, getInjectedRules } from './util' -import { JSDOM } from 'jsdom' -import { ignoreConsoleErrors } from 'test-utils' +import { + ignoreConsoleErrors, + disableBrowserEnvTemporarily, + safeQuerySelector +} from 'test-utils' + +let React +let renderToString +let render +let emotion +let emotionServer +let reactEmotion -let emotion = require('@emotion/css') -let reactEmotion = require('@emotion/styled') -let emotionServer = require('@emotion/server') +const resetAllModules = () => { + jest.resetModules() + React = require('react') + renderToString = require('react-dom/server').renderToString + render = require('@testing-library/react/pure').render + emotion = require('@emotion/css') + emotionServer = require('@emotion/server') + reactEmotion = require('@emotion/styled') +} describe('extractCritical', () => { - test('returns static css', () => { - const { Page1, Page2 } = getComponents(emotion, reactEmotion) - expect( - prettifyCritical(emotionServer.extractCritical(renderToString())) - ).toMatchSnapshot() - expect( - prettifyCritical(emotionServer.extractCritical(renderToString())) - ).toMatchSnapshot() + test('returns static css', async () => { + await disableBrowserEnvTemporarily(() => { + resetAllModules() + const { Page1, Page2 } = getComponents(emotion, reactEmotion) + expect( + prettifyCritical( + emotionServer.extractCritical(renderToString()) + ) + ).toMatchSnapshot() + expect( + prettifyCritical( + emotionServer.extractCritical(renderToString()) + ) + ).toMatchSnapshot() + }) }) - test('does not warn when using extract critical', () => { - let Provider = require('@emotion/react').CacheProvider - const WithNthSelector = reactEmotion.default('div')({ - ':nth-child(1)': {} - }) + test('does not warn when using extract critical', async () => { + await disableBrowserEnvTemporarily(() => { + resetAllModules() + let Provider = require('@emotion/react').CacheProvider + const WithNthSelector = reactEmotion.default('div')({ + ':nth-child(1)': {} + }) - ignoreConsoleErrors(() => { - emotionServer.extractCritical( - renderToString( - - - + ignoreConsoleErrors(() => { + emotionServer.extractCritical( + renderToString( + + + + ) ) - ) - expect((console.error: any).mock.calls).toMatchObject([]) + expect((console.error: any).mock.calls).toMatchObject([]) + }) }) }) }) describe('hydration', () => { - test('only rules that are not in the critical css are inserted', () => { - const { Page1 } = getComponents(emotion, reactEmotion) - const { html, ids, css } = emotionServer.extractCritical( - renderToString() - ) + test('only rules that are not in the critical css are inserted', async () => { + const { html, ids, css } = await disableBrowserEnvTemporarily(() => { + resetAllModules() + const { Page1 } = getComponents(emotion, reactEmotion) + return emotionServer.extractCritical(renderToString()) + }) + expect(prettifyCritical({ html, css, ids })).toMatchSnapshot() - const { window } = new JSDOM(html) - global.document = window.document - global.window = window + document.body.innerHTML = `
${html}
` - jest.resetModules() + resetAllModules() emotion = require('@emotion/css') emotionServer = require('@emotion/server') expect(emotion.cache.inserted).toEqual({}) emotion.hydrate(ids) const { Page1: NewPage1 } = getComponents(emotion, reactEmotion) - renderToString() + render(, { + container: safeQuerySelector('#root') + }) expect(getInjectedRules()).toMatchSnapshot() }) }) diff --git a/packages/server/test/inline.test.js b/packages/server/test/inline.test.js index 1519a4cf0..0378b0c5a 100644 --- a/packages/server/test/inline.test.js +++ b/packages/server/test/inline.test.js @@ -1,8 +1,8 @@ -/** - * @jest-environment node - * @flow - */ -import { JSDOM } from 'jsdom' +import { + stripDataReactRoot, + disableBrowserEnvTemporarily, + safeQuerySelector +} from 'test-utils' let React let renderToString @@ -24,56 +24,60 @@ const resetAllModules = () => { } describe('renderStylesToString', () => { - beforeEach(resetAllModules) - - test('renders styles with ids', () => { - const { Page1, Page2 } = util.getComponents(emotion, reactEmotion) - expect( - emotionServer.renderStylesToString(renderToString()) - ).toMatchSnapshot() - expect( - emotionServer.renderStylesToString(renderToString()) - ).toMatchSnapshot() + test('renders styles with ids', async () => { + await disableBrowserEnvTemporarily(() => { + resetAllModules() + const { Page1, Page2 } = util.getComponents(emotion, reactEmotion) + expect( + emotionServer.renderStylesToString(renderToString()) + ).toMatchSnapshot() + expect( + emotionServer.renderStylesToString(renderToString()) + ).toMatchSnapshot() + }) }) - test('skip undefined styles', () => { - const { css } = emotion - const style = css` - color: red; - ` - const component = - const output = emotionServer.renderStylesToString(renderToString(component)) + test('skip undefined styles', async () => { + await disableBrowserEnvTemporarily(() => { + resetAllModules() + const { css } = emotion + const style = css` + color: red; + ` + const component = ( + + ) + const output = emotionServer.renderStylesToString( + renderToString(component) + ) - expect(output).toEqual(expect.not.stringContaining('undefined')) - expect(output).toMatchSnapshot() + expect(output).toEqual(expect.not.stringContaining('undefined')) + expect(stripDataReactRoot(output)).toMatchSnapshot() + }) }) - test('renders large recursive component', () => { - const BigComponent = util.createBigComponent(emotion) - expect( - emotionServer.renderStylesToString( - renderToString() - ) - ).toMatchSnapshot() + test('renders large recursive component', async () => { + await disableBrowserEnvTemporarily(() => { + resetAllModules() + const BigComponent = util.createBigComponent(emotion) + expect( + stripDataReactRoot( + emotionServer.renderStylesToString( + renderToString() + ) + ) + ).toMatchSnapshot() + }) }) }) describe('hydration', () => { - beforeEach(resetAllModules) - - afterEach(() => { - global.document = undefined - global.window = undefined - global.navigator = undefined - }) - - test('only inserts rules that are not in the critical css', () => { - const { Page1 } = util.getComponents(emotion, reactEmotion) - const html = emotionServer.renderStylesToString(renderToString()) - expect(html).toMatchSnapshot() + test('only inserts rules that are not in the critical css', async () => { + const appHtml = await disableBrowserEnvTemporarily(() => { + resetAllModules() + const { Page1 } = util.getComponents(emotion, reactEmotion) + return emotionServer.renderStylesToString(renderToString()) + }) - const { window } = new JSDOM(html) - global.document = window.document - global.window = window - global.navigator = window.navigator - util.setHtml(html, document) + expect(appHtml).toMatchSnapshot() + document.body.innerHTML = `
${appHtml}
` resetAllModules() @@ -81,7 +85,9 @@ describe('hydration', () => { const { Page1: NewPage1 } = util.getComponents(emotion, reactEmotion) - render() + render(, { + container: safeQuerySelector('#root') + }) expect(util.getInjectedRules(document)).toMatchSnapshot() expect(util.getCssFromChunks(emotion, document)).toMatchSnapshot() }) diff --git a/packages/server/test/stream.test.js b/packages/server/test/stream.test.js index c708ceaa1..6c95301bb 100644 --- a/packages/server/test/stream.test.js +++ b/packages/server/test/stream.test.js @@ -1,8 +1,8 @@ -/** - * @jest-environment node - * @flow - */ -import { JSDOM } from 'jsdom' +import { + stripDataReactRoot, + disableBrowserEnvTemporarily, + safeQuerySelector +} from 'test-utils' let React let renderToString @@ -24,52 +24,52 @@ const resetAllModules = () => { } describe('renderStylesToNodeStream', () => { - beforeEach(resetAllModules) - test('renders styles with ids', async () => { - const { Page1, Page2 } = util.getComponents(emotion, reactEmotion) - expect( - await util.renderToStringWithStream(, emotionServer) - ).toMatchSnapshot() - expect( - await util.renderToStringWithStream(, emotionServer) - ).toMatchSnapshot() + await disableBrowserEnvTemporarily(async () => { + resetAllModules() + const { Page1, Page2 } = util.getComponents(emotion, reactEmotion) + expect( + await util.renderToStringWithStream(, emotionServer) + ).toMatchSnapshot() + expect( + await util.renderToStringWithStream(, emotionServer) + ).toMatchSnapshot() + }) }) test('renders large recursive component', async () => { - const BigComponent = util.createBigComponent(emotion) - expect( - await util.renderToStringWithStream( - , - emotionServer - ) - ).toMatchSnapshot() + await disableBrowserEnvTemporarily(async () => { + resetAllModules() + const BigComponent = util.createBigComponent(emotion) + expect( + stripDataReactRoot( + await util.renderToStringWithStream( + , + emotionServer + ) + ) + ).toMatchSnapshot() + }) }) }) describe('hydration', () => { - beforeEach(resetAllModules) - - afterEach(() => { - global.document = undefined - global.window = undefined - global.navigator = undefined - }) - test('only inserts rules that are not in the critical css', async () => { - const { Page1 } = util.getComponents(emotion, reactEmotion) - const html = await util.renderToStringWithStream(, emotionServer) - expect(html).toMatchSnapshot() - const { window } = new JSDOM(html) - global.document = window.document - global.window = window - global.navigator = window.navigator - util.setHtml(html, document) + const appHtml = await disableBrowserEnvTemporarily(() => { + resetAllModules() + const { Page1 } = util.getComponents(emotion, reactEmotion) + return util.renderToStringWithStream(, emotionServer) + }) + + expect(appHtml).toMatchSnapshot() + document.body.innerHTML = `
${appHtml}
` resetAllModules() expect(emotion.cache.registered).toEqual({}) const { Page1: NewPage1 } = util.getComponents(emotion, reactEmotion) - render() + render(, { + container: safeQuerySelector('#root') + }) expect(util.getInjectedRules(document)).toMatchSnapshot() expect(util.getCssFromChunks(emotion, document)).toMatchSnapshot() }) diff --git a/packages/server/test/util.js b/packages/server/test/util.js index 6c1ddae23..0f5df444d 100644 --- a/packages/server/test/util.js +++ b/packages/server/test/util.js @@ -198,7 +198,7 @@ const isSSRedStyle = node => { return attrib.length > 1 } -export const getCssFromChunks = (emotion: Emotion, document: Document) => { +export const getCssFromChunks = (emotion: Emotion) => { const chunks = Array.from( // $FlowFixMe emotion.sheet.tags[0].parentNode.querySelectorAll(`[data-emotion]`) @@ -211,7 +211,7 @@ export const getCssFromChunks = (emotion: Emotion, document: Document) => { return prettify(css) } -export const getInjectedRules = (document: Document = global.document) => +export const getInjectedRules = () => prettify( Array.from(document.querySelectorAll('[data-emotion]')) .filter(node => !isSSRedStyle(node)) @@ -219,14 +219,6 @@ export const getInjectedRules = (document: Document = global.document) => .join('') ) -export const setHtml = (html: string, document: Document) => { - if (document.body !== null) { - document.body.innerHTML = html - } else { - throw new Error('body does not exist on document') - } -} - export const renderToStringWithStream = ( element: React.Element<*>, { renderStylesToNodeStream }: EmotionServer diff --git a/packages/styled/src/base.js b/packages/styled/src/base.js index d2ea17900..74ce54106 100644 --- a/packages/styled/src/base.js +++ b/packages/styled/src/base.js @@ -9,8 +9,13 @@ import { type StyledElementType } from './utils' import { withEmotionCache, ThemeContext } from '@emotion/react' -import { getRegisteredStyles, insertStyles } from '@emotion/utils' +import { + getRegisteredStyles, + insertStyles, + registerStyles +} from '@emotion/utils' import { serializeStyles } from '@emotion/serialize' +import useInsertionEffectMaybe from './useInsertionEffectMaybe' const ILLEGAL_ESCAPE_SEQUENCE_ERROR = `You have illegal escape sequence in your template literal, most likely inside content's property value. Because you write your CSS inside a JavaScript string you actually have to do double escaping, so for example "content: '\\00d7';" should become "content: '\\\\00d7';". @@ -18,7 +23,33 @@ You can read more about this here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#ES2018_revision_of_illegal_escape_sequences` let isBrowser = typeof document !== 'undefined' -const Noop = () => null + +const Insertion = ({ cache, serialized, isStringTag }) => { + registerStyles(cache, serialized, isStringTag) + + const rules = useInsertionEffectMaybe(() => + insertStyles(cache, serialized, isStringTag) + ) + + if (!isBrowser && rules !== undefined) { + let serializedNames = serialized.name + let next = serialized.next + while (next !== undefined) { + serializedNames += ' ' + next.name + next = next.next + } + return ( +