UNPKG

@primer/components

Version:
273 lines (221 loc) • 7.02 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.mount = mount; exports.render = render; exports.renderRoot = renderRoot; exports.renderClasses = renderClasses; exports.rendersClass = rendersClass; exports.px = px; exports.percent = percent; exports.renderStyles = renderStyles; exports.getComputedStyles = getComputedStyles; exports.getProps = getProps; exports.getClassName = getClassName; exports.getClasses = getClasses; exports.loadCSS = loadCSS; exports.unloadCSS = unloadCSS; exports.behavesAsComponent = behavesAsComponent; exports.checkExports = checkExports; exports.COMPONENT_DISPLAY_NAME_REGEX = void 0; var _react = _interopRequireDefault(require("react")); var _util = require("util"); var _reactTestRenderer = _interopRequireDefault(require("react-test-renderer")); var _enzyme = _interopRequireDefault(require("enzyme")); var _enzymeAdapterReact = _interopRequireDefault(require("@wojtekmaj/enzyme-adapter-react-17")); var _ = require(".."); var _theme = _interopRequireDefault(require("../theme")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // eslint-disable-next-line @typescript-eslint/no-var-requires const readFile = (0, _util.promisify)(require('fs').readFile); const COMPONENT_DISPLAY_NAME_REGEX = /^[A-Z][A-Za-z]+(\.[A-Z][A-Za-z]+)*$/; exports.COMPONENT_DISPLAY_NAME_REGEX = COMPONENT_DISPLAY_NAME_REGEX; _enzyme.default.configure({ adapter: new _enzymeAdapterReact.default() }); function mount(component) { return _enzyme.default.mount(component); } /** * Render the component (a React.createElement() or JSX expression) * into its intermediate object representation with 'type', * 'props', and 'children' keys * * The returned object can be matched with expect().toEqual(), e.g. * * ```js * expect(render(<Foo />)).toEqual(render(<div foo='bar' />)) * ``` */ function render(component, theme = _theme.default) { return _reactTestRenderer.default.create( /*#__PURE__*/_react.default.createElement(_.ThemeProvider, { theme: theme }, component)).toJSON(); } /** * Render the component (a React.createElement() or JSX expression) * using react-test-renderer and return the root node * ``` */ function renderRoot(component) { return _reactTestRenderer.default.create(component).root; } /** * Get the HTML class names rendered by the component instance * as an array. * * ```js * expect(renderClasses(<div className='a b' />)) * .toEqual(['a', 'b']) * ``` */ function renderClasses(component) { const { props: { className } } = render(component); return className ? className.trim().split(' ') : []; } /** * Returns true if a node renders with a single class. */ function rendersClass(node, klass) { return renderClasses(node).includes(klass); } function px(value) { return typeof value === 'number' ? `${value}px` : value; } function percent(value) { return typeof value === 'number' ? `${value}%` : value; } function renderStyles(node) { const { props: { className } } = render(node); return getComputedStyles(className); } function getComputedStyles(className) { const div = document.createElement('div'); div.className = className; const computed = {}; for (const sheet of document.styleSheets) { // CSSRulesLists assumes every rule is a CSSRule, not a CSSStyleRule for (const rule of sheet.cssRules) { if (rule instanceof CSSMediaRule) { readMedia(rule); } else if (rule instanceof CSSStyleRule) { readRule(rule, computed); } else {// console.warn('rule.type =', rule.type) } } } return computed; function matchesSafe(node, selector) { if (!selector) { return false; } try { return node.matches(selector); } catch (error) { return false; } } function readRule(rule, dest) { if (matchesSafe(div, rule.selectorText)) { const { style } = rule; for (let i = 0; i < style.length; i++) { const prop = style[i]; dest[prop] = style.getPropertyValue(prop); } } else {// console.warn('no match:', rule.selectorText) } } function readMedia(mediaRule) { const key = `@media ${mediaRule.media[0]}`; // const dest = computed[key] || (computed[key] = {}) const dest = {}; for (const rule of mediaRule.cssRules) { if (rule instanceof CSSStyleRule) { readRule(rule, dest); } } // Don't add media rule to computed styles // if no styles were actually applied if (Object.keys(dest).length > 0) { computed[key] = dest; } } } /** * This provides a layer of compatibility between the render() function from * react-test-renderer and Enzyme's mount() */ function getProps(node) { return typeof node.props === 'function' ? node.props() : node.props; } function getClassName(node) { return getProps(node).className; } function getClasses(node) { const className = getClassName(node); return className ? className.trim().split(/ +/) : []; } async function loadCSS(path) { const css = await readFile(require.resolve(path), 'utf8'); const style = document.createElement('style'); style.setAttribute('data-path', path); style.textContent = css; document.head.appendChild(style); return style; } function unloadCSS(path) { const style = document.querySelector(`style[data-path="${path}"]`); if (style) { style.remove(); return true; } } // If a component requires certain props or other conditions in order // to render without errors, you can pass a `toRender` function that // returns an element ready to be rendered. function behavesAsComponent({ Component, toRender, options }) { options = options || {}; const getElement = () => toRender ? toRender() : /*#__PURE__*/_react.default.createElement(Component, null); if (!options.skipSx) { it('implements sx prop behavior', () => { expect(getElement()).toImplementSxBehavior(); }); } if (!options.skipAs) { it('respects the as prop', () => { const As = /*#__PURE__*/_react.default.forwardRef((_props, ref) => /*#__PURE__*/_react.default.createElement("div", { className: "as-component", ref: ref })); const elem = /*#__PURE__*/_react.default.cloneElement(getElement(), { as: As }); expect(render(elem)).toEqual(render( /*#__PURE__*/_react.default.createElement(As, null))); }); } it('sets a valid displayName', () => { expect(Component.displayName).toMatch(COMPONENT_DISPLAY_NAME_REGEX); }); it('renders consistently', () => { expect(render(getElement())).toMatchSnapshot(); }); } // eslint-disable-next-line @typescript-eslint/no-explicit-any function checkExports(path, exports) { it('has declared exports', () => { // eslint-disable-next-line @typescript-eslint/no-var-requires const mod = require(`../${path}`); expect(mod).toSetExports(exports); }); }