UNPKG

lucid-ui

Version:

A UI component library from AppNexus.

290 lines (258 loc) 14.4 kB
import _last from "lodash/last"; import _each from "lodash/each"; import _reject from "lodash/reject"; import _castArray from "lodash/castArray"; import _flatMap from "lodash/flatMap"; import _compact from "lodash/compact"; import _map from "lodash/map"; import _flow from "lodash/flow"; import _isFunction from "lodash/isFunction"; import _startsWith from "lodash/startsWith"; import _pickBy from "lodash/pickBy"; import _forEach from "lodash/forEach"; import _includes from "lodash/includes"; import _every from "lodash/every"; import _assign from "lodash/assign"; import _identity from "lodash/identity"; import _constant from "lodash/constant"; function _construct(Parent, args, Class) { if (_isNativeReflectConstruct()) { _construct = Reflect.construct; } else { _construct = function _construct(Parent, args, Class) { var a = [null]; a.push.apply(a, args); var Constructor = Function.bind.apply(Parent, a); var instance = new Constructor(); if (Class) _setPrototypeOf(instance, Class.prototype); return instance; }; } return _construct.apply(null, arguments); } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } import sinon from 'sinon'; import { parse } from 'path'; import React from 'react'; import PropTypes from 'prop-types'; import { mount, shallow } from 'enzyme'; import assert from 'assert'; import glob from 'glob'; import * as lucid from '../index'; // Common tests for all our components export function common(Component) { var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, _ref$getDefaultProps = _ref.getDefaultProps, getDefaultProps = _ref$getDefaultProps === void 0 ? _constant({}) : _ref$getDefaultProps, _ref$exemptFunctionPr = _ref.exemptFunctionProps, exemptFunctionProps = _ref$exemptFunctionPr === void 0 ? [] : _ref$exemptFunctionPr, _ref$exemptChildCompo = _ref.exemptChildComponents, exemptChildComponents = _ref$exemptChildCompo === void 0 ? [] : _ref$exemptChildCompo, _ref$selectRoot = _ref.selectRoot, selectRoot = _ref$selectRoot === void 0 ? _identity : _ref$selectRoot, _ref$noExport = _ref.noExport, noExport = _ref$noExport === void 0 ? false : _ref$noExport; function generateDefaultProps() { var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; return _assign({}, getDefaultProps(), props); } describe('[common]', function () { if (!Component) { throw new Error('An undefined component was passed to generic tests.'); } if (Component._isLucidHybridComponent) { throw new Error("You're trying to run generic tests on a hybrid component which is bad and won't work and will make you cry. Check your spec files for ".concat(Component.displayName, " and import the raw component instead of the hybrid version.")); } it('should have a `displayName` defined', function () { assert(Component.displayName); }); it('should pass through styles to the root element', function () { var style = { backgroundColor: '#f0f' }; var wrapper = shallow( /*#__PURE__*/React.createElement(Component, _extends({}, generateDefaultProps(), { style: style })), { disableLifecycleMethods: true }); var rootWrapper = selectRoot(wrapper).first(); var rootStyle = rootWrapper.prop('style'); assert(_every(style, function (val, key) { return val === rootStyle[key]; }), 'root style must contain passed styles'); }); it('should pass through `className`', function () { var expectedClass = 'rAnDoM'; var wrapper = shallow( /*#__PURE__*/React.createElement(Component, _extends({}, generateDefaultProps(), { className: expectedClass })), { disableLifecycleMethods: true }); var rootWrapper = selectRoot(wrapper).first(); var classNames = rootWrapper.prop('className').split(' '); assert(_includes(classNames, expectedClass), "'".concat(classNames, "' should include '").concat(expectedClass, "'")); }); it('should have an application scoped base class', function () { var expectedClass = 'lucid-' + Component.displayName; var wrapper = shallow( /*#__PURE__*/React.createElement(Component, generateDefaultProps()), { disableLifecycleMethods: true }); var rootWrapper = selectRoot(wrapper).first(); var classNames = rootWrapper.prop('className').split(' '); assert(_includes(classNames, expectedClass), "'".concat(classNames, "' should include '").concat(Component.displayName, "'")); }); it('should have only application scoped classes', function () { var wrapper = shallow( /*#__PURE__*/React.createElement(Component, generateDefaultProps()), { disableLifecycleMethods: true }); var rootWrapper = selectRoot(wrapper).first(); var parentClasses = rootWrapper.prop('className').split(' '); var childrenClasses = rootWrapper.children().reduce(function (acc, node) { if (!node.prop('className')) { return acc; } return acc.concat(node.prop('className').split(' ')); }, []); var allClasses = parentClasses.concat(childrenClasses); _forEach(allClasses, function (className) { assert(_includes(className, "lucid-".concat(Component.displayName)), "".concat(className, " must be scoped")); }); }); describe('function propTypes', function () { var funcProps = _pickBy(Component.propTypes, function (propType) { return propType === PropTypes.func; }); _forEach(funcProps, function (propType, propName) { it("".concat(propName, " should only use onX convention for function proptypes"), function () { assert(_startsWith(propName, 'on') || _includes(exemptFunctionProps, propName), "".concat(propName, " must follow onX convention")); }); }); }); describe('child components', function () { // Child components are all function types which start with a capital letter var childComponents = _pickBy(Component, function (value, key) { return /^[A-Z]/.test(key) && _isFunction(value); }); describe('propNames in propTypes', function () { _flow(function (x) { return _map(x, 'propName'); }, function (x) { return _compact(x); }, function (x) { return _flatMap(x, _castArray); }, function (x) { return _reject(x, function (propName) { return _includes(exemptChildComponents, propName); }); }, function (x) { return _forEach(x, function (propName) { it("should include ".concat(propName, " in propTypes"), function () { assert(Component.propTypes[propName], "must include ".concat(propName, " in propTypes")); }); }); })(childComponents); }); }); describe('example testing', function () { var fileNames = glob.sync("./src/components/**/".concat(Component.displayName, "/examples/*.@(j|t)sx")); _each(fileNames, function (path) { var lib = require('../../' + path.replace('.tsx', '')); var Example = lib.default; var title = parse(path).name; it("should match snapshot(s) for ".concat(title), function () { var shallowExample = shallow( /*#__PURE__*/React.createElement(Example, null), { disableLifecycleMethods: true }); // If the root of the example is an instance of the Component under test, snapshot it. // Otherwise, look under the root for instances of the Component and snapshot those. if (shallowExample.is(Component.displayName)) { expect(shallow( /*#__PURE__*/React.createElement(Component, shallowExample.props()), { disableLifecycleMethods: true })).toMatchSnapshot(); } else { shallowExample.find(Component.displayName).forEach(function (example) { expect(shallow( /*#__PURE__*/React.createElement(Component, example.props()), { disableLifecycleMethods: true })).toMatchSnapshot(); }); } }); }); }); // Only run this test if it's a public component if (!Component._isPrivate && !noExport) { it('should be available as an exported module from index.ts', function () { assert(lucid[Component.displayName]); }); } }); } // Common tests for all our icon components export function icons(Component) { describe('[icon]', function () { it('should add the correct class for isClickable', function () { var wrapper = mount( /*#__PURE__*/React.createElement(Component, { isClickable: true })); var targetClassName = 'lucid-Icon-is-clickable'; assert(wrapper.find('svg').hasClass(targetClassName), "Missing '".concat(targetClassName, "' class")); }); }); } // Common tests for all control components export function controls(Component, _ref2) { var callbackName = _ref2.callbackName, controlSelector = _ref2.controlSelector, eventType = _ref2.eventType, _ref2$additionalProps = _ref2.additionalProps, additionalProps = _ref2$additionalProps === void 0 ? {} : _ref2$additionalProps; // Use DOM tests here since some of our controls use dom events under the hood describe('[control]', function () { it('should callback with `event` and `props`', function () { var expectedSpecialProp = 32; var props = _objectSpread(_defineProperty({ specialProp: expectedSpecialProp }, callbackName, sinon.spy()), additionalProps); var wrapper = mount( /*#__PURE__*/React.createElement(Component, props)); wrapper.find(controlSelector).first().simulate(eventType); // Last argument should be an object with `uniqueId` and `event` var _last2 = _last(props[callbackName].args[0]), specialProp = _last2.props.specialProp, event = _last2.event; assert(event, 'missing event'); assert.equal(specialProp, expectedSpecialProp, 'incorrect or missing specialProp'); }); }); } // Common tests for all Functional Components // // These tests are intended to help us make sure our FCs are shaped corrected. // They are necessary because there isn't a perfect way to get the defaultProps // to be factored in correctly yet with React/TypeScript: // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30695#issuecomment-474780159 export function functionalComponents(FC) { // Use DOM tests here since some of our controls use dom events under the hood describe('[functionalComponent]', function () { it('should have the correct `peek` properties', function () { expect(FC.propName === undefined || typeof FC.propName === 'string').toBe(true); expect(FC._isPrivate === undefined || typeof FC._isPrivate === 'boolean').toBe(true); expect(_typeof(FC.peek)).toBe('object'); expect(_typeof(FC.peek.description)).toBe('string'); expect(FC.peek.extend === undefined || typeof FC.peek.extend === 'string').toBe(true); expect(FC.peek.extend === undefined || typeof FC.peek.extend === 'string').toBe(true); expect(FC.peek.categories === undefined || Array.isArray(FC.peek.categories)).toBe(true); expect(FC.peek.madeFrom === undefined || Array.isArray(FC.peek.madeFrom)).toBe(true); }); }); } var NativeDate = global.Date; var createMockDateClass = function createMockDateClass() { for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _assign(function MockDate() { // @ts-ignore return _construct(NativeDate, args); }, { UTC: NativeDate.UTC, parse: NativeDate.parse, // @ts-ignore now: function now() { return _construct(NativeDate, args).getTime(); }, prototype: NativeDate.prototype }); }; export var mockDate = _assign(function () { // @ts-ignore global.Date = createMockDateClass.apply(void 0, arguments); }, { restore: function restore() { global.Date = NativeDate; } });