/** * @fileoverview Prevent multiple component definition per file * @author Yannick Croissant */ 'use strict'; // ------------------------------------------------------------------------------ // Requirements // ------------------------------------------------------------------------------ const RuleTester = require('../../helpers/ruleTester'); const rule = require('../../../lib/rules/no-multi-comp'); const parsers = require('../../helpers/parsers'); const parserOptions = { ecmaVersion: 2018, sourceType: 'module', ecmaFeatures: { jsx: true, }, }; // ------------------------------------------------------------------------------ // Tests // ------------------------------------------------------------------------------ const ruleTester = new RuleTester({ parserOptions }); ruleTester.run('no-multi-comp', rule, { valid: parsers.all([ { code: ` var Hello = require('./components/Hello'); var HelloJohn = createReactClass({ render: function() { return ; } }); `, }, { code: ` class Hello extends React.Component { render() { return
Hello {this.props.name}
; } } `, }, { code: ` var Heading = createReactClass({ render: function() { return (
{this.props.buttons.map(function(button, index) { return
); } }); `, }, { code: ` function Hello(props) { return
Hello {props.name}
; } function HelloAgain(props) { return
Hello again {props.name}
; } `, options: [{ ignoreStateless: true }], }, { code: ` function Hello(props) { return
Hello {props.name}
; } class HelloJohn extends React.Component { render() { return ; } } `, options: [{ ignoreStateless: true }], }, { // multiple non-components code: ` import React, { createElement } from "react" const helperFoo = () => { return true; }; function helperBar() { return false; }; function RealComponent() { return createElement("img"); }; `, parserOptions: Object.assign({ sourceType: 'module' }, parserOptions), }, { code: ` const Hello = React.memo(function(props) { return
Hello {props.name}
; }); class HelloJohn extends React.Component { render() { return ; } } `, options: [{ ignoreStateless: true }], }, { code: ` class StoreListItem extends React.PureComponent { // A bunch of stuff here } export default React.forwardRef((props, ref) => ); `, options: [{ ignoreStateless: false }], }, { code: ` class StoreListItem extends React.PureComponent { // A bunch of stuff here } export default React.forwardRef((props, ref) => { return }); `, options: [{ ignoreStateless: false }], }, { code: ` const HelloComponent = (props) => { return
; } export default React.forwardRef((props, ref) => ); `, options: [{ ignoreStateless: false }], }, { code: ` class StoreListItem extends React.PureComponent { // A bunch of stuff here } export default React.forwardRef( function myFunction(props, ref) { return ; } ); `, options: [{ ignoreStateless: true }], }, { code: ` const HelloComponent = (props) => { return
; } class StoreListItem extends React.PureComponent { // A bunch of stuff here } export default React.forwardRef( function myFunction(props, ref) { return ; } ); `, options: [{ ignoreStateless: true }], }, { code: ` const HelloComponent = (props) => { return
; } export default React.memo((props, ref) => ); `, options: [{ ignoreStateless: true }], }, { code: ` import React from 'react'; function memo() { var outOfScope = "hello" return null; } class ComponentY extends React.Component { memoCities = memo((cities) => cities.map((v) => ({ label: v }))); render() { return (
Counter
); } } `, features: ['class fields'], }, { code: ` const MenuList = forwardRef(({onClose, ...props}, ref) => { const {t} = useTranslation(); const handleLogout = useLogoutHandler(); const onLogout = useCallback(() => { onClose(); handleLogout(); }, [onClose, handleLogout]); return ( {t('global-logout')} ); }); MenuList.displayName = 'MenuList'; MenuList.propTypes = { onClose: PropTypes.func, }; MenuList.defaultProps = { onClose: () => null, }; export default MenuList; `, }, { code: ` const MenuList = forwardRef(({ onClose, ...props }, ref) => { const onLogout = useCallback(() => { onClose() }, [onClose]) return ( Logout ) }) MenuList.displayName = 'MenuList' MenuList.propTypes = { onClose: PropTypes.func } MenuList.defaultProps = { onClose: () => null } export default MenuList `, }, ]), invalid: parsers.all([ { code: ` var Hello = createReactClass({ render: function() { return
Hello {this.props.name}
; } }); var HelloJohn = createReactClass({ render: function() { return ; } }); `.split('\n').join('\r'), errors: [ { messageId: 'onlyOneComponent', line: 7, }, ], }, { code: ` class Hello extends React.Component { render() { return
Hello {this.props.name}
; } } class HelloJohn extends React.Component { render() { return ; } } class HelloJohnny extends React.Component { render() { return ; } } `.split('\n').join('\r'), errors: [ { messageId: 'onlyOneComponent', line: 7, }, { messageId: 'onlyOneComponent', line: 12, }, ], }, { code: ` function Hello(props) { return
Hello {props.name}
; } function HelloAgain(props) { return
Hello again {props.name}
; } `, errors: [ { messageId: 'onlyOneComponent', line: 5, }, ], }, { code: ` function Hello(props) { return
Hello {props.name}
; } class HelloJohn extends React.Component { render() { return ; } } `, errors: [ { messageId: 'onlyOneComponent', line: 5, }, ], }, { code: ` export default { RenderHello(props) { let {name} = props; return
{name}
; }, RenderHello2(props) { let {name} = props; return
{name}
; } }; `, errors: [ { messageId: 'onlyOneComponent', line: 7, }, ], }, { code: ` exports.Foo = function Foo() { return <> } exports.createSomeComponent = function createSomeComponent(opts) { return function Foo() { return <>{opts.a} } } `, features: ['fragment'], errors: [ { messageId: 'onlyOneComponent', line: 7, }, ], }, { code: ` class StoreListItem extends React.PureComponent { // A bunch of stuff here } export default React.forwardRef((props, ref) =>
); `, options: [{ ignoreStateless: false }], errors: [ { messageId: 'onlyOneComponent', line: 5, }, ], }, { code: ` const HelloComponent = (props) => { return
; } const HelloComponent2 = React.forwardRef((props, ref) =>
); `, options: [{ ignoreStateless: false }], errors: [ { messageId: 'onlyOneComponent', line: 5, }, ], }, { code: ` const HelloComponent = (0, (props) => { return
; }); const HelloComponent2 = React.forwardRef((props, ref) => <>); `, options: [{ ignoreStateless: false }], features: ['fragment'], errors: [ { messageId: 'onlyOneComponent', line: 5, }, ], }, { code: ` const forwardRef = React.forwardRef; const HelloComponent = (0, (props) => { return
; }); const HelloComponent2 = forwardRef((props, ref) => ); `, options: [{ ignoreStateless: false }], errors: [ { messageId: 'onlyOneComponent', line: 6, }, ], }, { code: ` const memo = React.memo; const HelloComponent = (props) => { return
; }; const HelloComponent2 = memo((props) => ); `, options: [{ ignoreStateless: false }], errors: [ { messageId: 'onlyOneComponent', line: 6, }, ], }, { code: ` const {forwardRef} = React; const HelloComponent = (0, (props) => { return
; }); const HelloComponent2 = forwardRef((props, ref) => ); `, options: [{ ignoreStateless: false }], errors: [ { messageId: 'onlyOneComponent', line: 6, }, ], }, { code: ` const {memo} = React; const HelloComponent = (0, (props) => { return
; }); const HelloComponent2 = memo((props) => ); `, options: [{ ignoreStateless: false }], errors: [ { messageId: 'onlyOneComponent', line: 6, }, ], }, { code: ` import React, { memo } from 'react'; const HelloComponent = (0, (props) => { return
; }); const HelloComponent2 = memo((props) => ); `, options: [{ ignoreStateless: false }], errors: [ { messageId: 'onlyOneComponent', line: 6, }, ], }, { code: ` import {forwardRef} from 'react'; const HelloComponent = (0, (props) => { return
; }); const HelloComponent2 = forwardRef((props, ref) => ); `, options: [{ ignoreStateless: false }], errors: [ { messageId: 'onlyOneComponent', line: 6, }, ], }, { code: ` const { memo } = require('react'); const HelloComponent = (0, (props) => { return
; }); const HelloComponent2 = memo((props) => ); `, options: [{ ignoreStateless: false }], errors: [ { messageId: 'onlyOneComponent', line: 6, }, ], }, { code: ` const {forwardRef} = require('react'); const HelloComponent = (0, (props) => { return
; }); const HelloComponent2 = forwardRef((props, ref) => ); `, options: [{ ignoreStateless: false }], errors: [ { messageId: 'onlyOneComponent', line: 6, }, ], }, { code: ` const forwardRef = require('react').forwardRef; const HelloComponent = (0, (props) => { return
; }); const HelloComponent2 = forwardRef((props, ref) => ); `, options: [{ ignoreStateless: false }], errors: [ { messageId: 'onlyOneComponent', line: 6, }, ], }, { code: ` const memo = require('react').memo; const HelloComponent = (0, (props) => { return
; }); const HelloComponent2 = memo((props) => ); `, options: [{ ignoreStateless: false }], errors: [ { messageId: 'onlyOneComponent', line: 6, }, ], }, { code: ` import Foo, { memo, forwardRef } from 'foo'; const Text = forwardRef(({ text }, ref) => { return
{text}
; }) const Label = memo(() => ); `, settings: { react: { pragma: 'Foo', }, }, errors: [{ messageId: 'onlyOneComponent' }], }, ]), });