From bb9f556223bc517750114974814025275a0098c4 Mon Sep 17 00:00:00 2001 From: dan Date: Wed, 6 Apr 2022 18:53:28 +0100 Subject: [PATCH] Add React 18 support (#111) * Bump deps to 18 * Add React 18 support * Don't assume stack format --- package.json | 6 +- src/ReactShallowRenderer.js | 19 ++++- src/__tests__/ReactShallowRenderer-test.js | 4 +- .../ReactShallowRendererHooks-test.js | 76 ++++++++++++++++++- yarn.lock | 20 +++-- 5 files changed, 106 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 92e772b..c1a7fdf 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "homepage": "https://reactjs.org/", "dependencies": { "object-assign": "^4.1.1", - "react-is": "^16.12.0 || ^17.0.0" + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" }, "devDependencies": { "@babel/cli": "^7.8.4", @@ -45,14 +45,14 @@ "jest-diff": "^25.1.0", "lint-staged": "^10.0.8", "prettier": "1.19.1", - "react": "^16.12.0", + "react": "^18.0.0", "rimraf": "^3.0.1", "rollup": "^1.30.1", "rollup-plugin-babel": "^4.3.3", "rollup-plugin-strip-banner": "^1.0.0" }, "peerDependencies": { - "react": "^16.0.0 || ^17.0.0" + "react": "^16.0.0 || ^17.0.0 || ^18.0.0" }, "files": [ "LICENSE", diff --git a/src/ReactShallowRenderer.js b/src/ReactShallowRenderer.js index 5c59539..4f33ca5 100644 --- a/src/ReactShallowRenderer.js +++ b/src/ReactShallowRenderer.js @@ -169,6 +169,7 @@ class ReactShallowRenderer { this._didScheduleRenderPhaseUpdate = false; this._renderPhaseUpdates = null; this._numberOfReRenders = 0; + this._idCounter = 0; } _validateCurrentlyRenderingComponent() { @@ -311,7 +312,6 @@ See https://fb.me/react-invalid-hook-call for tips about how to debug and fix th responder, }); - // TODO: implement if we decide to keep the shallow renderer const useTransition = config => { this._validateCurrentlyRenderingComponent(); const startTransition = callback => { @@ -320,12 +320,22 @@ See https://fb.me/react-invalid-hook-call for tips about how to debug and fix th return [startTransition, false]; }; - // TODO: implement if we decide to keep the shallow renderer const useDeferredValue = (value, config) => { this._validateCurrentlyRenderingComponent(); return value; }; + const useId = () => { + this._validateCurrentlyRenderingComponent(); + const nextId = ++this._idCounter; + return ':r' + nextId + ':'; + }; + + const useSyncExternalStore = (subscribe, getSnapshot) => { + this._validateCurrentlyRenderingComponent(); + return getSnapshot(); + }; + return { readContext, useCallback: identity, @@ -337,13 +347,16 @@ See https://fb.me/react-invalid-hook-call for tips about how to debug and fix th useEffect: noOp, useImperativeHandle: noOp, useLayoutEffect: noOp, + useInsertionEffect: noOp, useMemo, useReducer, useRef, useState, useResponder, + useId, useTransition, useDeferredValue, + useSyncExternalStore, }; } @@ -437,11 +450,13 @@ See https://fb.me/react-invalid-hook-call for tips about how to debug and fix th // Start over from the beginning of the list this._workInProgressHook = null; this._rendering = false; + this._idCounter = 0; this.render(element, context); } else { this._workInProgressHook = null; this._renderPhaseUpdates = null; this._numberOfReRenders = 0; + this._idCounter = 0; } } diff --git a/src/__tests__/ReactShallowRenderer-test.js b/src/__tests__/ReactShallowRenderer-test.js index 84b52ad..bdca2d1 100644 --- a/src/__tests__/ReactShallowRenderer-test.js +++ b/src/__tests__/ReactShallowRenderer-test.js @@ -1210,8 +1210,8 @@ describe('ReactShallowRenderer', () => { shallowRenderer.render(React.createElement(SimpleComponent, {name: 123})), ).toErrorDev( 'Warning: Failed prop type: Invalid prop `name` of type `number` ' + - 'supplied to `SimpleComponent`, expected `string`.\n' + - ' in SimpleComponent', + 'supplied to `SimpleComponent`, expected `string`.', + {withoutStack: true}, ); }); diff --git a/src/__tests__/ReactShallowRendererHooks-test.js b/src/__tests__/ReactShallowRendererHooks-test.js index dabeb51..dbeceb4 100644 --- a/src/__tests__/ReactShallowRendererHooks-test.js +++ b/src/__tests__/ReactShallowRendererHooks-test.js @@ -233,8 +233,12 @@ describe('ReactShallowRenderer with hooks', () => { effectsCalled.push('useEffect'); }); + React.useInsertionEffect(() => { + effectsCalled.push('useInsertionEffect'); + }); + React.useLayoutEffect(() => { - effectsCalled.push('useEffect'); + effectsCalled.push('useLayoutEffect'); }); return
Hello world
; @@ -482,4 +486,74 @@ describe('ReactShallowRenderer with hooks', () => { result = shallowRenderer.render(element); expect(result).toEqual(5); }); + + it('should work with useId', () => { + function SomeComponent({defaultName}) { + const id = React.useId(); + const id2 = React.useId(); + + return ( +
+
+
+
+ ); + } + + const shallowRenderer = createRenderer(); + let result = shallowRenderer.render(); + + expect(result).toEqual( +
+
+
+
, + ); + + result = shallowRenderer.render(); + + expect(result).toEqual( +
+
+
+
, + ); + }); + + it('should work with useSyncExternalStore', () => { + function createExternalStore(initialState) { + const listeners = new Set(); + let currentState = initialState; + return { + set(text) { + currentState = text; + listeners.forEach(listener => listener()); + }, + subscribe(listener) { + listeners.add(listener); + return () => listeners.delete(listener); + }, + getState() { + return currentState; + }, + getSubscriberCount() { + return listeners.size; + }, + }; + } + + const store = createExternalStore('hello'); + + function SomeComponent() { + const value = React.useSyncExternalStore(store.subscribe, store.getState); + return
{value}
; + } + + const shallowRenderer = createRenderer(); + let result = shallowRenderer.render(); + expect(result).toEqual(
hello
); + store.set('goodbye'); + result = shallowRenderer.render(); + expect(result).toEqual(
goodbye
); + }); }); diff --git a/yarn.lock b/yarn.lock index d847165..93fa4db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4488,7 +4488,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.4" -prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -4525,19 +4525,17 @@ react-is@^16.12.0, react-is@^16.8.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -"react-is@^16.12.0 || ^17.0.0": - version "17.0.0" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.0.tgz#7d6ec2a5c3de3ae2c0bfa7586305115ec1192110" - integrity sha512-6IY5dc12jn4xU1kM25NVb86Zn472Kq70jcS7qpdQiSVPyng+7dnFH7BxHLX/Fwm2PZF6OJNEoHx6Zgivt/dZ+A== +"react-is@^16.12.0 || ^17.0.0 || ^18.0.0": + version "18.0.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.0.0.tgz#026f6c4a27dbe33bf4a35655b9e1327c4e55e3f5" + integrity sha512-yUcBYdBBbo3QiPsgYDcfQcIkGZHfxOaoE6HLSnr1sPzMhdyxusbfKOSUbSd/ocGi32dxcj366PsTj+5oggeKKw== -react@^16.12.0: - version "16.14.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" - integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== +react@^18.0.0: + version "18.0.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.0.0.tgz#b468736d1f4a5891f38585ba8e8fb29f91c3cb96" + integrity sha512-x+VL6wbT4JRVPm7EGxXhZ8w8LTROaxPXOqhlGyVSrv0sB1jkyFGgXxJ8LVoPRLvPR6/CIZGFmfzqUa2NYeMr2A== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" read-pkg-up@^7.0.1: version "7.0.1"