Skip to content

Commit

Permalink
[test] mui#22911 migrate accordion to react-testing-library
Browse files Browse the repository at this point in the history
Introduce a way for describeConformance to use react-teating-library and convert accordion.test.js to use react-testing-library
  • Loading branch information
bewong-atl committed Oct 8, 2020
1 parent b3cd80d commit 49ffa2d
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 57 deletions.
5 changes: 3 additions & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module.exports = {
'airbnb-typescript',
'prettier',
'prettier/react',
'prettier/@typescript-eslint',
'prettier/@typescript-eslint'
],
parser: '@typescript-eslint/parser',
parserOptions: {
Expand Down Expand Up @@ -137,7 +137,7 @@ module.exports = {
env: {
mocha: true,
},
extends: ['plugin:mocha/recommended'],
extends: ['plugin:mocha/recommended', 'plugin:chai-friendly/recommended'],
rules: {
// does not work with wildcard imports. Mistakes will throw at runtime anyway
'import/named': 'off',
Expand Down Expand Up @@ -187,6 +187,7 @@ module.exports = {
// components that are defined in test are isolated enough
// that they don't need type-checking
'react/prop-types': 'off',
'@typescript-eslint/no-unused-expressions': 'off',
},
},
{
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
"eslint-config-prettier": "^6.11.0",
"eslint-import-resolver-webpack": "^0.13.0",
"eslint-plugin-babel": "^5.3.1",
"eslint-plugin-chai-friendly": "^0.6.0",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-jsx-a11y": "^6.3.1",
"eslint-plugin-mocha": "^8.0.0",
Expand Down
85 changes: 46 additions & 39 deletions packages/material-ui/src/Accordion/Accordion.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@ import * as React from 'react';
import PropTypes from 'prop-types';
import { expect } from 'chai';
import { spy } from 'sinon';
import { createMount, describeConformance, getClasses, findOutermostIntrinsic } from 'test/utils';
import {
createClientRender,
describeConformance,
getClasses,
fireEvent
} from 'test/utils';
import Paper from '../Paper';
import Accordion from './Accordion';
import AccordionSummary from '../AccordionSummary';
import Collapse from '../Collapse';

describe('<Accordion />', () => {
const mount = createMount({ strict: true });
const mount = createClientRender({ strict: true });
let classes;
const minimalChildren = [<AccordionSummary key="header" />];
const minimalChildren = [(<AccordionSummary key="header" >Header</AccordionSummary>)];

before(() => {
classes = getClasses(<Accordion>{minimalChildren}</Accordion>);
Expand All @@ -23,104 +27,107 @@ describe('<Accordion />', () => {
mount,
refInstanceof: window.HTMLDivElement,
skip: ['componentProp'],
useRTL: true,
}));

it('should render and not be controlled', () => {
const wrapper = mount(<Accordion>{minimalChildren}</Accordion>);
const root = wrapper.find(`.${classes.root}`).first();
expect(root.type()).to.equal(Paper);
expect(root.props().square).to.equal(false);
expect(root.hasClass(classes.expanded)).to.equal(false);
const accordion = mount(<Accordion>{minimalChildren}</Accordion>);
expect(accordion.container.firstChild.tagName).to.equal("DIV");
expect(accordion.container.firstChild.classList.contains(classes.expanded)).to.be.false;
expect(accordion.container.firstChild.classList.contains(classes.square)).to.be.false;
});

it('should handle defaultExpanded prop', () => {
const wrapper = mount(<Accordion defaultExpanded>{minimalChildren}</Accordion>);
expect(findOutermostIntrinsic(wrapper).hasClass(classes.expanded)).to.equal(true);
const accordion = mount(<Accordion defaultExpanded>{minimalChildren}</Accordion>);
expect(accordion.container.firstChild.classList.contains(classes.expanded)).to.be.true;
});

it('should render the summary and collapse elements', () => {
const wrapper = mount(
const accordion = mount(
<Accordion>
<AccordionSummary>Summary</AccordionSummary>
<div id="panel-content">Hello</div>
</Accordion>,
);

expect(wrapper.find('[aria-expanded=false]').hostNodes().text()).to.equal('Summary');
expect(wrapper.find(Collapse).find('div#panel-content').text()).to.equal('Hello');
expect(accordion.getByText("Summary").parentNode.getAttribute("aria-expanded")).to.be.equal('false');
expect(accordion.getByText("Hello")).to.not.be.visible;
});

it('should be controlled', () => {
const wrapper = mount(<Accordion expanded>{minimalChildren}</Accordion>);
const panel = wrapper.find(`.${classes.root}`).first();
expect(panel.hasClass(classes.expanded)).to.equal(true);
wrapper.setProps({ expanded: false });
expect(wrapper.hasClass(classes.expanded)).to.equal(false);
const accordion = mount(<Accordion expanded>{minimalChildren}</Accordion>);
const panel = accordion.container.firstChild;
expect(panel.classList.contains(classes.expanded)).to.be.true;
accordion.rerender(<Accordion expanded={false}>{minimalChildren}</Accordion>);
expect(panel.classList.contains(classes.expanded)).to.be.false;
});

it('should call onChange when clicking the summary element', () => {
const handleChange = spy();
const wrapper = mount(<Accordion onChange={handleChange}>{minimalChildren}</Accordion>);
wrapper.find(AccordionSummary).simulate('click');
const accordion = mount(<Accordion onChange={handleChange}>{minimalChildren}</Accordion>);
const summary = accordion.getByText("Header");
fireEvent.click(summary);
expect(handleChange.callCount).to.equal(1);
});

it('when controlled should call the onChange', () => {
const handleChange = spy();
const wrapper = mount(
const accordion = mount(
<Accordion onChange={handleChange} expanded>
{minimalChildren}
</Accordion>,
);
wrapper.find(AccordionSummary).simulate('click');
const summary = accordion.getByText("Header");
fireEvent.click(summary);
expect(handleChange.callCount).to.equal(1);
expect(handleChange.args[0][1]).to.equal(false);
});

it('when undefined onChange and controlled should not call the onChange', () => {
const handleChange = spy();
const wrapper = mount(
<Accordion onChange={handleChange} expanded>
const accordion = mount(
<Accordion onChange={undefined} expanded>
{minimalChildren}
</Accordion>,
);
wrapper.setProps({ onChange: undefined });
wrapper.find(AccordionSummary).simulate('click');
const summary = accordion.getByText("Header");
fireEvent.click(summary);
expect(handleChange.callCount).to.equal(0);
});

it('when disabled should have the disabled class', () => {
const wrapper = mount(<Accordion disabled>{minimalChildren}</Accordion>);
expect(findOutermostIntrinsic(wrapper).hasClass(classes.disabled)).to.equal(true);
const accordion = mount(<Accordion disabled>{minimalChildren}</Accordion>);
expect(accordion.container.firstChild.classList.contains(classes.disabled)).to.be.true;
});

it('should handle the TransitionComponent prop', () => {
const NoTransitionCollapse = (props) => {
return props.in ? <div>{props.children}</div> : null;
return props.in ? <div data-testid="no-transition-collapse">{props.children}</div> : null;
};
NoTransitionCollapse.propTypes = {
children: PropTypes.node,
in: PropTypes.bool,
};

const CustomContent = () => <div>Hello</div>;
const wrapper = mount(
const accordion = mount(
<Accordion expanded TransitionComponent={NoTransitionCollapse}>
<AccordionSummary />
<CustomContent />
</Accordion>,
);

// Collapse is initially shown
const collapse = wrapper.find(NoTransitionCollapse);
expect(collapse.props().in).to.equal(true);
expect(wrapper.find(CustomContent).length).to.equal(1);
expect(accordion.getByText("Hello")).to.be.visible;

// Hide the collapse
wrapper.setProps({ expanded: false });
const collapse2 = wrapper.find(NoTransitionCollapse);
expect(collapse2.props().in).to.equal(false);
expect(wrapper.find(CustomContent).length).to.equal(0);
accordion.rerender(
<Accordion expanded={false} TransitionComponent={NoTransitionCollapse}>
<AccordionSummary />
<CustomContent />
</Accordion>
);
expect(accordion.queryByText("Hello")).to.not.exist
});

describe('prop: children', () => {
Expand Down Expand Up @@ -153,7 +160,7 @@ describe('<Accordion />', () => {
});

it('should accept empty content', () => {
mount(
createClientRender(
<Accordion>
<AccordionSummary />
{null}
Expand Down
49 changes: 33 additions & 16 deletions test/utils/describeConformance.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,18 @@ function randomStringValue() {
*/
function testClassName(element, getOptions) {
it('applies the className to the root component', () => {
const { mount } = getOptions();
const { mount, useRTL } = getOptions();
const className = randomStringValue();

const wrapper = mount(React.cloneElement(element, { className }));

expect(findOutermostIntrinsic(wrapper).hasClass(className)).to.equal(
true,
'does have a custom `className`',
);
if (useRTL) {
expect(wrapper.container.firstChild.classList.contains(className)).to.be.equal(true);
} else {
expect(findOutermostIntrinsic(wrapper).hasClass(className)).to.equal(
true,
'does have a custom `className`',
);
}
});
}

Expand Down Expand Up @@ -98,14 +101,17 @@ function testComponentProp(element, getOptions) {
function testPropsSpread(element, getOptions) {
it(`spreads props to the root component`, () => {
// type def in ConformanceOptions
const { classes, inheritComponent, mount } = getOptions();
const { classes, inheritComponent, mount, useRTL } = getOptions();
const testProp = 'data-test-props-spread';
const value = randomStringValue();

const wrapper = mount(React.cloneElement(element, { [testProp]: value }));
const root = findRootComponent(wrapper, { classes, component: inheritComponent });

expect(root.props()[testProp]).to.equal(value);
if(useRTL) {
expect(wrapper.container.firstChild.getAttribute(testProp)).to.equal(value);
} else {
const root = findRootComponent(wrapper, { classes, component: inheritComponent });
expect(root.props()[testProp]).to.equal(value);
}
});
}

Expand All @@ -121,14 +127,20 @@ function describeRef(element, getOptions) {
describe('ref', () => {
it(`attaches the ref`, () => {
// type def in ConformanceOptions
const { inheritComponent, mount, refInstanceof } = getOptions();
const { inheritComponent, mount, refInstanceof, useRTL } = getOptions();

testRef(element, mount, (instance, wrapper) => {
expect(instance).to.be.instanceof(refInstanceof);

if (inheritComponent && instance.nodeType === 1) {
const rootHost = findOutermostIntrinsic(wrapper);
expect(instance).to.equal(rootHost.instance());
if (useRTL) {
const rootHost = wrapper.container.firstChild;
expect(instance).to.equal(rootHost);
} else {
const rootHost = findOutermostIntrinsic(wrapper);
expect(instance).to.equal(rootHost.instance());
}

}
});
});
Expand All @@ -142,7 +154,7 @@ function describeRef(element, getOptions) {
*/
function testRootClass(element, getOptions) {
it('applies the root class to the root component if it has this class', () => {
const { classes, mount } = getOptions();
const { classes, mount, useRTL } = getOptions();
if (classes.root == null) {
return;
}
Expand All @@ -154,8 +166,13 @@ function testRootClass(element, getOptions) {
// jump to the host component because some components pass the `root` class
// to the `classes` prop of the root component.
// https://github.com/mui-org/material-ui/blob/f9896bcd129a1209153106296b3d2487547ba205/packages/material-ui/src/OutlinedInput/OutlinedInput.js#L101
expect(findOutermostIntrinsic(wrapper).hasClass(classes.root)).to.equal(true);
expect(findOutermostIntrinsic(wrapper).hasClass(className)).to.equal(true);
if (useRTL) {
expect(wrapper.container.firstChild.classList.contains(classes.root)).to.be.equal(true);
expect(wrapper.container.firstChild.classList.contains(className)).to.be.equal(true);
} else {
expect(findOutermostIntrinsic(wrapper).hasClass(classes.root)).to.equal(true);
expect(findOutermostIntrinsic(wrapper).hasClass(className)).to.equal(true);
}
});
}

Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7141,6 +7141,11 @@ eslint-plugin-babel@^5.3.1:
dependencies:
eslint-rule-composer "^0.3.0"

eslint-plugin-chai-friendly@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-0.6.0.tgz#54052fab79302ed0cea76ab997351ea4809bfb77"
integrity sha512-Uvvv1gkbRGp/qfN15B0kQyQWg+oFA8buDSqrwmW3egNSk/FpqH2MjQqKOuKwmEL6w4QIQrIjDp+gg6kGGmD3oQ==

eslint-plugin-import@^2.22.0:
version "2.22.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz#0896c7e6a0cf44109a2d97b95903c2bb689d7702"
Expand Down

0 comments on commit 49ffa2d

Please sign in to comment.