diff --git a/packages/material-ui/src/Avatar/Avatar.test.js b/packages/material-ui/src/Avatar/Avatar.test.js index 18def87d0c424b..38b96b8e8cfad7 100644 --- a/packages/material-ui/src/Avatar/Avatar.test.js +++ b/packages/material-ui/src/Avatar/Avatar.test.js @@ -74,21 +74,17 @@ describe('', () => { }); describe('font icon avatar', () => { - let container; - let avatar; - let icon; - - before(() => { - container = render( - - icon + it('should render a div containing an font icon', () => { + const { container } = render( + + + icon + , - ).container; - avatar = container.firstChild; - icon = avatar.firstChild; - }); + ); + const avatar = container.firstChild; + const icon = avatar.firstChild; - it('should render a div containing an font icon', () => { expect(avatar).to.have.tagName('div'); expect(icon).to.have.tagName('span'); expect(icon).to.have.class('my-icon-font'); @@ -96,100 +92,125 @@ describe('', () => { }); it('should merge user classes & spread custom props to the root node', () => { + const { container } = render( + + icon + , + ); + const avatar = container.firstChild; + expect(avatar).to.have.class(classes.root); expect(avatar).to.have.class('my-avatar'); expect(avatar).to.have.attribute('data-my-prop', 'woofAvatar'); }); it('should apply the colorDefault class', () => { + const { container } = render( + + icon + , + ); + const avatar = container.firstChild; + expect(avatar).to.have.class(classes.colorDefault); }); }); describe('svg icon avatar', () => { - let container; - let avatar; - - before(() => { - container = render( - + it('should render a div containing an svg icon', () => { + const container = render( + , ).container; - avatar = container.firstChild; - }); + const avatar = container.firstChild; - it('should render a div containing an svg icon', () => { expect(avatar).to.have.tagName('div'); const cancelIcon = avatar.firstChild; expect(cancelIcon).to.have.attribute('data-testid', 'CancelIcon'); }); it('should merge user classes & spread custom props to the root node', () => { + const container = render( + + + , + ).container; + const avatar = container.firstChild; + expect(avatar).to.have.class(classes.root); expect(avatar).to.have.class('my-avatar'); expect(avatar).to.have.attribute('data-my-prop', 'woofAvatar'); }); it('should apply the colorDefault class', () => { + const container = render( + + + , + ).container; + const avatar = container.firstChild; + expect(avatar).to.have.class(classes.colorDefault); }); }); describe('text avatar', () => { - let container; - let avatar; - - before(() => { - container = render( - - OT - , - ).container; - avatar = container.firstChild; - }); - it('should render a div containing a string', () => { + const container = render(OT).container; + const avatar = container.firstChild; + expect(avatar).to.have.tagName('div'); expect(avatar.firstChild).to.text('OT'); }); it('should merge user classes & spread custom props to the root node', () => { + const container = render( + + OT + , + ).container; + const avatar = container.firstChild; + expect(avatar).to.have.class(classes.root); expect(avatar).to.have.class('my-avatar'); expect(avatar).to.have.attribute('data-my-prop', 'woofAvatar'); }); it('should apply the colorDefault class', () => { + const container = render(OT).container; + const avatar = container.firstChild; + expect(avatar).to.have.class(classes.colorDefault); }); }); describe('falsey avatar', () => { - let container; - let avatar; - - before(() => { - container = render( - - {0} - , - ).container; - avatar = container.firstChild; - }); - it('should render with defaultColor class when supplied with a child with falsey value', () => { + const container = render({0}).container; + const avatar = container.firstChild; + expect(avatar).to.have.tagName('div'); expect(avatar.firstChild).to.text('0'); }); it('should merge user classes & spread custom props to the root node', () => { + const container = render( + + {0} + , + ).container; + const avatar = container.firstChild; + expect(avatar).to.have.class(classes.root); expect(avatar).to.have.class('my-avatar'); expect(avatar).to.have.attribute('data-my-prop', 'woofAvatar'); }); it('should apply the colorDefault class', () => { + const container = render({0}).container; + const avatar = container.firstChild; + expect(avatar).to.have.class(classes.colorDefault); }); }); diff --git a/packages/material-ui/src/ButtonBase/Ripple.test.js b/packages/material-ui/src/ButtonBase/Ripple.test.js index 023f19dc704185..8a1ba50a2d2466 100644 --- a/packages/material-ui/src/ButtonBase/Ripple.test.js +++ b/packages/material-ui/src/ButtonBase/Ripple.test.js @@ -18,108 +18,119 @@ describe('', () => { }); describe('starting and stopping', () => { - let wrapper; - - before(() => { - wrapper = render( + it('should start the ripple', () => { + const { container, setProps } = render( , ); - }); - it('should start the ripple', () => { - wrapper.setProps({ in: true }); - const ripple = wrapper.container.querySelector('span'); + setProps({ in: true }); + + const ripple = container.querySelector('span'); expect(ripple).to.have.class(classes.rippleVisible); }); it('should stop the ripple', () => { - wrapper.setProps({ in: true }); - wrapper.setProps({ in: false }); - const child = wrapper.container.querySelector('span > span'); + const { container, setProps } = render( + , + ); + + setProps({ in: false }); + + const child = container.querySelector('span > span'); expect(child).to.have.class(classes.childLeaving); }); }); describe('pulsating and stopping 1', () => { - let wrapper; - - before(() => { - wrapper = render( - , + it('should render the ripple inside a pulsating Ripple', () => { + const { container } = render( + , ); - }); - it('should render the ripple inside a pulsating Ripple', () => { - const ripple = wrapper.container.querySelector('span'); + const ripple = container.querySelector('span'); expect(ripple).to.have.class(classes.ripple); expect(ripple).to.have.class(classes.ripplePulsate); - const child = wrapper.container.querySelector('span > span'); + const child = container.querySelector('span > span'); expect(child).to.have.class(classes.childPulsate); }); it('should start the ripple', () => { - wrapper.setProps({ in: true }); - const ripple = wrapper.container.querySelector('span'); + const { container, setProps } = render( + , + ); + + setProps({ in: true }); + + const ripple = container.querySelector('span'); expect(ripple).to.have.class(classes.rippleVisible); - const child = wrapper.container.querySelector('span > span'); + const child = container.querySelector('span > span'); expect(child).to.have.class(classes.childPulsate); }); it('should stop the ripple', () => { - wrapper.setProps({ in: true }); - wrapper.setProps({ in: false }); - const child = wrapper.container.querySelector('span > span'); + const { container, setProps } = render( + , + ); + + setProps({ in: true }); + setProps({ in: false }); + const child = container.querySelector('span > span'); expect(child).to.have.class(classes.childLeaving); }); }); describe('pulsating and stopping 2', () => { - let wrapper; let clock; - let callbackSpy; beforeEach(() => { - callbackSpy = spy(); - wrapper = render( + clock = useFakeTimers(); + }); + + afterEach(() => { + clock.restore(); + }); + + it('handleExit should trigger a timer', () => { + const handleExited = spy(); + const { setProps } = render( , ); - clock = useFakeTimers(); - }); - - afterEach(() => { - clock.restore(); - }); - it('handleExit should trigger a timer', () => { - wrapper.setProps({ in: false }); + setProps({ in: false }); clock.tick(549); - expect(callbackSpy.callCount).to.equal(0); + expect(handleExited.callCount).to.equal(0); clock.tick(1); - expect(callbackSpy.callCount).to.equal(1); + expect(handleExited.callCount).to.equal(1); }); it('unmount should defuse the handleExit timer', () => { - wrapper.setProps({ in: false }); - wrapper.unmount(); + const handleExited = spy(); + const { setProps, unmount } = render( + , + ); + + setProps({ in: false }); + unmount(); clock.tick(550); - expect(callbackSpy.callCount).to.equal(0); + expect(handleExited.callCount).to.equal(0); }); }); }); diff --git a/test/utils/createClientRender.js b/test/utils/createClientRender.js index 13b24c96cb7d09..b7b8567c421bb7 100644 --- a/test/utils/createClientRender.js +++ b/test/utils/createClientRender.js @@ -1,6 +1,8 @@ /* eslint-env mocha */ import * as React from 'react'; import PropTypes from 'prop-types'; +import createEmotionCache from '@emotion/cache'; +import { CacheProvider as EmotionCacheProvider } from '@emotion/react'; import { act as rtlAct, buildQueries, @@ -175,49 +177,46 @@ const customQueries = { }; /** - * @typedef {object} RenderOptions - * @property {HTMLElement} [options.baseElement] - https://testing-library.com/docs/react-testing-library/api#baseelement-1 - * @property {HTMLElement} [options.container] - https://testing-library.com/docs/react-testing-library/api#container - * @property {boolean} [options.disableUnnmount] - if true does not cleanup before mount - * @property {boolean} [options.hydrate] - https://testing-library.com/docs/react-testing-library/api#hydrate - * @property {boolean} [options.strict] - wrap in React.StrictMode? + * @typedef {object} RenderConfiguration + * @property {HTMLElement} [baseElement] - https://testing-library.com/docs/react-testing-library/api#baseelement-1 + * @property {HTMLElement} [container] - https://testing-library.com/docs/react-testing-library/api#container + * @property {boolean} [disableUnnmount] - if true does not cleanup before mount + * @property {import('@emotion/cache').EmotionCache} emotionCache - Value for the CacheProvider of emotion + * @property {boolean} [hydrate] - https://testing-library.com/docs/react-testing-library/api#hydrate + * @property {typeof Profiler} profiler + * @property {boolean} [strict] - wrap in React.StrictMode? + */ + +/** + * @typedef {Omit} RenderOptions */ /** * @param {React.ReactElement} element - * @param {RenderOptions} [options] + * @param {RenderConfiguration} configuration * @returns {import('@testing-library/react').RenderResult & { setProps(props: object): void}} * TODO: type return RenderResult in setProps */ -function clientRender(element, options = {}) { +function clientRender(element, configuration) { const { baseElement, container, + emotionCache, hydrate, strict = true, profiler, wrapper: InnerWrapper = React.Fragment, - } = options; - - if (profiler === null) { - // TODO: remove tests rendering in mocha hooks - // throw new Error('Rendered outside of a test. Use the test renderer only in tests.'); - } + } = configuration; const Mode = strict ? React.StrictMode : React.Fragment; function Wrapper({ children }) { - if (profiler !== null) { - return ( - + return ( + + {children} - - ); - } - return ( - - {children} + ); } @@ -256,8 +255,8 @@ function clientRender(element, options = {}) { } /** - * @param {RenderOptions} globalOptions - * @returns {clientRender} + * @param {RenderOptions} [globalOptions] + * @returns {(element: React.ReactElement, options?: RenderOptions) => import('@testing-library/react').RenderResult & { setProps(props: object): void}} */ export function createClientRender(globalOptions = {}) { const { strict: globalStrict } = globalOptions; @@ -290,6 +289,15 @@ export function createClientRender(globalOptions = {}) { } }); + /** + * @type {import('@emotion/cache').EmotionCache} + */ + let emotionCache = null; + /** + * Flag whether all setup for `configuredClientRender` was completed. + * For legacy reasons `configuredClientRender` might accidentally be called in a before(Each) hook. + */ + let prepared = false; let profiler = null; beforeEach(function beforeEachHook() { if (!wasCalledInSuite) { @@ -307,6 +315,10 @@ export function createClientRender(globalOptions = {}) { ); } profiler = new Profiler(this.currentTest); + + emotionCache = createEmotionCache({ key: 'emotion-client-render' }); + + prepared = true; }); afterEach(() => { @@ -325,12 +337,25 @@ export function createClientRender(globalOptions = {}) { cleanup(); profiler.report(); profiler = null; + + emotionCache.sheet.tags.forEach((styleTag) => { + styleTag.remove(); + }); + emotionCache = null; }); return function configuredClientRender(element, options = {}) { + if (!prepared) { + throw new Error( + 'Unable to finish setup before `render()` was called. ' + + 'This usually indicates that `render()` was called in a `before()` or `beforeEach` hook. ' + + 'Move the call into each `it()`. Otherwise you cannot run a specific test and we cannot isolate each test.', + ); + } + const { strict = globalStrict, ...localOptions } = options; - return clientRender(element, { ...localOptions, strict, profiler }); + return clientRender(element, { ...localOptions, strict, profiler, emotionCache }); }; }