From d642956bf122f9ea93e160ea7dded0049405308f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 26 Sep 2021 14:34:11 +0200 Subject: [PATCH 1/6] Add a failing test for printing `forwardRef` element --- src/index.spec.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/index.spec.js b/src/index.spec.js index c2b4c288c..4fd619597 100644 --- a/src/index.spec.js +++ b/src/index.spec.js @@ -1160,4 +1160,13 @@ describe('reactElementToJSXString(ReactElement)', () => { } mount(); }); + + it('should use inferred function name as display name for `forwardRef` element', () => { + const Tag = React.forwardRef(function Tag({ text }, ref) { + return {text}; + }); + expect(reactElementToJSXString()).toEqual( + `` + ); + }); }); From 6053e34970dff68c33d134518cef814168379689 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 26 Sep 2021 14:36:08 +0200 Subject: [PATCH 2/6] Unwind `getReactElementDisplayName` to a cascade of if statements --- src/parser/parseReactElement.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/parser/parseReactElement.js b/src/parser/parseReactElement.js index 773b1a98d..e3388b73b 100644 --- a/src/parser/parseReactElement.js +++ b/src/parser/parseReactElement.js @@ -12,12 +12,18 @@ import type { TreeNode } from './../tree'; const supportFragment = Boolean(Fragment); -const getReactElementDisplayName = (element: ReactElement<*>): string => - element.type.displayName || - (element.type.name !== '_default' ? element.type.name : null) || // function name - (typeof element.type === 'function' // function without a name, you should provide one - ? 'No Display Name' - : element.type); +const getReactElementDisplayName = ({ type }: ReactElement<*>): string => { + if (type.displayName) { + return type.displayName; + } + if (type.name && type.name !== '_default') { + return type.name; + } + if (typeof type === 'function') { + return 'No Display Name'; + } + return type; +}; const noChildren = (propsValue, propName) => propName !== 'children'; From 3d098716cd906617bc1f66e71a5dfcdc2bad53b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 26 Sep 2021 15:16:34 +0200 Subject: [PATCH 3/6] Refactor `getReactElementDisplayName` to switch statement --- src/parser/parseReactElement.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/parser/parseReactElement.js b/src/parser/parseReactElement.js index e3388b73b..d98b58b45 100644 --- a/src/parser/parseReactElement.js +++ b/src/parser/parseReactElement.js @@ -13,16 +13,17 @@ import type { TreeNode } from './../tree'; const supportFragment = Boolean(Fragment); const getReactElementDisplayName = ({ type }: ReactElement<*>): string => { - if (type.displayName) { - return type.displayName; + switch (true) { + case Boolean(type.displayName): + return type.displayName; + case typeof type === 'function': + if (!type.name || type.name === '_default') { + return 'No Display Name'; + } + return type.name; + default: + return type; } - if (type.name && type.name !== '_default') { - return type.name; - } - if (typeof type === 'function') { - return 'No Display Name'; - } - return type; }; const noChildren = (propsValue, propName) => propName !== 'children'; From 507e2486e0f839ab271bdbb1f827e5693d3bd2cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 27 Sep 2021 09:32:46 +0200 Subject: [PATCH 4/6] Support stringifying more element types --- package.json | 13 +-- rollup.config.js | 15 +-- src/index.spec.js | 161 ++++++++++++++++++++++++++++++++ src/parser/parseReactElement.js | 65 +++++++++++-- yarn.lock | 39 +------- 5 files changed, 232 insertions(+), 61 deletions(-) diff --git a/package.json b/package.json index 7811865fb..3dd3550f1 100644 --- a/package.json +++ b/package.json @@ -24,10 +24,7 @@ "smoke": "node tests/smoke/run" }, "lint-staged": { - "*.js": [ - "prettier --write \"**/*.{js,json}\"", - "git add" - ] + "*.js": ["prettier --write \"**/*.{js,json}\"", "git add"] }, "author": { "name": "Algolia, Inc.", @@ -72,7 +69,6 @@ "react-test-renderer": "16.14.0", "rollup": "1.32.1", "rollup-plugin-babel": "4.4.0", - "rollup-plugin-commonjs": "10.1.0", "rollup-plugin-node-builtins": "2.1.2", "rollup-plugin-node-globals": "1.4.0", "rollup-plugin-node-resolve": "5.2.0", @@ -84,11 +80,10 @@ }, "dependencies": { "@base2/pretty-print-object": "1.0.0", - "is-plain-object": "3.0.1" + "is-plain-object": "3.0.1", + "react-is": "^17.0.2" }, "jest": { - "setupFilesAfterEnv": [ - "tests/setupTests.js" - ] + "setupFilesAfterEnv": ["tests/setupTests.js"] } } diff --git a/rollup.config.js b/rollup.config.js index d582d022c..aa07cb4a6 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,12 +1,13 @@ import babel from 'rollup-plugin-babel'; -import commonjs from 'rollup-plugin-commonjs'; import resolve from 'rollup-plugin-node-resolve'; import builtins from 'rollup-plugin-node-builtins'; import globals from 'rollup-plugin-node-globals'; import pkg from './package.json'; -const extractPackagePeerDependencies = () => - Object.keys(pkg.peerDependencies) || []; +const extractExternals = () => [ + ...Object.keys(pkg.dependencies || {}), + ...Object.keys(pkg.peerDependencies || {}), +]; export default { input: 'src/index.js', @@ -22,7 +23,7 @@ export default { sourcemap: true, }, ], - external: extractPackagePeerDependencies(), + external: extractExternals(), plugins: [ babel({ babelrc: false, @@ -36,12 +37,6 @@ export default { resolve({ mainFields: ['module', 'main', 'jsnext', 'browser'], }), - commonjs({ - sourceMap: true, - namedExports: { - react: ['isValidElement'], - }, - }), globals(), builtins(), ], diff --git a/src/index.spec.js b/src/index.spec.js index 4fd619597..790d3177d 100644 --- a/src/index.spec.js +++ b/src/index.spec.js @@ -1169,4 +1169,165 @@ describe('reactElementToJSXString(ReactElement)', () => { `` ); }); + + it('should use `displayName` instead of inferred function name as display name for `forwardRef` element', () => { + const Tag = React.forwardRef(function Tag({ text }, ref) { + return {text}; + }); + Tag.displayName = 'MyTag'; + expect(reactElementToJSXString()).toEqual( + `` + ); + }); + + it('should use inferred function name as display name for `memo` element', () => { + const Tag = React.memo(function Tag({ text }) { + return {text}; + }); + expect(reactElementToJSXString()).toEqual( + `` + ); + }); + + it('should use `displayName` instead of inferred function name as display name for `memo` element', () => { + const Tag = React.memo(function Tag({ text }) { + return {text}; + }); + Tag.displayName = 'MyTag'; + expect(reactElementToJSXString()).toEqual( + `` + ); + }); + + it('should use inferred function name as display name for a `forwardRef` wrapped in `memo`', () => { + const Tag = React.memo( + React.forwardRef(function Tag({ text }, ref) { + return {text}; + }) + ); + expect(reactElementToJSXString()).toEqual( + `` + ); + }); + + it('should use inferred function name as display name for a component wrapped in `memo` multiple times', () => { + const Tag = React.memo( + React.memo( + React.memo(function Tag({ text }) { + return {text}; + }) + ) + ); + expect(reactElementToJSXString()).toEqual( + `` + ); + }); + + it('should stringify `StrictMode` correctly', () => { + const App = () => null; + + expect( + reactElementToJSXString( + + + + ) + ).toEqual(` + +`); + }); + + it('should stringify `Suspense` correctly', () => { + const Spinner = () => null; + const ProfilePage = () => null; + + expect( + reactElementToJSXString( + }> + + + ) + ).toEqual(`}> + +`); + }); + + it('should stringify `Profiler` correctly', () => { + const Navigation = () => null; + + expect( + reactElementToJSXString( + {}}> + + + ) + ).toEqual(` + +`); + }); + + it('should stringify `Contex.Provider` correctly', () => { + const Ctx = React.createContext(); + const App = () => {}; + + expect( + reactElementToJSXString( + + + + ) + ).toEqual(` + +`); + }); + + it('should stringify `Contex.Provider` with `displayName` correctly', () => { + const Ctx = React.createContext(); + Ctx.displayName = 'MyCtx'; + + const App = () => {}; + + expect( + reactElementToJSXString( + + + + ) + ).toEqual(` + +`); + }); + + it('should stringify `Contex.Consumer` correctly', () => { + const Ctx = React.createContext(); + const Button = () => null; + + expect( + reactElementToJSXString( + {theme =>