diff --git a/package.json b/package.json index 581215a61981dc..150677b15bb7cf 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@babel/preset-env": "^7.6.0", "@babel/preset-react": "^7.0.0", "@babel/register": "^7.6.2", + "@testing-library/dom": "^6.8.1", "@testing-library/react": "^9.2.0", "@types/chai": "^4.2.3", "@types/chai-dom": "^0.0.8", diff --git a/packages/material-ui-lab/src/SpeedDialAction/SpeedDialAction.test.js b/packages/material-ui-lab/src/SpeedDialAction/SpeedDialAction.test.js index fbafcf4fdb61aa..15739f0bd422d1 100644 --- a/packages/material-ui-lab/src/SpeedDialAction/SpeedDialAction.test.js +++ b/packages/material-ui-lab/src/SpeedDialAction/SpeedDialAction.test.js @@ -17,7 +17,7 @@ describe('', () => { }; before(() => { - // StrictModeViolation: unknown + // StrictModeViolation: uses Tooltip mount = createMount({ strict: false }); classes = getClasses(); }); diff --git a/packages/material-ui/src/Button/Button.test.js b/packages/material-ui/src/Button/Button.test.js index 5a3fdf98b42f52..1d10779b04119a 100644 --- a/packages/material-ui/src/Button/Button.test.js +++ b/packages/material-ui/src/Button/Button.test.js @@ -8,12 +8,11 @@ import ButtonBase from '../ButtonBase'; describe('); }); diff --git a/packages/material-ui/src/ExpansionPanelSummary/ExpansionPanelSummary.js b/packages/material-ui/src/ExpansionPanelSummary/ExpansionPanelSummary.js index dc995b5efa3129..81d71a8ff81b4c 100644 --- a/packages/material-ui/src/ExpansionPanelSummary/ExpansionPanelSummary.js +++ b/packages/material-ui/src/ExpansionPanelSummary/ExpansionPanelSummary.js @@ -93,6 +93,7 @@ const ExpansionPanelSummary = React.forwardRef(function ExpansionPanelSummary(pr onBlur(event); } }; + // TODO: Remove in v5 and use onClick in ExpansionPanel.js const handleChange = event => { if (onChange) { onChange(event); diff --git a/packages/material-ui/src/ExpansionPanelSummary/ExpansionPanelSummary.test.js b/packages/material-ui/src/ExpansionPanelSummary/ExpansionPanelSummary.test.js index 0be9a293e46acf..73c129ef216b3a 100644 --- a/packages/material-ui/src/ExpansionPanelSummary/ExpansionPanelSummary.test.js +++ b/packages/material-ui/src/ExpansionPanelSummary/ExpansionPanelSummary.test.js @@ -1,125 +1,119 @@ import React from 'react'; -import { assert } from 'chai'; +import { expect } from 'chai'; import { spy } from 'sinon'; -import { createMount, findOutermostIntrinsic, getClasses } from '@material-ui/core/test-utils'; +import { createMount, getClasses } from '@material-ui/core/test-utils'; import describeConformance from '../test-utils/describeConformance'; +import { createClientRender, fireEvent } from 'test/utils/createClientRender'; import ExpansionPanelSummary from './ExpansionPanelSummary'; import ButtonBase from '../ButtonBase'; describe('', () => { let mount; let classes; - - function findExpandButton(wrapper) { - return wrapper.find('[role="button"]:not([aria-hidden=true])'); - } + const render = createClientRender({ strict: true }); before(() => { - // StrictModeViolation: uses simulate - mount = createMount({ strict: false }); + mount = createMount({ strict: true }); classes = getClasses(); }); - after(() => { - mount.cleanUp(); - }); - describeConformance(, () => ({ classes, inheritComponent: ButtonBase, mount, refInstanceof: window.HTMLDivElement, skip: ['componentProp'], + after: () => mount.cleanUp(), })); - it('should render with the content', () => { - const wrapper = mount(The Summary); - const itemsWrap = wrapper.find(`.${classes.content}`); - assert.strictEqual(itemsWrap.text(), 'The Summary'); + it('renders the children inside the .content element', () => { + const { container } = render(The Summary); + + expect(container.querySelector(`.${classes.content}`)).to.have.text('The Summary'); }); it('when disabled should have disabled class', () => { - const wrapper = mount(); - assert.strictEqual(findExpandButton(wrapper).hasClass(classes.disabled), true); + const { getByRole } = render(); + + expect(getByRole('button')).to.have.class(classes.disabled); }); - it('when expanded should have expanded class', () => { - const wrapper = mount(); - assert.strictEqual(wrapper.find('[aria-expanded=true]').every(`.${classes.expanded}`), true); + it('when expanded adds the expanded class to any button regardless of a11y', () => { + const { getAllByRole } = render(); + + const buttons = getAllByRole('button', { hidden: true }); + expect(buttons).to.have.length(2); + expect(buttons[0]).to.have.class(classes.expanded); + expect(buttons[0]).to.have.attribute('aria-expanded', 'true'); + expect(buttons[0]).not.to.be.inaccessible; + expect(buttons[1]).to.have.class(classes.expanded); + expect(buttons[1]).to.be.inaccessible; }); it('should render with the expand icon and have the expandIcon class', () => { - const wrapper = mount(Icon} />); - const iconWrap = wrapper.find(`.${classes.expandIcon}`).first(); - assert.strictEqual(iconWrap.text(), 'Icon'); + const { getAllByRole } = render(Icon} />); + + const expandButton = getAllByRole('button', { hidden: true })[1]; + expect(expandButton).to.have.class(classes.expandIcon); + expect(expandButton).to.have.text('Icon'); + expect(expandButton).to.be.inaccessible; }); - it('handleFocusVisible() should set focused state', () => { - const wrapper = mount(); - wrapper - .find(ButtonBase) - .props() - .onFocusVisible(); - wrapper.update(); - assert.strictEqual(findExpandButton(wrapper).hasClass(classes.focused), true); + it('focusing adds the `focused` class if focused visible', () => { + // TODO: Rename `focused` -> `focus-visible` + // `focused` is a global state which is applied on focus + // only here do we constrain it to focus-visible. THe name is also not consistent + // with :focus + const { getByRole } = render(); + fireEvent.mouseDown(document.body); // pointer device + + fireEvent.keyDown(document.activeElement, { key: 'Tab' }); // not actually focusing (yet) + getByRole('button').focus(); + + expect(getByRole('button')).to.be.focused; + expect(getByRole('button')).to.have.class(classes.focused); }); - it('handleBlur() should unset focused state', () => { - const wrapper = mount(); - wrapper - .find(ButtonBase) - .props() - .onFocusVisible(); - wrapper.update(); - wrapper - .find(ButtonBase) - .props() - .onBlur(); - wrapper.update(); - assert.strictEqual(findExpandButton(wrapper).hasClass(classes.focused), false); + it('blur should unset focused state', () => { + const { getByRole } = render(); + fireEvent.mouseDown(document.body); // pointer device + fireEvent.keyDown(document.activeElement, { key: 'Tab' }); // not actually focusing (yet) + getByRole('button').focus(); + + getByRole('button').blur(); + + expect(getByRole('button')).not.to.be.focused; + expect(getByRole('button')).not.to.have.class(classes.focused); }); - describe('event callbacks', () => { - it('should fire event callbacks', () => { - const events = ['onClick', 'onFocusVisible', 'onBlur']; - - const handlers = events.reduce((result, n) => { - result[n] = spy(); - return result; - }, {}); - - const wrapper = mount(); - - events.forEach(event => { - wrapper - .find(ButtonBase) - .props() - [event]({ persist: () => {} }); - assert.strictEqual(handlers[event].callCount, 1, `should have called the ${event} handler`); - }); - }); + it('should fire onClick callbacks', () => { + const handleClick = spy(); + const { getByRole } = render(); + + getByRole('button').click(); + + expect(handleClick.callCount).to.equal(1); }); - describe('prop: onChange', () => { - it('fires onChange if the summary control is clicked', () => { - const handleChange = spy(); - const wrapper = mount(); + it('calls onChange when clicking', () => { + const handleChange = spy(); + const { getByRole } = render(); - const control = findOutermostIntrinsic(wrapper); - const eventMock = 'woofExpansionPanelSummary'; - control.simulate('click', { eventMock }); + getByRole('button').click(); - assert.strictEqual(handleChange.callCount, 1); - assert.strictEqual(handleChange.calledWithMatch({ eventMock }), true); - }); + expect(handleChange.callCount).to.equal(1); }); - describe('prop: click', () => { - it('should trigger onClick', () => { - const handleClick = spy(); - const wrapper = mount(); - wrapper.simulate('click'); - assert.strictEqual(handleClick.callCount, 1); - }); + it('calls onFocusVisible if focused visibly', () => { + const handleFocusVisible = spy(); + const { getByRole } = render(); + // simulate pointer device + fireEvent.mouseDown(document.body); + + // this doesn't actually apply focus like in the browser. we need to move focus manually + fireEvent.keyDown(document.body, { key: 'Tab' }); + getByRole('button').focus(); + + expect(handleFocusVisible.callCount).to.equal(1); }); }); diff --git a/packages/material-ui/src/RadioGroup/RadioGroup.test.js b/packages/material-ui/src/RadioGroup/RadioGroup.test.js index 04a63c2d473c83..e426f44575253a 100644 --- a/packages/material-ui/src/RadioGroup/RadioGroup.test.js +++ b/packages/material-ui/src/RadioGroup/RadioGroup.test.js @@ -13,7 +13,7 @@ describe('', () => { let mount; before(() => { - // StrictModeViolation: uses #simulate + // StrictModeViolation: test uses #simulate mount = createMount({ strict: false }); }); diff --git a/packages/material-ui/src/Select/Select.test.js b/packages/material-ui/src/Select/Select.test.js index 1dd9b4fa4e7e62..8e3f64384f7643 100644 --- a/packages/material-ui/src/Select/Select.test.js +++ b/packages/material-ui/src/Select/Select.test.js @@ -16,7 +16,7 @@ describe('); - // StrictModeViolation: test uses MenuItem + // StrictModeViolation: uses Menu mount = createMount({ strict: false }); }); diff --git a/packages/material-ui/src/Tab/Tab.test.js b/packages/material-ui/src/Tab/Tab.test.js index 2b2cfef4849314..c9294dc223b864 100644 --- a/packages/material-ui/src/Tab/Tab.test.js +++ b/packages/material-ui/src/Tab/Tab.test.js @@ -20,7 +20,7 @@ describe('', () => { before(() => { shallow = createShallow({ dive: true }); - // StrictModeViolation: uses text() + // StrictModeViolation: test uses text() mount = createMount({ strict: false }); classes = getClasses(); }); diff --git a/packages/material-ui/src/TablePagination/TablePagination.test.js b/packages/material-ui/src/TablePagination/TablePagination.test.js index da41203c51a1f6..206e9a2daa1495 100644 --- a/packages/material-ui/src/TablePagination/TablePagination.test.js +++ b/packages/material-ui/src/TablePagination/TablePagination.test.js @@ -32,7 +32,7 @@ describe('', () => { classes = getClasses( {}} page={0} rowsPerPage={10} />, ); - // StrictModeViolation: uses #html() + // StrictModeViolation: test uses #html() mount = createMount({ strict: false }); }); diff --git a/packages/material-ui/test/integration/Menu.test.js b/packages/material-ui/test/integration/Menu.test.js index c88b1a80159597..504346ed3884c3 100644 --- a/packages/material-ui/test/integration/Menu.test.js +++ b/packages/material-ui/test/integration/Menu.test.js @@ -74,6 +74,7 @@ describe(' integration', () => { * @type {ReturnType} */ let clock; + // StrictModeViolation: uses Popover const render = createClientRender({ strict: false }); beforeEach(() => { diff --git a/packages/material-ui/test/integration/NestedMenu.test.js b/packages/material-ui/test/integration/NestedMenu.test.js index 39430c3d401095..510e77fd7e6c1a 100644 --- a/packages/material-ui/test/integration/NestedMenu.test.js +++ b/packages/material-ui/test/integration/NestedMenu.test.js @@ -38,7 +38,7 @@ function NestedMenu(props) { } describe(' integration', () => { - // StrictModeViolation: test uses Popover + // StrictModeViolation: uses Popover const render = createClientRender({ strict: false }); afterEach(() => { diff --git a/test/utils/init.d.ts b/test/utils/init.d.ts index 27229b7913d899..df15c7cbbeac0d 100644 --- a/test/utils/init.d.ts +++ b/test/utils/init.d.ts @@ -5,8 +5,13 @@ declare namespace Chai { /** * checks if the element in question is considered aria-hidden * Does not replace accessibility check as that requires display/visibility/layout + * @deprecated Use `inaccessible` + `visible` instead */ ariaHidden: Assertion; + /** + * checks if the element is inaccessible + */ + inaccessible: Assertion; /** * checks if the element is focused */ diff --git a/test/utils/initMatchers.js b/test/utils/initMatchers.js index 1f0ed376f65e83..1ea2ca24774e45 100644 --- a/test/utils/initMatchers.js +++ b/test/utils/initMatchers.js @@ -1,5 +1,6 @@ import chai from 'chai'; import chaiDom from 'chai-dom'; +import { isInaccessible } from '@testing-library/dom'; import { prettyDOM } from '@testing-library/react'; chai.use(chaiDom); @@ -48,4 +49,16 @@ chai.use((chaiAPI, utils) => { )} had aria-hidden="true" instead\n${prettyDOM(previousNode)}`, ); }); + + chai.Assertion.addProperty('inaccessible', function elementIsAccessible() { + const element = utils.flag(this, 'object'); + + const inaccessible = isInaccessible(element); + + this.assert( + inaccessible === true, + `expected ${utils.elToString(element)} to be inaccessible but it was accessible`, + `expected ${utils.elToString(element)} to be accessible but it was inaccessible`, + ); + }); }); diff --git a/yarn.lock b/yarn.lock index 46b97aab42feeb..6c312f091db9b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -825,7 +825,7 @@ core-js "^2.6.5" regenerator-runtime "^0.13.2" -"@babel/runtime@7.0.0", "@babel/runtime@7.1.2", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.1.5", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0": +"@babel/runtime@7.0.0", "@babel/runtime@7.1.2", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.1.5", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.2": version "7.6.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.6.2.tgz#c3d6e41b304ef10dcf13777a33e7694ec4a9a6dd" integrity sha512-EXxN64agfUqqIGeEjI5dL5z0Sw0ZwWo1mLTi4mQowCZ42O59b7DRpZAnTC6OqdF28wMBMFKNb/4uFGrVaigSpg== @@ -1068,14 +1068,14 @@ unique-filename "^1.1.1" which "^1.3.1" -"@jest/types@^24.8.0": - version "24.8.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.8.0.tgz#f31e25948c58f0abd8c845ae26fcea1491dea7ad" - integrity sha512-g17UxVr2YfBtaMUxn9u/4+siG1ptg9IGYAYwvpwn61nBg779RXnjE/m7CxYcIzEt0AbHZZAHSEZNhkE2WxURVg== +"@jest/types@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.9.0.tgz#63cb26cb7500d069e5a389441a7c6ab5e909fc59" + integrity sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw== dependencies: "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^1.1.1" - "@types/yargs" "^12.0.9" + "@types/yargs" "^13.0.0" "@lerna/add@3.16.2": version "3.16.2" @@ -1986,17 +1986,17 @@ dependencies: defer-to-connect "^1.0.1" -"@testing-library/dom@^6.3.0": - version "6.4.1" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-6.4.1.tgz#4efd38d896b9b2255025acf9567e2360e1f4814f" - integrity sha512-bjPHLO5NzlTvA57Tfz8txHEUmnOed3NuvObB2ttoKfO6A/utr7TZt9bDHHcYymcZIG2IsQZLix/m4ZKkedDDwQ== +"@testing-library/dom@^6.3.0", "@testing-library/dom@^6.8.1": + version "6.8.1" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-6.8.1.tgz#47b4e0cc0742302fc9d122ac43010e6fc60bee65" + integrity sha512-b+Q4wryafqsSTFBV14cc5xqhN/OVB9oMeUQvZwy1kVx+kdqs4zQAcyvCsFkdcqx7NxibWpUN/fBIUaqyAEhitw== dependencies: - "@babel/runtime" "^7.5.5" + "@babel/runtime" "^7.6.2" "@sheerun/mutationobserver-shim" "^0.3.2" "@types/testing-library__dom" "^6.0.0" aria-query "3.0.0" - pretty-format "^24.8.0" - wait-for-expect "^1.3.0" + pretty-format "^24.9.0" + wait-for-expect "^3.0.0" "@testing-library/react@^9.2.0": version "9.3.0" @@ -2297,10 +2297,17 @@ resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.5.tgz#9da44ed75571999b65c37b60c9b2b88db54c585d" integrity sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg== -"@types/yargs@^12.0.9": - version "12.0.12" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916" - integrity sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw== +"@types/yargs-parser@*": + version "13.1.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.1.0.tgz#c563aa192f39350a1d18da36c5a8da382bbd8228" + integrity sha512-gCubfBUZ6KxzoibJ+SCUc/57Ms1jz5NjHe4+dI2krNmU5zCPAphyLJYyTOg06ueIyfj+SaCUqmzun7ImlxDcKg== + +"@types/yargs@^13.0.0": + version "13.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.3.tgz#76482af3981d4412d65371a318f992d33464a380" + integrity sha512-K8/LfZq2duW33XW/tFwEAfnZlqIfVsoyRB3kfXdPXYhl0nfM8mmh7GS0jg7WrX2Dgq/0Ha/pR1PaR+BvmWwjiQ== + dependencies: + "@types/yargs-parser" "*" "@webassemblyjs/ast@1.7.11": version "1.7.11" @@ -11729,12 +11736,12 @@ pretty-format@^23.6.0: ansi-regex "^3.0.0" ansi-styles "^3.2.0" -pretty-format@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2" - integrity sha512-P952T7dkrDEplsR+TuY7q3VXDae5Sr7zmQb12JU/NDQa/3CH7/QW0yvqLcGN6jL+zQFKaoJcPc+yJxMTGmosqw== +pretty-format@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9" + integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA== dependencies: - "@jest/types" "^24.8.0" + "@jest/types" "^24.9.0" ansi-regex "^4.0.0" ansi-styles "^3.2.0" react-is "^16.8.4" @@ -14960,10 +14967,10 @@ w3c-xmlserializer@^1.1.2: webidl-conversions "^4.0.2" xml-name-validator "^3.0.0" -wait-for-expect@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-1.3.0.tgz#65241ce355425f907f5d127bdb5e72c412ff830c" - integrity sha512-8fJU7jiA96HfGPt+P/UilelSAZfhMBJ52YhKzlmZQvKEZU2EcD1GQ0yqGB6liLdHjYtYAoGVigYwdxr5rktvzA== +wait-for-expect@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-3.0.0.tgz#d6fbf28959e3b8779dc172fb1ea56bf1e833bf7a" + integrity sha512-9LyJL+MugZdcQn5V9PBSEC4d2UPTy1xX2U9wTc6LvG/18qeeYqdE/CgmAQJxc/Vcjs+VzH+wiyIXxz05F3nrpQ== warning@^4.0.1: version "4.0.3"