/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; let babel = require('@babel/core'); let {wrap} = require('jest-snapshot-serializer-raw'); let freshPlugin = require('react-refresh/babel'); function transform(input, options = {}) { return wrap( babel.transform(input, { babelrc: false, configFile: false, plugins: [ '@babel/syntax-jsx', '@babel/syntax-dynamic-import', [freshPlugin, {skipEnvCheck: true}], ...(options.plugins || []), ], }).code, ); } describe('ReactFreshBabelPlugin', () => { it('registers top-level function declarations', () => { // Hello and Bar should be registered, handleClick shouldn't. expect( transform(` function Hello() { function handleClick() {} return

Hi

; } function Bar() { return ; } `), ).toMatchSnapshot(); }); it('registers top-level exported function declarations', () => { expect( transform(` export function Hello() { function handleClick() {} return

Hi

; } export default function Bar() { return ; } function Baz() { return

OK

; } const NotAComp = 'hi'; export { Baz, NotAComp }; export function sum() {} export const Bad = 42; `), ).toMatchSnapshot(); }); it('registers top-level exported named arrow functions', () => { expect( transform(` export const Hello = () => { function handleClick() {} return

Hi

; }; export let Bar = (props) => ; export default () => { // This one should be ignored. // You should name your components. return ; }; `), ).toMatchSnapshot(); }); it('uses original function declaration if it get reassigned', () => { // This should register the original version. // TODO: in the future, we may *also* register the wrapped one. expect( transform(` function Hello() { return

Hi

; } Hello = connect(Hello); `), ).toMatchSnapshot(); }); it('only registers pascal case functions', () => { // Should not get registered. expect( transform(` function hello() { return 2 * 2; } `), ).toMatchSnapshot(); }); it('registers top-level variable declarations with function expressions', () => { // Hello and Bar should be registered; handleClick, sum, Baz, and Qux shouldn't. expect( transform(` let Hello = function() { function handleClick() {} return

Hi

; }; const Bar = function Baz() { return ; }; function sum() {} let Baz = 10; var Qux; `), ).toMatchSnapshot(); }); it('registers top-level variable declarations with arrow functions', () => { // Hello, Bar, and Baz should be registered; handleClick and sum shouldn't. expect( transform(` let Hello = () => { const handleClick = () => {}; return

Hi

; } const Bar = () => { return ; }; var Baz = () =>
; var sum = () => {}; `), ).toMatchSnapshot(); }); it('ignores HOC definitions', () => { // TODO: we might want to handle HOCs at usage site, however. // TODO: it would be nice if we could always avoid registering // a function that is known to return a function or other non-node. expect( transform(` let connect = () => { function Comp() { const handleClick = () => {}; return

Hi

; } return Comp; }; function withRouter() { return function Child() { const handleClick = () => {}; return

Hi

; } }; `), ).toMatchSnapshot(); }); it('ignores complex definitions', () => { expect( transform(` let A = foo ? () => { return

Hi

; } : null const B = (function Foo() { return

Hi

; })(); let C = () => () => { return

Hi

; }; let D = bar && (() => { return

Hi

; }); `), ).toMatchSnapshot(); }); it('ignores unnamed function declarations', () => { expect( transform(` export default function() {} `), ).toMatchSnapshot(); }); it('registers likely HOCs with inline functions', () => { expect( transform(` const A = forwardRef(function() { return

Foo

; }); const B = memo(React.forwardRef(() => { return

Foo

; })); export default React.memo(forwardRef((props, ref) => { return

Foo

; })); `), ).toMatchSnapshot(); expect( transform(` export default React.memo(forwardRef(function (props, ref) { return

Foo

; })); `), ).toMatchSnapshot(); expect( transform(` export default React.memo(forwardRef(function Named(props, ref) { return

Foo

; })); `), ).toMatchSnapshot(); }); it('ignores higher-order functions that are not HOCs', () => { expect( transform(` const throttledAlert = throttle(function() { alert('Hi'); }); const TooComplex = (function() { return hello })(() => {}); if (cond) { const Foo = thing(() => {}); } `), ).toMatchSnapshot(); }); it('registers identifiers used in JSX at definition site', () => { // When in doubt, register variables that were used in JSX. // Foo, Header, and B get registered. // A doesn't get registered because it's not declared locally. // Alias doesn't get registered because its definition is just an identifier. expect( transform(` import A from './A'; import Store from './Store'; Store.subscribe(); const Header = styled.div\`color: red\` const Factory = funny.factory\`\`; let Alias1 = A; let Alias2 = A.Foo; const Dict = {}; function Foo() { return (
); } const B = hoc(A); // This is currently registered as a false positive: const NotAComponent = wow(A); // We could avoid it but it also doesn't hurt. `), ).toMatchSnapshot(); }); it('registers identifiers used in React.createElement at definition site', () => { // When in doubt, register variables that were used in JSX. // Foo, Header, and B get registered. // A doesn't get registered because it's not declared locally. // Alias doesn't get registered because its definition is just an identifier. expect( transform(` import A from './A'; import Store from './Store'; Store.subscribe(); const Header = styled.div\`color: red\` const Factory = funny.factory\`\`; let Alias1 = A; let Alias2 = A.Foo; const Dict = {}; function Foo() { return [ React.createElement(A), React.createElement(B), React.createElement(Alias1), React.createElement(Alias2), jsx(Header), React.createElement(Dict.X), ]; } React.createContext(Store); const B = hoc(A); // This is currently registered as a false positive: const NotAComponent = wow(A); // We could avoid it but it also doesn't hurt. `), ).toMatchSnapshot(); }); it('registers capitalized identifiers in HOC calls', () => { expect( transform(` function Foo() { return

Hi

; } export default hoc(Foo); export const A = hoc(Foo); const B = hoc(Foo); `), ).toMatchSnapshot(); }); it('generates signatures for function declarations calling hooks', () => { expect( transform(` export default function App() { const [foo, setFoo] = useState(0); React.useEffect(() => {}); return

{foo}

; } `), ).toMatchSnapshot(); }); it('generates signatures for function expressions calling hooks', () => { // Unlike __register__, we want to sign all functions -- not just top level. // This lets us support editing HOCs better. // For function declarations, __signature__ is called on next line. // For function expressions, it wraps the expression. // In order for this to work, __signature__ returns its first argument. expect( transform(` export const A = React.memo(React.forwardRef((props, ref) => { const [foo, setFoo] = useState(0); React.useEffect(() => {}); return

{foo}

; })); export const B = React.memo(React.forwardRef(function(props, ref) { const [foo, setFoo] = useState(0); React.useEffect(() => {}); return

{foo}

; })); function hoc() { return function Inner() { const [foo, setFoo] = useState(0); React.useEffect(() => {}); return

{foo}

; }; } export let C = hoc(); `), ).toMatchSnapshot(); }); it('includes custom hooks into the signatures', () => { expect( transform(` function useFancyState() { const [foo, setFoo] = React.useState(0); useFancyEffect(); return foo; } const useFancyEffect = () => { React.useEffect(() => {}); }; export default function App() { const bar = useFancyState(); return

{bar}

; } `), ).toMatchSnapshot(); }); it('includes custom hooks into the signatures when commonjs target is used', () => { // this test is passing with Babel 6 // but would fail for Babel 7 _without_ custom hook node being cloned for signature expect( transform( ` import {useFancyState} from './hooks'; export default function App() { const bar = useFancyState(); return

{bar}

; } `, { plugins: ['transform-es2015-modules-commonjs'], }, ), ).toMatchSnapshot(); }); it('generates valid signature for exotic ways to call Hooks', () => { expect( transform(` import FancyHook from 'fancy'; export default function App() { function useFancyState() { const [foo, setFoo] = React.useState(0); useFancyEffect(); return foo; } const bar = useFancyState(); const baz = FancyHook.useThing(); React.useState(); useThePlatform(); return

{bar}{baz}

; } `), ).toMatchSnapshot(); }); it('does not consider require-like methods to be HOCs', () => { // None of these were declared in this file. // It's bad to register them because that would trigger // modules to execute in an environment with inline requires. // So we expect the transform to skip all of them even though // they are used in JSX. expect( transform(` const A = require('A'); const B = foo ? require('X') : require('Y'); const C = requireCond(gk, 'C'); const D = import('D'); export default function App() { return (
); } `), ).toMatchSnapshot(); }); it('can handle implicit arrow returns', () => { expect( transform(` export default () => useContext(X); export const Foo = () => useContext(X); module.exports = () => useContext(X); const Bar = () => useContext(X); const Baz = memo(() => useContext(X)); const Qux = () => (0, useContext(X)); `), ).toMatchSnapshot(); }); });