@salesforce/design-system-react
Version:
Salesforce Lightning Design System for React
609 lines (528 loc) • 22 kB
JavaScript
;
var _react = require("react");
var _react2 = _interopRequireDefault(_react);
var _reactDom = require("react-dom");
var _reactDom2 = _interopRequireDefault(_reactDom);
var _propTypes = require("prop-types");
var _propTypes2 = _interopRequireDefault(_propTypes);
var _chai = require("chai");
var _chai2 = _interopRequireDefault(_chai);
var _chaiEnzyme = require("chai-enzyme");
var _chaiEnzyme2 = _interopRequireDefault(_chaiEnzyme);
var _enzyme = require("enzyme");
var _lodash = require("lodash.assign");
var _lodash2 = _interopRequireDefault(_lodash);
var _testUtils = require("react-dom/test-utils");
var _enzymeHelpers = require("../../../tests/enzyme-helpers");
var _menuDropdown = require("../../menu-dropdown");
var _menuDropdown2 = _interopRequireDefault(_menuDropdown);
var _iconSettings = require("../../icon-settings");
var _iconSettings2 = _interopRequireDefault(_iconSettings);
var _menuList = require("../../utilities/menu-list");
var _menuList2 = _interopRequireDefault(_menuList);
var _keyCode = require("../../../utilities/key-code");
var _keyCode2 = _interopRequireDefault(_keyCode);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
/* Set Chai to use chaiEnzyme for enzyme compatible assertions:
* https://github.com/producthunt/chai-enzyme
*/
_chai2.default.use((0, _chaiEnzyme2.default)());
var menuOptions = [{
label: 'A super short',
value: 'A0'
}, {
label: 'B Option Super Super Long',
value: 'B0'
}, {
label: 'C Option',
value: 'C0'
}, {
disabled: true,
label: 'D Option',
value: 'D0'
}];
var defaultProps = {
iconCategory: 'utility',
iconName: 'down',
id: 'sample-dropdown',
label: 'Test',
menuPosition: 'relative',
openOn: 'click',
options: menuOptions,
placeholder: 'Select a contact',
value: 'B0'
};
/* eslint-disable react/prop-types */
var DropdownCustomContent = function DropdownCustomContent(props) {
return _react2.default.createElement("div", {
id: "custom-dropdown-menu-content"
}, _react2.default.createElement("div", {
className: "slds-m-around--medium"
}, _react2.default.createElement("div", {
className: "slds-tile slds-tile--board slds-m-horizontal--small"
}, _react2.default.createElement("p", {
className: "tile__title slds-text-heading--small"
}, "Art Vandelay"), _react2.default.createElement("div", {
className: "slds-tile__detail"
}, _react2.default.createElement("p", {
className: "slds-truncate"
}, _react2.default.createElement("a", {
id: "custom-dropdown-menu-content-link",
className: "slds-m-right--medium",
href: "javascript:void(0);",
onClick: props.onClick
}, "Settings"), _react2.default.createElement("a", {
href: "javascript:void(0);",
onClick: props.onClick
}, "Log Out"))))));
};
DropdownCustomContent.displayName = 'DropdownCustomContent';
/* 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.
*/
var DemoComponent =
/*#__PURE__*/
function (_React$Component) {
_inherits(DemoComponent, _React$Component);
function DemoComponent() {
_classCallCheck(this, DemoComponent);
return _possibleConstructorReturn(this, (DemoComponent.__proto__ || Object.getPrototypeOf(DemoComponent)).apply(this, arguments));
}
_createClass(DemoComponent, [{
key: "render",
value: function render() {
return _react2.default.createElement(_iconSettings2.default, {
iconPath: "/assets/icons"
}, _react2.default.createElement(_menuDropdown2.default, _extends({}, defaultProps, this.props), this.props.children));
}
}]);
return DemoComponent;
}(_react2.default.Component);
DemoComponent.displayName = 'DropdownDemoComponent';
DemoComponent.defaultProps = defaultProps;
var getNodes = function getNodes(_ref) {
var wrapper = _ref.wrapper;
return {
trigger: wrapper.find('.slds-dropdown-trigger'),
button: wrapper.find('.slds-dropdown-trigger button'),
menu: wrapper.find('.slds-dropdown'),
customContent: wrapper.find('#custom-dropdown-menu-content'),
customContentLink: wrapper.find('#custom-dropdown-menu-content #custom-dropdown-menu-content-link')
};
};
/* 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('SLDSMenuDropdown', function () {
var wrapper;
describe('Styling', function () {
beforeEach((0, _enzymeHelpers.mountComponent)(_react2.default.createElement(DemoComponent, {
menuStyle: {
height: '500px'
}
})));
afterEach(_enzymeHelpers.unmountComponent);
it('has correct CSS classes and style', function () {
var nodes = getNodes({
wrapper: this.wrapper
});
nodes.button.simulate('click', {});
var openNodes = getNodes({
wrapper: this.wrapper
});
(0, _chai.expect)(openNodes.menu).to.exist;
(0, _chai.expect)(openNodes.menu).to.have.style('height', '500px');
});
});
describe('Custom Content Present', function () {
beforeEach((0, _enzymeHelpers.mountComponent)(_react2.default.createElement(DemoComponent, {
nubbinPosition: "top left",
openOn: "click"
}, _react2.default.createElement(DropdownCustomContent, null), _react2.default.createElement(_menuList2.default, {
options: [{
label: 'Custom Content Option'
}].concat(menuOptions)
}))));
afterEach(_enzymeHelpers.unmountComponent);
it('has content with custom ID is present', function () {
var nodes = getNodes({
wrapper: this.wrapper
});
nodes.button.simulate('click', {});
var openNodes = getNodes({
wrapper: this.wrapper
});
(0, _chai.expect)(openNodes.customContent.length).to.equal(1);
});
it('closes when custom content is clicked', function () {
var nodes = getNodes({
wrapper: this.wrapper
});
nodes.button.simulate('click', {});
var openNodes = getNodes({
wrapper: this.wrapper
});
openNodes.customContentLink.simulate('click', {});
var closedNodes = getNodes({
wrapper: this.wrapper
});
(0, _chai.expect)(closedNodes.customContent.length).to.equal(0);
});
it("has additional ListItem from list child's options prop", function () {
var nodes = getNodes({
wrapper: this.wrapper
});
var buttonId = nodes.trigger.prop('id');
nodes.button.simulate('click', {});
var openNodes = getNodes({
wrapper: this.wrapper
});
(0, _chai.expect)(openNodes.menu.find("li#".concat(buttonId, "-item-0")).text()).to.equal('Custom Content Option');
});
});
describe('Clickable', function () {
var onClick = sinon.spy();
beforeEach((0, _enzymeHelpers.mountComponent)(_react2.default.createElement(DemoComponent, {
onClick: onClick
})));
afterEach(_enzymeHelpers.unmountComponent);
it('does not expand on hover', function () {
var nodes = getNodes({
wrapper: this.wrapper
});
(0, _chai.expect)(nodes.menu.length).to.equal(0);
nodes.trigger.simulate('mouseEnter', {});
var hoverNodes = getNodes({
wrapper: this.wrapper
});
(0, _chai.expect)(hoverNodes.menu.length).to.equal(0);
});
it('expands/contracts on click', function () {
var nodes = getNodes({
wrapper: this.wrapper
});
(0, _chai.expect)(nodes.menu.length).to.equal(0);
nodes.trigger.simulate('click', {});
var openNodes = getNodes({
wrapper: this.wrapper
});
(0, _chai.expect)(openNodes.menu.length).to.equal(1);
openNodes.trigger.simulate('click', {});
var closedNodes = getNodes({
wrapper: this.wrapper
});
(0, _chai.expect)(closedNodes.menu.length).to.equal(0);
});
it('preserves click behavior', function () {
onClick.reset();
var nodes = getNodes({
wrapper: this.wrapper
});
nodes.trigger.simulate('click', {});
(0, _chai.expect)(onClick.calledOnce);
});
});
describe('Expanded', function () {
var selected;
beforeEach((0, _enzymeHelpers.mountComponent)(_react2.default.createElement(DemoComponent, {
onSelect: function onSelect(selectedOption) {
selected = selectedOption;
}
})));
afterEach(_enzymeHelpers.unmountComponent);
it('selects an item on click', function () {
var nodes = getNodes({
wrapper: this.wrapper
});
(0, _chai.expect)(nodes.menu.length).to.equal(0);
nodes.trigger.simulate('click', {});
var openNodes = getNodes({
wrapper: this.wrapper
});
openNodes.menu.find('li a').first().simulate('click', {});
(0, _chai.expect)(selected.value).to.equal('A0');
});
});
describe('accessible markup for label Dropdowns', function () {
beforeEach((0, _enzymeHelpers.mountComponent)(_react2.default.createElement(DemoComponent, null)));
afterEach(_enzymeHelpers.unmountComponent);
it('<ul> has role menu & aria-labelledby', function () {
var nodes = getNodes({
wrapper: this.wrapper
});
nodes.trigger.simulate('click', {});
var openNodes = getNodes({
wrapper: this.wrapper
});
(0, _chai.expect)(openNodes.menu.find('ul')).to.have.attr('role', 'menu');
var nodeId = openNodes.trigger.prop('id');
(0, _chai.expect)(openNodes.menu.find('ul')).attr('aria-labelledby', nodeId);
});
it('<a> inside <li> has role menuitem', function () {
var nodes = getNodes({
wrapper: this.wrapper
});
nodes.trigger.simulate('click', {});
var openNodes = getNodes({
wrapper: this.wrapper
});
var anchorRole = openNodes.menu.find('li a').first().prop('role');
var match = anchorRole === 'menuitem' || anchorRole === 'menuitemradio' || anchorRole === 'menuitemcheckbox';
(0, _chai.expect)(match).to.be.true;
});
it('if option.disabled, add aria-disabled to <a> that has role menuitem', function () {
var nodes = getNodes({
wrapper: this.wrapper
});
nodes.trigger.simulate('click', {});
var openNodes = getNodes({
wrapper: this.wrapper
});
var lastItemAriaDisabledRole = openNodes.menu.find('li a').at(3).prop('aria-disabled');
(0, _chai.expect)(lastItemAriaDisabledRole).to.be.true;
});
});
describe('accessible markup for Icon Only Dropdowns', function () {
beforeEach((0, _enzymeHelpers.mountComponent)(_react2.default.createElement(DemoComponent, {
assistiveText: "more options",
buttonVariant: "icon",
checkmark: true,
iconCategory: "utility",
iconName: "down",
iconVariant: "border-filled"
})));
afterEach(_enzymeHelpers.unmountComponent);
it('<button> has assistiveText', function () {
var nodes = getNodes({
wrapper: this.wrapper
});
(0, _chai.expect)(nodes.button.find('.slds-assistive-text').text()).to.equal('more options');
});
});
describe('Keyboard behavior', function () {
var selected;
beforeEach((0, _enzymeHelpers.mountComponent)(_react2.default.createElement(DemoComponent, {
onSelect: function onSelect(selectedOption) {
selected = selectedOption;
}
})));
afterEach(_enzymeHelpers.unmountComponent);
it('opens menu with enter', function () {
var nodes = getNodes({
wrapper: this.wrapper
});
(0, _chai.expect)(nodes.menu.length).to.equal(0);
nodes.button.simulate('keyDown', _keyCode.keyObjects.ENTER);
var openNodes = getNodes({
wrapper: this.wrapper
});
(0, _chai.expect)(openNodes.menu.length).to.equal(1);
});
it('opens menu with down arrow key', function () {
var nodes = getNodes({
wrapper: this.wrapper
});
(0, _chai.expect)(nodes.menu.length).to.equal(0);
nodes.button.simulate('keyDown', _keyCode.keyObjects.DOWN);
var openNodes = getNodes({
wrapper: this.wrapper
});
(0, _chai.expect)(openNodes.menu.length).to.equal(1);
});
it('selects an item with keyboard', function () {
var nodes = getNodes({
wrapper: this.wrapper
});
nodes.trigger.simulate('click', {});
var openNodes = getNodes({
wrapper: this.wrapper
});
openNodes.menu.simulate('keyDown', _keyCode.keyObjects.DOWN);
openNodes.menu.simulate('keyDown', _keyCode.keyObjects.DOWN);
openNodes.menu.simulate('keyDown', _keyCode.keyObjects.ENTER);
(0, _chai.expect)(selected.value).to.equal('B0');
});
it('closes Menu on esc', function () {
var nodes = getNodes({
wrapper: this.wrapper
});
(0, _chai.expect)(nodes.menu.length).to.equal(0);
nodes.trigger.simulate('click', {});
var openNodes = getNodes({
wrapper: this.wrapper
});
(0, _chai.expect)(openNodes.menu.length).to.equal(1);
openNodes.menu.find('.slds-dropdown__item a').first().simulate('keyDown', _keyCode.keyObjects.ESCAPE);
var closedNodes = getNodes({
wrapper: this.wrapper
});
(0, _chai.expect)(closedNodes.menu.length).to.equal(0);
});
});
describe('multiple selection', function () {
beforeEach((0, _enzymeHelpers.mountComponent)(_react2.default.createElement(DemoComponent, {
multiple: true,
checkmark: true
})));
afterEach(_enzymeHelpers.unmountComponent);
it('selects multiple items and renders checkmarks', function () {
var nodes = getNodes({
wrapper: this.wrapper
});
nodes.trigger.simulate('click', {});
var openNodes = getNodes({
wrapper: this.wrapper
});
(0, _chai.expect)(openNodes.menu.find('.slds-dropdown__item svg').length).to.equal(1);
openNodes.menu.find('.slds-dropdown__item a').at(0).simulate('click');
openNodes = getNodes({
wrapper: this.wrapper
});
(0, _chai.expect)(openNodes.menu.find('.slds-dropdown__item svg').length).to.equal(2);
});
}); // Hover and hybrid hover UX patterns are not approved UX patterns due to accessibility concerns
describe('Hoverable', function () {
var body;
var renderDropdown = function renderDropdown(inst) {
body = document.createElement('div');
document.body.appendChild(body); // eslint-disable-next-line react/no-render-return-value
return _reactDom2.default.render(_react2.default.createElement(_iconSettings2.default, {
iconPath: "/assets/icons"
}, inst), body);
};
function removeDropdownTrigger() {
_reactDom2.default.unmountComponentAtNode(body);
document.body.removeChild(body);
}
var createDropdown = function createDropdown(props) {
return _react2.default.createElement(_menuDropdown2.default, (0, _lodash2.default)({}, defaultProps, props));
};
createDropdown.displayName = 'createDropdown';
var dropItDown = function dropItDown(props, children) {
return renderDropdown(createDropdown(props, children));
};
var getMenu = function getMenu(dom) {
return dom.querySelector('.slds-dropdown');
};
var cmp;
var btn;
beforeEach(function () {
cmp = dropItDown({
buttonClassName: 'dijkstrafied',
openOn: 'hover',
hoverCloseDelay: 2
});
btn = (0, _testUtils.findRenderedDOMComponentWithClass)(cmp, 'slds-dropdown-trigger');
});
afterEach(function () {
removeDropdownTrigger(btn);
});
it('gives the button correct aria properties', function () {
(0, _chai.expect)(btn.firstChild.getAttribute('aria-haspopup')).to.equal('true');
});
it('sets the label', function () {
(0, _chai.expect)(btn.textContent).to.equal('Test');
});
it('expands the dropdown on hover', function () {
(0, _chai.expect)(getMenu(body)).to.equal(null);
_testUtils.Simulate.mouseEnter(btn, {});
(0, _chai.expect)(getMenu(body).className).to.include('slds-dropdown');
_testUtils.Simulate.mouseLeave(btn, {});
});
it('closes on blur based on timeout delay', function (done) {
(0, _chai.expect)(getMenu(body)).to.equal(null);
_testUtils.Simulate.mouseEnter(btn, {});
_testUtils.Simulate.mouseLeave(btn);
(0, _chai.expect)(getMenu(body)).to.not.equal(null);
setTimeout(function () {
(0, _chai.expect)(getMenu(body)).to.equal(null);
done();
}, 3);
});
it("doesn't close on quick hover outside", function (done) {
(0, _chai.expect)(getMenu(body)).to.equal(null);
_testUtils.Simulate.mouseEnter(btn, {});
_testUtils.Simulate.mouseLeave(btn);
setTimeout(function () {
_testUtils.Simulate.mouseEnter(btn, {});
(0, _chai.expect)(getMenu(body)).to.not.equal(null);
setTimeout(function () {
(0, _chai.expect)(getMenu(body)).to.not.equal(null);
done();
}, 3);
}, 1);
});
});
describe('Hybrid-able', function () {
var body;
var renderDropdown = function renderDropdown(inst) {
body = document.createElement('div');
document.body.appendChild(body); // eslint-disable-next-line react/no-render-return-value
return _reactDom2.default.render(_react2.default.createElement(_iconSettings2.default, {
iconPath: "/assets/icons"
}, inst), body);
};
function removeDropdownTrigger() {
_reactDom2.default.unmountComponentAtNode(body);
document.body.removeChild(body);
}
var createDropdown = function createDropdown(props) {
return _react2.default.createElement(_menuDropdown2.default, (0, _lodash2.default)({}, defaultProps, props));
};
createDropdown.displayName = 'createDropdown';
var dropItDown = function dropItDown(props, children) {
return renderDropdown(createDropdown(props, children));
};
var getMenu = function getMenu(dom) {
return dom.querySelector('.slds-dropdown');
};
var cmp;
var btn;
var onClick = sinon.spy();
beforeEach(function () {
cmp = dropItDown({
openOn: 'hybrid',
onClick: onClick,
hoverCloseDelay: 1
});
btn = (0, _testUtils.findRenderedDOMComponentWithClass)(cmp, 'slds-dropdown-trigger');
});
afterEach(function () {
removeDropdownTrigger(btn);
});
it('doesnt expand on hover', function () {
(0, _chai.expect)(getMenu(body)).to.equal(null);
_testUtils.Simulate.mouseEnter(btn, {});
(0, _chai.expect)(getMenu(body)).to.equal(null);
});
it('opens on click, closes on mouseLeave', function (done) {
// open
(0, _chai.expect)(getMenu(body)).to.equal(null);
_testUtils.Simulate.click(btn, {});
(0, _chai.expect)(getMenu(body).className).to.include('slds-dropdown'); // close
_testUtils.Simulate.mouseEnter(btn, {});
_testUtils.Simulate.mouseLeave(btn);
(0, _chai.expect)(getMenu(body)).to.not.equal(null);
setTimeout(function () {
(0, _chai.expect)(getMenu(body)).to.equal(null);
done();
}, 2);
});
});
});