From 21c31615baa8d614722b1faac97b2750ef42f28f Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Wed, 2 Dec 2020 11:06:31 +0100 Subject: [PATCH] [test] Add conformance test suite for v5 (#23798) --- docs/pages/api-docs/slider.md | 7 ++- docs/src/modules/utils/parseTest.ts | 5 +- packages/material-ui/src/Slider/Slider.d.ts | 1 + .../material-ui/src/Slider/Slider.test.js | 8 +-- test/utils/describeConformance.js | 12 ++-- test/utils/describeConformanceV5.js | 58 +++++++++++++++++++ test/utils/index.js | 1 + 7 files changed, 80 insertions(+), 12 deletions(-) create mode 100644 test/utils/describeConformanceV5.js diff --git a/docs/pages/api-docs/slider.md b/docs/pages/api-docs/slider.md index 026836c7c7e5fd..72b5d1a7dee92b 100644 --- a/docs/pages/api-docs/slider.md +++ b/docs/pages/api-docs/slider.md @@ -58,7 +58,7 @@ The `MuiSlider` name can be used for providing [default props](/customization/gl The `ref` is forwarded to the root element. -Any other props supplied will be provided to the root element (native element). +Any other props supplied will be provided to the root element ([SliderUnstyled](/api/slider-unstyled/)). ## CSS @@ -90,6 +90,11 @@ You can override the style of the component thanks to one of these customization - With a [global class name](/guides/interoperability/#global-css). - With a rule name as part of the component's [`styleOverrides` property](/customization/components/#global-theme-override) in a custom theme. +## Inheritance + +The props of the [SliderUnstyled](/api/slider-unstyled/) component are also available. +You can take advantage of this behavior to [target nested components](/guides/api/#spread). + ## Demos - [Slider](/components/slider/) diff --git a/docs/src/modules/utils/parseTest.ts b/docs/src/modules/utils/parseTest.ts index c3d4cf7d4d44ca..aeb269782b43ae 100644 --- a/docs/src/modules/utils/parseTest.ts +++ b/docs/src/modules/utils/parseTest.ts @@ -34,7 +34,10 @@ function findConformanceDescriptor(file: babel.ParseResult): babel.types.ObjectE CallExpression(babelPath) { const { node: callExpression } = babelPath; const { callee } = callExpression; - if (t.isIdentifier(callee) && callee.name === 'describeConformance') { + if ( + t.isIdentifier(callee) && + (callee.name === 'describeConformance' || callee.name === 'describeConformanceV5') + ) { const [, optionsFactory] = callExpression.arguments; if ( t.isArrowFunctionExpression(optionsFactory) && diff --git a/packages/material-ui/src/Slider/Slider.d.ts b/packages/material-ui/src/Slider/Slider.d.ts index e5aeba5abab978..04935e9aa01a43 100644 --- a/packages/material-ui/src/Slider/Slider.d.ts +++ b/packages/material-ui/src/Slider/Slider.d.ts @@ -45,6 +45,7 @@ export const SliderValueLabel: React.FC; * API: * * - [Slider API](https://material-ui.com/api/slider/) + * - inherits [SliderUnstyled API](https://material-ui.com/api/slider-unstyled/) */ declare const Slider: ExtendSliderUnstyled; diff --git a/packages/material-ui/src/Slider/Slider.test.js b/packages/material-ui/src/Slider/Slider.test.js index c19852a825533c..641ff10c80c55f 100644 --- a/packages/material-ui/src/Slider/Slider.test.js +++ b/packages/material-ui/src/Slider/Slider.test.js @@ -2,8 +2,9 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { spy, stub } from 'sinon'; import { expect } from 'chai'; -import { createMount, describeConformance, act, createClientRender, fireEvent } from 'test/utils'; +import { createMount, describeConformanceV5, act, createClientRender, fireEvent } from 'test/utils'; import { ThemeProvider, createMuiTheme } from '@material-ui/core/styles'; +import { SliderUnstyled } from '@material-ui/unstyled'; import clsx from 'clsx'; import Slider, { sliderClasses as classes } from './Slider'; @@ -30,12 +31,11 @@ describe('', () => { const mount = createMount(); const render = createClientRender(); - describeConformance(, () => ({ + describeConformanceV5(, () => ({ classes: {}, - inheritComponent: 'span', + inheritComponent: SliderUnstyled, mount, refInstanceof: window.HTMLSpanElement, - testComponentPropWith: 'span', })); it('should call handlers', () => { diff --git a/test/utils/describeConformance.js b/test/utils/describeConformance.js index 1a5b975cf8fe2b..2af297e85347e6 100644 --- a/test/utils/describeConformance.js +++ b/test/utils/describeConformance.js @@ -39,7 +39,7 @@ function testRef(element, mount, onRef = assertDOMNode) { * @param {object} options * @param {import('react').ElementType} component */ -function findRootComponent(wrapper, { component }) { +export function findRootComponent(wrapper, { component }) { const outermostHostElement = findOutermostIntrinsic(wrapper).getElement(); return wrapper.find(component).filterWhere((componentWrapper) => { @@ -57,7 +57,7 @@ function randomStringValue() { * @param {React.ReactElement} element * @param {() => ConformanceOptions} getOptions */ -function testClassName(element, getOptions) { +export function testClassName(element, getOptions) { it('applies the className to the root component', () => { const { mount } = getOptions(); const className = randomStringValue(); @@ -95,7 +95,7 @@ function testComponentProp(element, getOptions) { * @param {React.ReactElement} element * @param {() => ConformanceOptions} getOptions */ -function testPropsSpread(element, getOptions) { +export function testPropsSpread(element, getOptions) { it(`spreads props to the root component`, () => { // type def in ConformanceOptions const { classes, inheritComponent, mount } = getOptions(); @@ -117,7 +117,7 @@ function testPropsSpread(element, getOptions) { * @param {React.ReactElement} element * @param {() => ConformanceOptions} getOptions */ -function describeRef(element, getOptions) { +export function describeRef(element, getOptions) { describe('ref', () => { it(`attaches the ref`, () => { // type def in ConformanceOptions @@ -140,7 +140,7 @@ function describeRef(element, getOptions) { * @param {React.ReactElement} element * @param {() => ConformanceOptions} getOptions */ -function testRootClass(element, getOptions) { +export function testRootClass(element, getOptions) { it('applies the root class to the root component if it has this class', () => { const { classes, mount } = getOptions(); if (classes.root == null) { @@ -164,7 +164,7 @@ function testRootClass(element, getOptions) { * This is important for snapshot testing with Jest (even if we don't encourage it). * @param {React.ReactElement} element */ -function testReactTestRenderer(element) { +export function testReactTestRenderer(element) { it('should render without errors in ReactTestRenderer', () => { ReactTestRenderer.act(() => { ReactTestRenderer.create(element, { diff --git a/test/utils/describeConformanceV5.js b/test/utils/describeConformanceV5.js new file mode 100644 index 00000000000000..d3a8b9d6e23ece --- /dev/null +++ b/test/utils/describeConformanceV5.js @@ -0,0 +1,58 @@ +/* eslint-env mocha */ +import { expect } from 'chai'; +import * as React from 'react'; +import { + testClassName, + testPropsSpread, + describeRef, + testRootClass, + findRootComponent, + testReactTestRenderer, +} from './describeConformance'; + +/** + * Material-UI components have a `components` prop that allows rendering a different + * Components from @inheritComponent + * @param {React.ReactElement} element + * @param {() => ConformanceOptions} getOptions + */ +function testComponentsProp(element, getOptions) { + describe('prop: components', () => { + it('can render another root component with the `components` prop', () => { + const { classes, mount, testComponentsRootPropWith: component = 'em' } = getOptions(); + + const wrapper = mount(React.cloneElement(element, { components: { Root: component } })); + + expect(findRootComponent(wrapper, { classes, component }).exists()).to.equal(true); + }); + }); +} + +const fullSuite = { + componentsProp: testComponentsProp, + mergeClassName: testClassName, + propsSpread: testPropsSpread, + refForwarding: describeRef, + rootClass: testRootClass, + reactTestRenderer: testReactTestRenderer, +}; + +/** + * Tests various aspects of a component that should be equal across Material-UI + * components. + * @param {React.ReactElement} minimalElement - the component with it's minimal required props + * @param {() => ConformanceOptions} getOptions + */ +export default function describeConformanceV5(minimalElement, getOptions) { + const { after: runAfterHook = () => {}, only = Object.keys(fullSuite), skip = [] } = getOptions(); + describe('Material-UI component API', () => { + after(runAfterHook); + + Object.keys(fullSuite) + .filter((testKey) => only.indexOf(testKey) !== -1 && skip.indexOf(testKey) === -1) + .forEach((testKey) => { + const test = fullSuite[testKey]; + test(minimalElement, getOptions); + }); + }); +} diff --git a/test/utils/index.js b/test/utils/index.js index 9291a8190b3cff..05c6c1ab43e0f9 100644 --- a/test/utils/index.js +++ b/test/utils/index.js @@ -1,5 +1,6 @@ export * from './components'; export { default as describeConformance } from './describeConformance'; +export { default as describeConformanceV5 } from './describeConformanceV5'; export * from './createClientRender'; export { default as createMount } from './createMount'; export { default as createServerRender } from './createServerRender';