UNPKG

@instructure/ui-test-utils

Version:

A UI testing library made by Instructure Inc.

225 lines (221 loc) • 7.71 kB
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; /* * The MIT License (MIT) * * Copyright (c) 2015 - present Instructure, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ import { nanoid } from 'nanoid'; import { generatePropCombinations } from './generatePropCombinations'; import React from 'react'; /** * Generates examples for the given component based on the given configuration. * @param Component A React component * @param config A configuration object (stored in xy.examples.jsx files in InstUI) * @returns Array of examples broken into sections and pages if configured to do so. * @module generateComponentExamples * @private * */ export function generateComponentExamples(Component, config) { const sectionProp = config.sectionProp, excludeProps = config.excludeProps, filter = config.filter; const PROPS_CACHE = []; const sections = []; const maxExamples = config.maxExamples ? config.maxExamples : 500; let exampleCount = 0; let propValues = {}; const getParameters = page => { const examples = page.examples; const index = page.index; let parameters = {}; if (typeof config.getParameters === 'function') { parameters = { ...config.getParameters({ examples, index }) }; } return parameters; }; /** * Merges the auto-generated props with ones in the examples files specified * by the `getComponentProps()` method; props from the example files have * priority */ const mergeComponentPropsFromConfig = props => { let componentProps = props; // TODO this code is so complicated because getComponentProps(props) can return // different values based on its props parameter. // If it would always return the same thing then we could reduce the // number of combinations generated by generatePropCombinations() by // getComponentProps() reducing some to 1 value, it would also remove the // need of PROPS_CACHE and duplicate checks. // InstUI is not using the 'props' param of getComponentProps(), but others are if (typeof config.getComponentProps === 'function') { componentProps = { ...componentProps, ...config.getComponentProps(props) }; } return componentProps; }; const getExampleProps = props => { let exampleProps = {}; if (typeof config.getExampleProps === 'function') { exampleProps = { ...config.getExampleProps(props) }; } return exampleProps; }; const addPage = section => { const page = { examples: [], index: section.pages.length }; section.pages.push(page); return page; }; const addExample = (sectionName, example) => { let section = sections.find(section => section.sectionName === sectionName); if (!section) { section = { sectionName: sectionName, propName: sectionProp, propValue: sectionName, pages: [] }; sections.push(section); } let page = section.pages[section.pages.length - 1]; let maxExamplesPerPage = config.maxExamplesPerPage; if (typeof maxExamplesPerPage === 'function') { maxExamplesPerPage = maxExamplesPerPage(sectionName); } if (!page) { page = addPage(section); } else if (maxExamplesPerPage && page.examples.length % maxExamplesPerPage === 0 && page.examples.length > 0) { page = addPage(section); } page.examples.push(example); }; // Serializes the given recursively, faster than JSON.stringify() const fastSerialize = props => { const strArr = []; objToString(props, strArr); return strArr.join(''); }; const objToString = (currObject, currString) => { if (!currObject) { return; } if (/*#__PURE__*/React.isValidElement(currObject)) { currString.push(JSON.stringify(currObject)); } else if (typeof currObject === 'object') { for (const _ref of Object.entries(currObject)) { var _ref2 = _slicedToArray(_ref, 2); const key = _ref2[0]; const value = _ref2[1]; currString.push(key); objToString(value, currString); } } else { currString.push(currObject); } }; const maybeAddExample = props => { const componentProps = mergeComponentPropsFromConfig(props); const ignore = typeof filter === 'function' ? filter(componentProps) : false; if (ignore) { return; } const propsString = fastSerialize(componentProps); if (!PROPS_CACHE.includes(propsString)) { const key = nanoid(); const exampleProps = getExampleProps(props); exampleCount++; if (exampleCount < maxExamples) { PROPS_CACHE.push(propsString); let sectionName = 'Examples'; if (sectionProp && componentProps[sectionProp]) { sectionName = componentProps[sectionProp]; } addExample(sectionName, { Component, componentProps, exampleProps, key }); } } }; if (isEmpty(config.propValues)) { maybeAddExample({}); } else { if (Array.isArray(excludeProps)) { ; Object.keys(config.propValues).forEach(propName => { if (!excludeProps.includes(propName)) { propValues[propName] = config.propValues[propName]; } }); } else { propValues = config.propValues; } // eslint-disable-next-line no-console console.info(`Generating examples for ${Component.displayName} (${Object.keys(propValues).length} props):`, propValues); // TODO reconcile the differences between these files // generatePropCombinations should call getComponentProps and not do anything? const combos = generatePropCombinations(propValues).filter(Boolean); let index = 0; while (index < combos.length && exampleCount < maxExamples) { const combo = combos[index]; if (combo) { maybeAddExample(combo); index++; } } } if (exampleCount >= maxExamples) { console.error(`Too many examples for ${Component.displayName}! Add a filter to the config.`); } // eslint-disable-next-line no-console console.info(`Generated ${exampleCount} examples for ${Component.displayName}`); sections.forEach(({ pages }) => { pages.forEach(page => { // eslint-disable-next-line no-param-reassign page.parameters = getParameters(page); }); }); return sections; } function isEmpty(obj) { if (typeof obj !== 'object') return true; for (const key in obj) { if (Object.hasOwnProperty.call(obj, key)) return false; } return true; } export default generateComponentExamples;