@salesforce/design-system-react
Version:
Salesforce Lightning Design System for React
680 lines (615 loc) • 25.4 kB
JavaScript
;
var _react = _interopRequireDefault(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _chai = _interopRequireWildcard(require("chai"));
var _chaiEnzyme = _interopRequireDefault(require("chai-enzyme"));
var _enzyme = require("enzyme");
var _lodash = _interopRequireDefault(require("lodash.assign"));
var _enzymeHelpers = require("../../../tests/enzyme-helpers");
var _combobox = _interopRequireDefault(require("../../../components/combobox"));
var _tooltip = _interopRequireDefault(require("../../../components/tooltip"));
var _icon = _interopRequireDefault(require("../../../components/icon"));
var _filter = _interopRequireDefault(require("../../../components/combobox/filter"));
var _popover = _interopRequireDefault(require("../../../components/popover"));
var _keyCode = _interopRequireWildcard(require("../../../utilities/key-code"));
var _letterKeyCode = _interopRequireWildcard(require("../../../utilities/letter-key-code"));
var _iconSettings = _interopRequireDefault(require("../../../components/icon-settings"));
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
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 _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); }
function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }
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 _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
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, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
/* Set Chai to use chaiEnzyme for enzyme compatible assertions:
* https://github.com/producthunt/chai-enzyme
*/
_chai.default.use((0, _chaiEnzyme.default)());
var accounts = [{
id: '1',
label: 'Acme',
subTitle: 'Account • San Francisco',
type: 'account'
}, {
id: '2',
label: 'Salesforce.com, Inc.',
subTitle: 'Account • San Francisco',
type: 'account'
}, {
id: '3',
label: "Paddy's Pub",
subTitle: 'Account • Boston, MA',
type: 'account'
}, {
id: '4',
label: 'Tyrell Corp',
subTitle: 'Account • San Francisco, CA',
type: 'account',
disabled: true
}, {
id: '5',
label: 'Paper St. Soap Company',
subTitle: 'Account • Beloit, WI',
type: 'account'
}, {
id: '6',
label: 'Nakatomi Investments',
subTitle: 'Account • Chicago, IL',
type: 'account'
}, {
id: '7',
label: 'Acme Landscaping',
type: 'account'
}, {
id: '8',
label: 'Acme Construction',
subTitle: 'Account • Grand Marais, MN',
type: 'account'
}];
var accountsWithIcon = accounts.map(function (elem) {
return (0, _lodash.default)(elem, {
icon: _react.default.createElement(_icon.default, {
assistiveText: {
label: 'Account'
},
category: "standard",
name: elem.type
})
});
});
var defaultProps = {
id: 'combobox-unique-id',
labels: {
label: 'Search',
placeholder: 'Search Salesforce'
},
menuPosition: 'relative',
onOpen: function onOpen() {}
};
var propTypes = {
componentWillUpdate: _propTypes.default.func,
initialSelection: _propTypes.default.array
};
/* 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(props) {
var _this;
_classCallCheck(this, DemoComponent);
_this = _possibleConstructorReturn(this, _getPrototypeOf(DemoComponent).call(this, props));
_this.state = {
inputValue: '',
selection: _this.props.initialSelection || []
};
return _this;
}
_createClass(DemoComponent, [{
key: "componentWillUpdate",
value: function componentWillUpdate(nextProps, nextState) {
if (this.props.componentWillUpdate) {
this.props.componentWillUpdate(nextState);
}
}
}, {
key: "render",
value: function render() {
var _this2 = this;
return _react.default.createElement(_iconSettings.default, {
iconPath: "/assets/icons"
}, _react.default.createElement(_combobox.default, _extends({
events: {
onChange: function onChange(event, _ref) {
var value = _ref.value;
_this2.setState({
inputValue: value
});
},
onRequestRemoveSelectedOption: function onRequestRemoveSelectedOption(event, data) {
_this2.setState({
inputValue: '',
selection: data.selection
});
},
onSubmit: function onSubmit(event, _ref2) {
var value = _ref2.value;
_this2.setState({
inputValue: '',
selection: [].concat(_toConsumableArray(_this2.state.selection), [{
label: value,
id: 'another-account',
icon: _react.default.createElement(_icon.default, {
assistiveText: {
label: 'Account'
},
category: "standard",
name: "account"
})
}])
});
},
onSelect: function onSelect(event, data) {
_this2.setState({
inputValue: '',
selection: data.selection
});
},
onOpen: function onOpen(event) {
_this2.props.onOpen();
}
},
options: (0, _filter.default)({
inputValue: this.state.inputValue,
options: accountsWithIcon,
selection: this.state.selection
}),
selection: this.state.selection,
value: this.state.inputValue
}, this.props)));
}
}]);
return DemoComponent;
}(_react.default.Component);
DemoComponent.displayName = 'ComboboxDemoComponent';
DemoComponent.propTypes = propTypes;
DemoComponent.defaultProps = defaultProps;
var getNodes = function getNodes(_ref3) {
var wrapper = _ref3.wrapper;
return {
combobox: wrapper.find('.slds-combobox'),
input: wrapper.find('.slds-combobox input'),
menuListbox: wrapper.find('.slds-combobox .slds-listbox.slds-dropdown'),
removeSingleItem: wrapper.find('.slds-combobox button.slds-input__icon'),
selectedListbox: wrapper.find("#".concat(defaultProps.id, "-selected-listbox .slds-listbox")),
popover: wrapper.find('.slds-popover')
};
};
/* 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('SLDSCombobox', function () {
var _this3 = this;
var mountNode;
var wrapper;
describe('Assistive technology and keyboard interactions', function () {
/* Detect if presence of accessibility features such as ARIA
* roles and screen reader text is present in the DOM.
*/
beforeEach(function () {
mountNode = (0, _enzymeHelpers.createMountNode)({
context: _this3
});
});
afterEach(function () {
(0, _enzymeHelpers.destroyMountNode)({
wrapper: wrapper,
mountNode: mountNode
});
});
it('has aria-haspopup, aria-expanded is false when closed, aria-expanded is true when open', function () {
wrapper = (0, _enzyme.mount)(_react.default.createElement(DemoComponent, {
multiple: true
}), {
attachTo: mountNode
});
var nodes = getNodes({
wrapper: wrapper
});
(0, _chai.expect)(nodes.combobox).attr('aria-haspopup', 'listbox'); // closed
(0, _chai.expect)(nodes.combobox).attr('aria-expanded', 'false'); // open
nodes.input.simulate('click', {});
(0, _chai.expect)(nodes.combobox).attr('aria-expanded', 'true');
});
it('menu filters to second item, menu listbox menu item 2 aria-selected is true, input activedescendent has item 2 id, after pressing down arrow, enter selects item 2', function () {
wrapper = (0, _enzyme.mount)(_react.default.createElement(DemoComponent, {
multiple: true,
isOpen: true
}), {
attachTo: mountNode
});
var nodes = getNodes({
wrapper: wrapper
});
nodes.input.simulate('focus');
nodes.input.simulate('change', {
target: {
value: accounts[1].label
}
});
nodes.input.simulate('keyDown', _keyCode.keyObjects.DOWN);
(0, _chai.expect)(nodes.menuListbox.find('#combobox-unique-id-listbox-option-2')).to.have.attr('aria-selected', 'true');
(0, _chai.expect)(nodes.input).attr('aria-activedescendant', "".concat(defaultProps.id, "-listbox-option-2")); // select
nodes.input.simulate('keyDown', _keyCode.keyObjects.ENTER);
nodes = getNodes({
wrapper: wrapper
});
(0, _chai.expect)(nodes.input).attr('value', '');
(0, _chai.expect)(nodes.selectedListbox.find('.slds-pill__label').text()).to.equal(accounts[1].label);
});
it('Selected Listbox: remove initial first pill, remove third initial item, cycles focus (first to last), removes last and initial fifth pill, cycles focus (last to first), remove inital second and fourth pill', function (done) {
var getSelectedListboxPills = function getSelectedListboxPills(_ref4) {
var nodes = _ref4.nodes,
index = _ref4.index;
return nodes.selectedListbox.children().at(index).childAt(0);
};
var getFocusedPillLabel = function getFocusedPillLabel() {
return document.activeElement.querySelector('.slds-pill__label').innerText;
};
var selectionKeyedStates = {
removeInitialFirstPill: [accountsWithIcon[1], accountsWithIcon[2], accountsWithIcon[3], accountsWithIcon[4]],
removeThirdInitialItem: [accountsWithIcon[1], accountsWithIcon[3], accountsWithIcon[4]],
removesLastAndInitialFifthPill: [accountsWithIcon[1], accountsWithIcon[3]],
removeInitalSecondAndFourthPill: [accountsWithIcon[3]],
allPillsRemoved: []
};
var selectionIndexedStates = Object.keys(selectionKeyedStates).map(function (key, index) {
return selectionKeyedStates[key];
});
var counter = 0;
wrapper = (0, _enzyme.mount)(_react.default.createElement(DemoComponent, {
componentWillUpdate: function componentWillUpdate(prevState) {
(0, _chai.expect)(prevState.selection).to.have.members(selectionIndexedStates[counter]);
if (counter === 4) {
done();
}
counter += 1;
},
initialSelection: [accounts[0], accounts[1], accounts[2], accounts[3], accounts[4]],
multiple: true
}), {
attachTo: mountNode
});
var nodes = getNodes({
wrapper: wrapper
});
nodes.input.simulate('focus');
nodes.input.simulate('keyDown', _keyCode.keyObjects.TAB);
getSelectedListboxPills({
nodes: nodes,
index: 0
}).simulate('keyDown', _keyCode.keyObjects.DELETE);
(0, _chai.expect)(getFocusedPillLabel()).to.equal(accountsWithIcon[1].label);
getSelectedListboxPills({
nodes: getNodes({
wrapper: wrapper
}),
index: 0
}).simulate('keyDown', _keyCode.keyObjects.RIGHT);
(0, _chai.expect)(getFocusedPillLabel()).to.equal(accountsWithIcon[2].label);
getSelectedListboxPills({
nodes: getNodes({
wrapper: wrapper
}),
index: 1
}).simulate('keyDown', _keyCode.keyObjects.DELETE);
(0, _chai.expect)(getFocusedPillLabel()).to.equal(accountsWithIcon[3].label);
getSelectedListboxPills({
nodes: getNodes({
wrapper: wrapper
}),
index: 1
}).simulate('keyDown', _keyCode.keyObjects.LEFT);
getSelectedListboxPills({
nodes: getNodes({
wrapper: wrapper
}),
index: 0
}).simulate('keyDown', _keyCode.keyObjects.LEFT);
(0, _chai.expect)(getFocusedPillLabel()).to.equal(accountsWithIcon[4].label);
getSelectedListboxPills({
nodes: getNodes({
wrapper: wrapper
}),
index: 2
}).simulate('keyDown', _keyCode.keyObjects.DELETE);
(0, _chai.expect)(getFocusedPillLabel()).to.equal(accountsWithIcon[3].label);
getSelectedListboxPills({
nodes: getNodes({
wrapper: wrapper
}),
index: 1
}).simulate('keyDown', _keyCode.keyObjects.RIGHT);
(0, _chai.expect)(getFocusedPillLabel()).to.equal(accountsWithIcon[1].label);
getSelectedListboxPills({
nodes: getNodes({
wrapper: wrapper
}),
index: 0
}).simulate('keyDown', _keyCode.keyObjects.DELETE);
getSelectedListboxPills({
nodes: getNodes({
wrapper: wrapper
}),
index: 0
}).simulate('keydown', _keyCode.keyObjects.DELETE);
});
it('selects a menu item and scrolls when a letter key is pressed in read-only mode', function () {
wrapper = (0, _enzyme.mount)(_react.default.createElement(DemoComponent, {
variant: "readonly"
}), {
attachTo: mountNode
});
var nodes = getNodes({
wrapper: wrapper
});
nodes.input.simulate('keyDown', _keyCode.keyObjects.DOWN);
nodes = getNodes({
wrapper: wrapper
});
for (var i = 0; i < 3; i++) {
nodes.input.simulate('keyDown', _letterKeyCode.keyObjects.A);
}
var menuListItem = nodes.menuListbox.find('#combobox-unique-id-listbox-option-8');
(0, _chai.expect)(menuListItem.instance().className.search('slds-has-focus') > -1).to.eql(true);
var scrollTop = nodes.menuListbox.instance().scrollTop;
(0, _chai.expect)(scrollTop === 98 || scrollTop === 0).to.eql(true); // done because menu and menu item size in phantomjs is weird
});
it('selects menu items and scrolls when the down/up keys are pressed', function () {
wrapper = (0, _enzyme.mount)(_react.default.createElement(DemoComponent, {
variant: "readonly"
}), {
attachTo: mountNode
});
var nodes = getNodes({
wrapper: wrapper
});
var i;
var menuListItem;
var scrollTop;
nodes.input.simulate('keyDown', _keyCode.keyObjects.DOWN);
nodes = getNodes({
wrapper: wrapper
});
for (i = 0; i < 8; i++) {
nodes.input.simulate('keyDown', _keyCode.keyObjects.DOWN);
}
menuListItem = nodes.menuListbox.find('#combobox-unique-id-listbox-option-8');
(0, _chai.expect)(menuListItem.instance().className.search('slds-has-focus') > -1).to.eql(true);
scrollTop = nodes.menuListbox.instance().scrollTop;
(0, _chai.expect)(scrollTop === 98 || scrollTop === 0).to.eql(true); // done because menu and menu item size in phantomjs is weird
for (i = 0; i < 8; i++) {
nodes.input.simulate('keyDown', _keyCode.keyObjects.UP);
}
menuListItem = nodes.menuListbox.find('#combobox-unique-id-listbox-option-1');
(0, _chai.expect)(menuListItem.instance().className.search('slds-has-focus') > -1).to.eql(true);
scrollTop = nodes.menuListbox.instance().scrollTop;
(0, _chai.expect)(scrollTop === 4 || scrollTop === 0).to.eql(true); // done because menu and menu item size in phantomjs is weird
});
});
describe('Variant-specific', function () {
beforeEach(function () {
mountNode = (0, _enzymeHelpers.createMountNode)({
context: _this3
});
});
afterEach(function () {
(0, _enzymeHelpers.destroyMountNode)({
wrapper: wrapper,
mountNode: mountNode
});
});
it('Limit to pre-defined choices', function () {
wrapper = (0, _enzyme.mount)(_react.default.createElement(DemoComponent, {
multiple: true,
predefinedOptionsOnly: true
}), {
attachTo: mountNode
});
var nodes = getNodes({
wrapper: wrapper
});
nodes.input.simulate('focus');
nodes.input.simulate('keyDown', _letterKeyCode.keyObjects.A);
nodes.input.simulate('keyDown', _keyCode.keyObjects.ENTER);
nodes = getNodes({
wrapper: wrapper
});
(0, _chai.expect)(nodes.selectedListbox).not.to.be.present;
});
it('Inline Single Selection Remove selection', function () {
wrapper = (0, _enzyme.mount)(_react.default.createElement(DemoComponent, {
variant: "inline-listbox"
}), {
attachTo: mountNode
});
var nodes = getNodes({
wrapper: wrapper
}); // add selection
nodes.input.simulate('focus');
nodes.input.simulate('change', {
target: {
value: accounts[1].label
}
});
nodes.input.simulate('keyDown', _keyCode.keyObjects.ENTER);
(0, _chai.expect)(nodes.input).to.have.value('Salesforce.com, Inc.');
nodes = getNodes({
wrapper: wrapper
}); // remove selection
nodes.removeSingleItem.simulate('click');
nodes = getNodes({
wrapper: wrapper
});
(0, _chai.expect)(nodes.input).to.have.value('');
});
});
describe('Dialog variant', function () {
beforeEach(function () {
mountNode = (0, _enzymeHelpers.createMountNode)({
context: _this3
});
});
afterEach(function () {
(0, _enzymeHelpers.destroyMountNode)({
wrapper: wrapper,
mountNode: mountNode
});
});
it('popover opens when down arrow is pressed', function () {
wrapper = (0, _enzyme.mount)(_react.default.createElement(DemoComponent, {
variant: "popover",
popover: _react.default.createElement(_popover.default, null),
isOpen: true
}));
var nodes = getNodes({
wrapper: wrapper
});
nodes.input.simulate('keyDown', _keyCode.keyObjects.DOWN);
nodes = getNodes({
wrapper: wrapper
});
(0, _chai.expect)(nodes.popover).to.be.present;
});
it('onOpen callback is called when dialog variant', function () {
wrapper = (0, _enzyme.mount)(_react.default.createElement(DemoComponent, {
variant: "popover",
popover: _react.default.createElement(_popover.default, null),
isOpen: true
}));
var nodes = getNodes({
wrapper: wrapper
});
nodes.input.simulate('click', {});
nodes = getNodes({
wrapper: wrapper
});
(0, _chai.expect)(nodes.popover).to.be.present;
});
});
describe('Optional Props', function () {
beforeEach(function () {
mountNode = (0, _enzymeHelpers.createMountNode)({
context: _this3
});
});
afterEach(function () {
(0, _enzymeHelpers.destroyMountNode)({
wrapper: wrapper,
mountNode: mountNode
});
});
it('Displays No match found', function () {
wrapper = (0, _enzyme.mount)(_react.default.createElement(DemoComponent, {
isOpen: true
}), {
attachTo: mountNode
});
var nodes = getNodes({
wrapper: wrapper
});
nodes.input.simulate('focus');
nodes.input.simulate('change', {
target: {
value: 'Random text'
}
}); // nodes.input.simulate('keyDown', keyObjects.ENTER);
nodes = getNodes({
wrapper: wrapper
});
(0, _chai.expect)(nodes.menuListbox.find('.slds-listbox__item.slds-listbox__status').text()).to.equal('No matches found.');
});
});
describe('Input Onclick', function () {
var onOpenCallback = sinon.spy();
beforeEach(function () {
mountNode = (0, _enzymeHelpers.createMountNode)({
context: _this3
});
});
afterEach(function () {
(0, _enzymeHelpers.destroyMountNode)({
wrapper: wrapper,
mountNode: mountNode
});
});
it('onOpen callback is called', function () {
wrapper = (0, _enzyme.mount)(_react.default.createElement(DemoComponent, {
onOpen: onOpenCallback
}), {
attachTo: mountNode
});
var nodes = getNodes({
wrapper: wrapper
});
nodes.input.simulate('click', {});
(0, _chai.expect)(onOpenCallback.callCount).to.equal(1);
});
});
describe('Combobox with items disabled', function () {
beforeEach(function () {
mountNode = (0, _enzymeHelpers.createMountNode)({
context: _this3
});
});
afterEach(function () {
(0, _enzymeHelpers.destroyMountNode)({
wrapper: wrapper,
mountNode: mountNode
});
});
it('Tooltip component shows when focused on menu item.', function () {
wrapper = (0, _enzyme.mount)(_react.default.createElement(DemoComponent, {
multiple: true,
isOpen: true,
tooltipMenuItemDisabled: _react.default.createElement(_tooltip.default, null)
}), {
attachTo: mountNode
});
var nodes = getNodes({
wrapper: wrapper
});
nodes.input.simulate('focus');
nodes.input.simulate('change', {
target: {
value: accounts[3].label
}
});
nodes.input.simulate('keyDown', _keyCode.keyObjects.DOWN);
var nodeInFocus = nodes.menuListbox.find('.slds-tooltip-trigger');
var span = nodeInFocus.find('#combobox-unique-id-listbox-option-4'); // verify span is aria-selected and aria-disabled
(0, _chai.expect)(span).to.have.attr('aria-selected', 'true');
(0, _chai.expect)(span).to.have.attr('aria-disabled', 'true'); // verify tooltip is rendered
(0, _chai.expect)(nodes.menuListbox.find('#combobox-unique-id-listbox-option-help-4').length).to.equal(1);
});
});
});