lucid-ui
Version:
A UI component library from AppNexus.
290 lines (258 loc) • 14.4 kB
JavaScript
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;
}
});