UNPKG

@salesforce/design-system-react

Version:

Salesforce Lightning Design System for React

328 lines (276 loc) 9.01 kB
import React from 'react'; import PropTypes from 'prop-types'; import chai, { expect } from 'chai'; import chaiEnzyme from 'chai-enzyme'; import { mount } from 'enzyme'; /* Enzyme Helpers that can mount and unmount React component instances to * the DOM and set `this.wrapper` and `this.dom` within Mocha's `this` * context [full source here](tests/enzyme-helpers.js). `this` can * only be referenced if inside `function () {}`. */ import { createMountNode, destroyMountNode, } from '../../../tests/enzyme-helpers'; // Import your internal dependencies (for example): import Popover from '../../popover'; import Button from '../../button'; import Dialog from '../../utilities/dialog'; import IconSettings from '../../icon-settings'; /* Set Chai to use chaiEnzyme for enzyme compatible assertions: * https://github.com/producthunt/chai-enzyme */ chai.use(chaiEnzyme()); const defaultProps = { id: 'sample-popover', body: <span id="sample-body">This is the body</span>, heading: <span id="sample-heading">This is the heading</span>, }; const defaultIds = { trigger: `button#${defaultProps.id}`, popover: `${defaultProps.id}-popover`, body: `${defaultProps.id}-dialog-body`, heading: `${defaultProps.id}-dialog-heading`, }; /* A re-usable demo component fixture outside of `describe` sections * can accept props within each test and be unmounted after each tests. * This wrapping component will be similar to your wrapping component * you will create in the React Storybook for manual testing. */ class DemoComponent extends React.Component { static displayName = 'PopoverDemoComponent'; static propTypes = { isOpen: PropTypes.bool, }; static defaultProps = defaultProps; state = {}; // event handlers render() { return ( <IconSettings iconPath="/assets/icons"> <div> <Popover {...this.props}> <Button label="Trigger Popover" /> </Popover> <Button id="not-the-trigger" label="Not Trigger Popover" /> </div> </IconSettings> ); } } /* All tests for component being tested should be wrapped in a root `describe`, * which should be named after the component being tested. * When read aloud, the cumulative `describe` and `it` names should form a coherent * sentence, eg "Date Picker default structure and css is present with expected * attributes set". If you are having trouble constructing a cumulative short * sentence, this may be an indicator that your test is poorly structured. * String provided as first parameter names the `describe` section. Limit to nouns * as much as possible/appropriate.` */ describe('SLDSPopover', function () { let mountNode; let wrapper; // BASIC STRUCTURE describe('Default structure and css', function () { beforeEach(() => { mountNode = createMountNode({ context: this }); }); afterEach(() => { destroyMountNode({ wrapper, mountNode }); }); it('is open, has heading, body, close button', () => { wrapper = mount(<DemoComponent isOpen />, { attachTo: mountNode }); expect(wrapper.find(`#${defaultIds.heading}`)).to.exist; expect(wrapper.find(`#${defaultIds.body}`)).to.exist; expect(wrapper.find('.slds-popover__close')).to.exist; }); }); describe('Assistive technology', () => { /* Detect if presence of accessibility features such as ARIA * roles and screen reader text is present in the DOM. */ beforeEach(() => { mountNode = createMountNode({ context: this }); }); afterEach(() => { destroyMountNode({ wrapper, mountNode }); }); it('has aria-labelledby/aria-describedby on popover', function () { wrapper = mount(<DemoComponent isOpen />, { attachTo: mountNode }); const popover = wrapper.find(`#${defaultIds.popover}`); expect(popover).to.have.attr('aria-labelledby', defaultIds.heading); expect(popover).to.have.attr('aria-describedby', defaultIds.body); }); }); // PROPS AND CHILDREN describe('Optional props', () => { const popoverBackgroundColor = 'rgb(255, 80, 121)'; const containerBackgroundColor = 'rgb(255, 127, 80)'; // What should be present in the DOM when style and className are applied? const optionalProps = { className: 'sample-classname', assistiveText: { closeButton: 'Shut it now!', }, containerClassName: 'sample-container-classname', containerStyle: { background: containerBackgroundColor }, footer: <p id="footer">Footer</p>, style: { background: popoverBackgroundColor }, }; beforeEach(() => { mountNode = createMountNode({ context: this }); }); afterEach(() => { destroyMountNode({ wrapper, mountNode }); }); it('has correct className, assistiveText, style, and footer', function () { wrapper = mount(<DemoComponent {...optionalProps} isOpen />, { attachTo: mountNode, }); const popover = wrapper.find(Dialog); expect(popover).to.have.className(optionalProps.className); expect(popover.find('button.slds-popover__close')).to.have.text( optionalProps.assistiveText.closeButton ); expect(popover.find('#footer')).to.exist; expect(popover.prop('style').background).to.equal(popoverBackgroundColor); }); }); // EVENTS describe('Mouse and keyboard interactions', () => { /* Test event callback functions using Simulate. For more information, view * https://github.com/airbnb/enzyme/blob/master/docs/api/ReactWrapper/simulate.md */ describe('onClick', function () { const triggerClicked = sinon.spy(); beforeEach(() => { mountNode = createMountNode({ context: this }); }); afterEach(() => { destroyMountNode({ wrapper, mountNode }); }); it('calls onClick handler on trigger, click on popover close closes', function (done) { wrapper = mount( <DemoComponent onClick={triggerClicked} onClose={() => { setTimeout(() => { const popover = wrapper.find(`#${defaultIds.popover}`); expect(popover).to.not.exist; done(); }, 0); }} onOpen={() => { wrapper.update(); const popover = wrapper.find(`#${defaultIds.popover}`); expect(popover).to.exist; expect(triggerClicked.callCount).to.equal(1); popover.find('button.slds-popover__close').simulate('click'); }} position="absolute" />, { attachTo: mountNode } ); const trigger = wrapper.find(defaultIds.trigger); trigger.simulate('click'); }); it('opens on click, closes on ESC', function (done) { wrapper = mount( <DemoComponent onClose={() => { setTimeout(() => { const popover = wrapper.find(`#${defaultIds.popover}`); expect(popover).to.not.exist; done(); }, 0); }} onOpen={() => { wrapper.update(); const popover = wrapper.find(`#${defaultIds.popover}`); popover.simulate('keyDown', { key: 'Esc', keyCode: 27, which: 27, }); }} />, { attachTo: mountNode } ); const trigger = wrapper.find(defaultIds.trigger); trigger.simulate('click'); }); it('stops event propagation after closing on ESC', function (done) { const mockEvent = { key: 'Esc', keyCode: 27, which: 27, stopPropagation: sinon.spy(), }; wrapper = mount( <DemoComponent onOpen={() => { wrapper.update(); const popover = wrapper.find(`#${defaultIds.popover}`); popover.simulate('keyDown', mockEvent); }} onKeyDown={() => { setTimeout(() => { expect(mockEvent.stopPropagation.callCount).to.equal(1); done(); }, 0); }} />, { attachTo: mountNode } ); const trigger = wrapper.find(defaultIds.trigger); trigger.simulate('click'); }); }); }); describe('focus has moved to dialog', function () { const triggerClicked = sinon.spy(); beforeEach(() => { mountNode = createMountNode({ context: this }); }); afterEach(() => { destroyMountNode({ wrapper, mountNode }); }); it('focus moves to correct node on open', function (done) { wrapper = mount( <DemoComponent onClick={triggerClicked} onOpen={() => { expect(document.activeElement.id).to.equal(`${defaultIds.popover}`); done(); }} />, { attachTo: mountNode } ); const trigger = wrapper.find(defaultIds.trigger); trigger.simulate('click', {}); }); }); describe('Disabled', function () { const triggerClicked = sinon.spy(); const popoverOpened = sinon.spy(); beforeEach(() => { mountNode = createMountNode({ context: this }); }); afterEach(() => { destroyMountNode({ wrapper, mountNode }); }); it('onOpen is not called when disabled', function () { wrapper = mount( <DemoComponent disabled onClick={triggerClicked} onOpen={popoverOpened} />, { attachTo: mountNode } ); const trigger = wrapper.find(defaultIds.trigger); trigger.simulate('click', {}); expect(popoverOpened.callCount).to.equal(0); }); }); });