From 7562368b4bad3dcb6cb61bfe1e6a49021de4b6d6 Mon Sep 17 00:00:00 2001 From: Alexander Fedyashov Date: Fri, 24 Mar 2017 22:26:06 +0200 Subject: [PATCH] style(tests): refactor tests (#1501) --- test/specs/commonTests.js | 1086 ----------------- test/specs/commonTests/classNameHelpers.js | 50 + test/specs/commonTests/commonHelpers.js | 14 + test/specs/commonTests/componentInfo.js | 63 + test/specs/commonTests/hasSubComponents.js | 16 + test/specs/commonTests/hasUIClassName.js | 20 + .../commonTests/implementsClassNameProps.js | 146 +++ .../commonTests/implementsCommonProps.js | 278 +++++ .../commonTests/implementsCreateMethod.js | 69 ++ .../commonTests/implementsShorthandProp.js | 101 ++ test/specs/commonTests/index.js | 10 + test/specs/commonTests/isConformant.js | 342 ++++++ test/specs/commonTests/rendersChildren.js | 34 + 13 files changed, 1143 insertions(+), 1086 deletions(-) delete mode 100644 test/specs/commonTests.js create mode 100644 test/specs/commonTests/classNameHelpers.js create mode 100644 test/specs/commonTests/commonHelpers.js create mode 100644 test/specs/commonTests/componentInfo.js create mode 100644 test/specs/commonTests/hasSubComponents.js create mode 100644 test/specs/commonTests/hasUIClassName.js create mode 100644 test/specs/commonTests/implementsClassNameProps.js create mode 100644 test/specs/commonTests/implementsCommonProps.js create mode 100644 test/specs/commonTests/implementsCreateMethod.js create mode 100644 test/specs/commonTests/implementsShorthandProp.js create mode 100644 test/specs/commonTests/index.js create mode 100644 test/specs/commonTests/isConformant.js create mode 100644 test/specs/commonTests/rendersChildren.js diff --git a/test/specs/commonTests.js b/test/specs/commonTests.js deleted file mode 100644 index ae583bd65d..0000000000 --- a/test/specs/commonTests.js +++ /dev/null @@ -1,1086 +0,0 @@ -import faker from 'faker' -import _ from 'lodash' -import path from 'path' -import React, { createElement, isValidElement } from 'react' -import ReactDOMServer from 'react-dom/server' - -import { - createShorthand, - META, - numberToWord, - SUI, -} from 'src/lib' -import { consoleUtil, sandbox, syntheticEvent } from 'test/utils' -import * as semanticUIReact from 'semantic-ui-react' - -import Button from 'src/elements/Button' -import Icon from 'src/elements/Icon' -import Image from 'src/elements/Image' -import Label from 'src/elements/Label' - -const commonTestHelpers = (testName, Component) => { - const throwError = msg => { - throw new Error(`${testName}: ${msg} \n Component: ${Component && Component.name}`) - } - - const assertRequired = (required, description) => { - return required || throwError(`Required ${description}, got: ${required} (${typeof required})`) - } - - return { - throwError, - assertRequired, - } -} - -const componentCtx = require.context( - '../../src/', - true, - /(addons|collections|elements|modules|views).\w+.(?!index)\w+.js/ -) - -const componentInfo = componentCtx.keys().map(key => { - const Component = componentCtx(key).default - const componentType = typeof Component - - const { throwError } = commonTestHelpers('componentInfo', Component) - - if (componentType !== 'function') { - throwError([ - `${key} is not properly exported.`, - `Components should export a class or function, got: ${componentType}.`, - ].join(' ')) - } - - const { _meta, prototype } = Component - - if (!_meta) { - throwError([ - 'Component is missing a static _meta object property. This should help identify it:', - `Rendered:\n${ReactDOMServer.renderToStaticMarkup()}`, - ].join('\n')) - } - - const constructorName = prototype.constructor.name - const filePath = key - const filename = path.basename(key) - const filenameWithoutExt = path.basename(key, '.js') - const subComponentName = _.has(_meta, 'parent') && _.has(_meta, 'name') - ? _meta.name.replace(_meta.parent, '') - : null - - // name of the component, sub component, or plural parent for sub component groups - const componentClassName = ( - META.isChild(Component) - ? subComponentName.replace(/Group$/, `${_meta.parent}s`) - : _meta.name - ).toLowerCase() - - return { - _meta, - Component, - constructorName, - componentClassName, - subComponentName, - filePath, - filename, - filenameWithoutExt, - } -}) - -/** - * Assert Component conforms to guidelines that are applicable to all components. - * @param {React.Component|Function} Component A component that should conform. - * @param {Object} [options={}] - * @param {Object} [options.eventTargets={}] Map of events and the child component to target. - * @param {Object} [options.requiredProps={}] Props required to render Component without errors or warnings. - */ -export const isConformant = (Component, options = {}) => { - const { eventTargets = {}, requiredProps = {} } = options - const { throwError } = commonTestHelpers('isConformant', Component) - - // tests depend on Component constructor names, enforce them - if (!Component.prototype.constructor.name) { - throwError([ - 'Component is not a named function. This should help identify it:', - `static _meta = ${JSON.stringify(Component._meta, null, 2)}`, - `Rendered:\n${ReactDOMServer.renderToStaticMarkup()}`, - ].join('\n')) - } - - // extract componentInfo for this component - const { - _meta, - constructorName, - componentClassName, - filenameWithoutExt, - } = _.find(componentInfo, i => i.constructorName === Component.prototype.constructor.name) - - // ---------------------------------------- - // Class and file name - // ---------------------------------------- - it(`constructor name matches filename "${constructorName}"`, () => { - constructorName.should.equal(filenameWithoutExt) - }) - - // ---------------------------------------- - // Is exported or private - // ---------------------------------------- - // detect components like: semanticUIReact.H1 - const isTopLevelAPIProp = _.has(semanticUIReact, constructorName) - - // detect sub components like: semanticUIReact.Form.Field (ie FormField component) - // Build a path by following _meta.parents to the root: - // ['Form', 'FormField', 'FormTextArea'] - let apiPath = [] - let meta = _meta - while (meta) { - apiPath.unshift(meta.name) - meta = _.get(semanticUIReact, [meta.parent, '_meta']) - } - // Remove parent name from paths: - // ['Form', 'Field', 'TextArea'] - apiPath = apiPath.reduce((acc, next) => ( - [...acc, next.replace(acc.join(''), '')] - ), []) - - // find the apiPath in the semanticUIReact object - const isSubComponent = _.isFunction(_.get(semanticUIReact, apiPath)) - - if (META.isPrivate(constructorName)) { - it('is not exported as a component nor sub component', () => { - expect(isTopLevelAPIProp).to.equal( - false, - `"${constructorName}" is private (starts with "_").` + - ' It cannot be exposed on the top level API' - ) - - expect(isSubComponent).to.equal( - false, - `"${constructorName}" is private (starts with "_").` + - ' It cannot be a static prop of another component (sub-component)' - ) - }) - } else { - // require all components to be exported at the top level - it('is exported at the top level', () => { - expect(isTopLevelAPIProp).to.equal(true, [ - `"${constructorName}" must be exported at top level.`, - 'Export it in `src/index.js`.', - ].join(' ')) - }) - } - - if (_meta.parent) { - it('is a static component on its parent', () => { - expect(isSubComponent).to.equal( - true, - `\`${constructorName}\` is a child component (has a _meta.parent).` + - ` It must be a static prop of its parent \`${_meta.parent}\`` - ) - }) - } - - // ---------------------------------------- - // Props - // ---------------------------------------- - it('spreads user props', () => { - // JSX does not render custom html attributes so we prefix them with data-*. - // https://facebook.github.io/react/docs/jsx-gotchas.html#custom-html-attributes - const props = { - [`data-${_.kebabCase(faker.hacker.noun())}`]: faker.hacker.verb(), - } - - // descendants() accepts an enzyme - // props should be spread on some descendant - // we find the descendant with spread props via a matching props object selector - // we do not test Component for props, of course they exist as we are spreading them - shallow() - .should.have.descendants(props) - }) - - describe('"as" prop (common)', () => { - it('renders the component as HTML tags or passes "as" to the next component', () => { - // silence element nesting warnings - consoleUtil.disableOnce() - - const tags = ['a', 'em', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'i', 'p', 'span', 'strong'] - try { - tags.forEach((tag) => { - shallow() - .should.have.tagName(tag) - }) - } catch (err) { - tags.forEach((tag) => { - const wrapper = shallow() - wrapper.type().should.not.equal(Component) - wrapper.should.have.prop('as', tag) - }) - } - }) - - it('renders as a functional component or passes "as" to the next component', () => { - const MyComponent = () => null - - try { - shallow() - .type() - .should.equal(MyComponent) - } catch (err) { - const wrapper = shallow() - wrapper.type().should.not.equal(Component) - wrapper.should.have.prop('as', MyComponent) - } - }) - - it('renders as a ReactClass or passes "as" to the next component', () => { - class MyComponent extends React.Component { - render() { - return
- } - } - - try { - shallow() - .type() - .should.equal(MyComponent) - } catch (err) { - const wrapper = shallow() - wrapper.type().should.not.equal(Component) - wrapper.should.have.prop('as', MyComponent) - } - }) - - it('passes extra props to the component it is renders as', () => { - const MyComponent = () => null - - shallow() - .should.have.descendants('[data-extra-prop="foo"]') - }) - }) - - describe('handles props', () => { - it('defines handled props in Component.handledProps', () => { - Component.should.have.any.keys('handledProps') - Component.handledProps.should.be.an('array') - }) - - it('Component.handledProps includes all handled props', () => { - const computedProps = _.union( - Component.autoControlledProps, - _.keys(Component.defaultProps), - _.keys(Component.propTypes), - ) - const expectedProps = _.uniq(computedProps).sort() - - Component.handledProps.should.to.deep.equal(expectedProps, - 'It seems that not all props were defined in Component.handledProps, you need to check that they are equal ' + - 'to the union of Component.autoControlledProps and keys of Component.defaultProps and Component.propTypes' - ) - }) - }) - - // ---------------------------------------- - // Events - // ---------------------------------------- - it('handles events transparently', () => { - // Events should be handled transparently, working just as they would in vanilla React. - // Example, both of these handler()s should be called with the same event: - // - //