@salesforce/design-system-react
Version:
Salesforce Lightning Design System for React
334 lines (288 loc) • 8.51 kB
JSX
/* eslint-disable no-unused-vars */
/* eslint-disable react/no-find-dom-node */
import React from 'react';
import ReactDOM from 'react-dom';
import assign from 'lodash.assign';
import TestUtils from 'react-dom/test-utils';
import { expect } from 'chai';
import SLDSModal from '../../modal';
import IconSettings from '../../icon-settings';
import Settings from '../../settings';
const { Simulate } = TestUtils;
describe('SLDSModal: ', function () {
let container;
let renderedNode;
// set "app node" fixture, so no warnings are triggered.
let appNode = document.createElement('span');
appNode.id = 'app';
document.body.appendChild(appNode);
Settings.setAppElement('#app');
after(() => {
document.body.removeChild(appNode);
appNode = null;
});
afterEach(() => {
ReactDOM.unmountComponentAtNode(container);
document.body.removeChild(container);
container = null;
});
const defaultProps = {
align: 'top',
children: <div key>hello</div>,
};
const renderModal = (modalInstance) => {
container = document.createElement('div');
const opener = (
<button type="button">
<IconSettings iconPath="/assets/icons">{modalInstance}</IconSettings>
</button>
);
document.body.appendChild(container);
// eslint-disable-next-line react/no-render-return-value
renderedNode = ReactDOM.render(opener, container); // deepscan-disable-line REACT_ASYNC_RENDER_RETURN_VALUE
return renderedNode;
};
const createModal = (props) =>
React.createElement(SLDSModal, assign({}, defaultProps, props));
const getModal = (props) => renderModal(createModal(props));
const getModalContainerNode = (dom) =>
dom.querySelector('[role="dialog"]') ||
dom.querySelector('[role="alertdialog"]');
const getModalNode = (dom) => dom.querySelector('.slds-modal');
describe('Styling', () => {
beforeEach(() => {
getModal({
containerClassName: 'container-class-name-test',
contentClassName: 'content-class-name-test',
contentStyle: { height: '500px' },
isOpen: true,
portalClassName: 'portal-class-name-test',
});
});
it('has correct containerClassName, contentClassName, contentStyle, and portalClassName', () => {
const modalContainer = getModalNode(document.body).querySelector(
'.slds-modal__container.container-class-name-test'
);
expect(modalContainer).to.exist;
const modalContent = getModalNode(document.body).querySelector(
'.slds-modal__content.content-class-name-test'
);
expect(modalContent).to.exist;
expect(modalContent.style.height).to.equal('500px');
const modalPortal = document.querySelector(
'body > .portal-class-name-test'
);
expect(modalPortal).to.exist;
});
});
describe('Sizing', () => {
it('size is set to small', () => {
const cmp = getModal({
isOpen: true,
size: 'small',
});
const modal = getModalNode(document.body);
expect(modal.className).to.include('slds-modal_small');
});
it('size is set to medium', () => {
const cmp = getModal({
isOpen: true,
size: 'medium',
});
const modal = getModalNode(document.body);
expect(modal.className).to.include('slds-modal_medium');
});
it('size is set to large', () => {
const cmp = getModal({
isOpen: true,
size: 'large',
});
const modal = getModalNode(document.body);
expect(modal.className).to.include('slds-modal_large');
});
});
describe('Closed modal', () => {
beforeEach(() => {
getModal({ isOpen: false });
});
it('updates the overflow', () => {
expect(document.body.style.overflow).to.equal('inherit');
});
it('does not render to the body', () => {
expect(getModalNode(document.body)).to.be.null;
});
});
describe('Open modal', () => {
let cmp;
let closed;
let modal;
beforeEach(() => {
closed = false;
cmp = getModal({
assistiveText: {
closeButton: 'Exit',
},
isOpen: true,
size: 'large',
containerClassName: 'my-custom-class',
onRequestClose: () => {
closed = true;
},
});
modal = getModalNode(document.body);
});
it('size is set to large', () => {
expect(modal.className).to.include('slds-modal_large');
});
it('adds custom classname from modal container prop', () => {
expect(modal.firstChild.className).to.include('my-custom-class');
});
it('renders correct assistive text/title for close button', () => {
const closeBtn = modal.querySelector('.slds-modal__close');
expect(closeBtn.title).to.equal('Exit');
});
it('calls onRequestClose', () => {
const closeBtn = modal.querySelector('.slds-modal__close');
expect(closed).to.be.false;
Simulate.click(closeBtn, {});
expect(closed).to.be.true;
});
});
describe('Proper HTML markup', () => {
it('dismissible modal has role=dialog', () => {
// eslint-disable-next-line no-unused-vars
const cmp = getModal({
isOpen: true,
size: 'medium',
});
const modal = getModalContainerNode(document.body);
const role = modal.getAttribute('role');
expect(role).to.equal('dialog');
});
it('non-dismissible modal has role=alertdialog', () => {
const cmp = getModal({
isOpen: true,
disableClose: true,
});
const modal = getModalContainerNode(document.body);
const role = modal.getAttribute('role');
expect(role).to.equal('alertdialog');
});
});
describe('Open with custom header and header className', () => {
let modal;
beforeEach(() => {
getModal({
header: <div id="art-vandelay">Art vandelay</div>,
headerClassName: 'art-vandelay',
isOpen: true,
});
modal = getModalNode(document.body);
});
it('adds the header', () => {
const customHeader = modal.querySelector('#art-vandelay');
expect(customHeader).to.not.be.null;
});
it('adds the custom header class', () => {
expect(modal.querySelector('.slds-modal__header').className).to.include(
'art-vandelay'
);
});
});
describe('Open with Prompt and Footer', () => {
let modal;
beforeEach(() => {
const feet = <div className="toes">Toes</div>;
getModal({
isOpen: true,
prompt: 'warning',
heading: 'are you sure?',
footer: feet,
});
modal = getModalNode(document.body);
});
it('adds the default h1 heading element', () => {
const header = modal.querySelector('section .slds-modal__header h1');
expect(header).to.not.be.null;
});
it('adds the footer', () => {
const footer = modal.querySelector('.slds-modal__footer');
expect(footer.className).to.include('slds-theme_default');
});
it('adds the prompt class', () => {
expect(modal.className).to.include('slds-modal_prompt');
});
it('adds the prompt theme class', () => {
expect(modal.querySelector('.slds-modal__header').className).to.include(
'slds-theme_warning'
);
});
it('adds the footer html content', () => {
expect(modal.querySelector('.toes').innerHTML).to.equal('Toes');
});
});
describe('Open Directional', () => {
let modal;
beforeEach(() => {
const feet = [
<div key="test-content1" className="toes">
Toe 1
</div>,
<div key="test-content2" className="toes">
Toe 2
</div>,
];
getModal({
isOpen: true,
directional: true,
footer: feet,
});
modal = getModalNode(document.body);
});
it('adds the footer', () => {
const footer = modal.querySelector('.slds-modal__footer_directional');
expect(footer.className).to.include('slds-modal__footer');
});
});
describe('Keyboard behavior', () => {
let modal;
beforeEach(() => {
const feet = [
<button type="button" key="test-content1" className="cancel">
Cancel
</button>,
<button type="button" key="test-content2" className="save">
Save
</button>,
];
getModal({
isOpen: true,
directional: true,
footer: feet,
});
modal = getModalNode(document.body);
});
it('first tab focuses close button', () => {
// There an issue with this test, functionality works fine.
// setTimeout(() => {
// Simulate.keyDown(modal, {
// key: 'Tab',
// keyCode: 9,
// which: 9,
// });
// setTimeout(() => {
// expect(document.activeElement.className).to.include(
// 'slds-modal__close'
// );
// done();
// }, 200);
// }, 200);
});
it('enter on close button works', () => {
// TODO: simulate enter on close button and modal is undefined
});
it('traps focus inside Modal', () => {
// TODO: simulate tabbing around inside of Modal
});
});
});