@salesforce/design-system-react
Version:
Salesforce Lightning Design System for React
336 lines (286 loc) • 8.45 kB
JSX
/* eslint-env mocha */
/* global sinon */
/* eslint-disable prefer-arrow-callback */
/* eslint-disable no-unused-expressions */
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import chai, { expect } from 'chai';
import chaiEnzyme from 'chai-enzyme';
// `this.wrapper` and `this.dom` is set in the helpers file
import {
mountComponent,
unmountComponent
} from '../../../tests/enzyme-helpers';
// ### isFunction
import isFunction from 'lodash.isfunction';
import sampleNodes from '../../../utilities/sample-data/tree';
import IconSettings from '../../icon-settings';
import Tree from '../../tree';
import Search from '../../forms/input/search';
chai.use(chaiEnzyme());
const COMPONENT_CSS_CLASSES = {
base: 'slds-tree'
};
const DemoTree = createReactClass({
displayName: 'DemoTree',
// ### Prop Types
propTypes: {
branchExpandClicked: PropTypes.func,
exampleNodesIndex: PropTypes.string,
getNodes: PropTypes.func,
itemClicked: PropTypes.func,
noBranchSelection: PropTypes.bool,
searchTerm: PropTypes.string,
searchable: PropTypes.bool,
singleSelection: PropTypes.bool,
treeScrolled: PropTypes.func
},
getDefaultProps () {
return {
exampleNodesIndex: 'sampleNodesDefault',
id: 'example-tree'
};
},
getInitialState () {
const initalNodes = this.props.exampleNodesIndex
? sampleNodes[this.props.exampleNodesIndex]
: sampleNodes.sampleNodesDefault;
return {
nodes: initalNodes,
searchTerm: this.props.searchable ? 'fruit' : undefined
};
},
// By default Tree can have multiple selected nodes and folders/branches can be selected. To disable either of these, you can use the following logic. However, `props` are immutable. The node passed in shouldn't be modified, and due to object and arrays being reference variables, forceUpate is needed. This is just a "working example" not a prescription.
handleExpandClick (event, data) {
if (isFunction(this.props.branchExpandClicked)) {
this.props.branchExpandClicked(event, data);
}
data.node.loading = data.expand ? true : undefined;
// Fake delay to demonstrate use of loading node attibute
setTimeout(
(node) => {
node.loading = false;
this.forceUpdate();
},
500,
data.node
);
data.node.expanded = data.expand;
},
handleClick (event, data) {
if (this.props.singleSelection) {
data.node.selected = data.select;
this.setState({ singleSelection: data.node });
if (this.state.singleSelection) {
this.state.singleSelection.selected = undefined;
}
this.forceUpdate();
if (isFunction(this.props.itemClicked)) {
this.props.itemClicked(event, data);
}
} else if (
!this.props.noBranchSelection ||
(this.props.noBranchSelection && data.node.type !== 'branch')
) {
data.node.selected = data.select;
this.forceUpdate();
if (isFunction(this.props.itemClicked)) {
this.props.itemClicked(event, data);
}
}
},
handleScroll (event, data) {
if (isFunction(this.props.treeScrolled)) {
this.props.treeScrolled(event, data);
}
},
handleSearchChange (event) {
this.setState({ searchTerm: event.target.value });
},
render () {
return (
<IconSettings iconPath="/assets/icons">
<div>
{this.props.searchable ? (
<div>
<Search
assistiveText="Search Tree"
value={this.state.searchTerm}
onChange={this.handleSearchChange}
/>
<br />
</div>
) : null}
<Tree
id="example-tree"
getNodes={this.props.getNodes}
nodes={this.state.nodes}
onExpandClick={this.handleExpandClick}
onClick={this.handleClick}
onScroll={this.handleScroll}
searchTerm={this.state.searchTerm}
{...this.props}
/>
</div>
</IconSettings>
);
}
});
describe('Tree: ', () => {
/*
Tests
*/
describe('Default Structure and CSS', () => {
const id = 'this-is-a-container-test';
beforeEach(
mountComponent(
<DemoTree
className="this-is-a-container-test"
heading="Foods"
id={id}
listClassName="this-is-an-unordered-list-test"
listStyle={{ height: '500px' }}
/>
)
);
afterEach(unmountComponent);
it('has tree container class, list class, and heading', function () {
const container = this.wrapper.find('.slds-tree_container');
expect(container.hasClass('this-is-a-container-test')).to.be.true;
const list = this.wrapper.find(`.${COMPONENT_CSS_CLASSES.base}`);
expect(list).to.have.length(1);
expect(list.hasClass('this-is-an-unordered-list-test')).to.be.true;
expect(list.node.offsetHeight).to.equal(500);
const heading = this.wrapper.find(`#${id}__heading`);
expect(heading).to.have.length(1);
});
});
describe('Assistive Technology', () => {
beforeEach(mountComponent(<DemoTree assistiveText="Foods" />));
afterEach(unmountComponent);
it('has heading via assistiveText', function () {
const heading = this.wrapper.find(
'#example-tree__heading.slds-assistive-text'
);
expect(heading).to.have.length(1);
const ariaLabelledbyId = this.wrapper.find(
'.slds-tree[aria-labelledby="example-tree__heading"]'
);
expect(ariaLabelledbyId).to.have.length(1);
});
});
describe('Initial Expanded and Selection based on nodes', () => {
beforeEach(
mountComponent(
<DemoTree
exampleNodesIndex="sampleNodesWithInitialState"
heading="Foods"
/>
)
);
afterEach(unmountComponent);
it('has initial selection', function () {
let selectedNode = this.wrapper
.find('#example-tree-1')
.find('.slds-is-selected');
expect(selectedNode).to.have.length(1);
selectedNode = this.wrapper
.find('#example-tree-5')
.find('.slds-is-selected');
expect(selectedNode).to.have.length(1);
});
it('has initial expanded branches', function () {
const expandedBranchList = this.wrapper
.find('#example-tree-2')
.find('.slds-is-expanded');
expect(expandedBranchList.node.childNodes).to.have.length(2);
});
});
describe('Branch expands and selects on click', () => {
const itemClicked = sinon.spy();
const expandClicked = sinon.spy();
beforeEach(
mountComponent(
<DemoTree
branchExpandClicked={expandClicked}
itemClicked={itemClicked}
heading="Foods"
/>
)
);
afterEach(unmountComponent);
it('branch calls onExpandClicked and onClick', function () {
const branch = this.wrapper
.find('#example-tree-2')
.find('.slds-tree__item');
branch.simulate('click');
expect(itemClicked.callCount).to.equal(1);
const expandButton = this.wrapper
.find('#example-tree-2')
.find('.slds-button');
expandButton.simulate('click');
expect(expandClicked.callCount).to.equal(1);
});
});
describe('Item calls onClick', () => {
const itemClicked = sinon.spy();
beforeEach(
mountComponent(<DemoTree itemClicked={itemClicked} heading="Foods" />)
);
afterEach(unmountComponent);
it('item calls itemClicked', function () {
const item = this.wrapper
.find('#example-tree-1')
.find('.slds-tree__item');
item.simulate('click');
expect(itemClicked.callCount).to.equal(1);
});
});
describe('getNodes is called on initial tree', () => {
const getNodes = sinon.spy();
beforeEach(
mountComponent(
<DemoTree
exampleNodesIndex="sampleNodesWithInitialState"
getNodes={getNodes}
heading="Foods"
/>
)
);
afterEach(unmountComponent);
it('getNodes is called on initial tree', () => {
expect(getNodes.callCount).to.equal(1);
});
});
describe('Search term is highlighted', () => {
beforeEach(mountComponent(<DemoTree searchTerm="fruit" heading="Foods" />));
afterEach(unmountComponent);
it('item calls itemClicked', function () {
const markedItem = this.wrapper.find('mark');
expect(markedItem).to.have.length(1);
});
});
describe('Scrolling calls onScroll', () => {
const onScroll = sinon.spy();
beforeEach(
mountComponent(
<DemoTree
exampleNodesIndex="sampleNodesWithLargeDataset"
heading="Foods"
onScroll={onScroll}
listStyle={{
height: '300px',
overflowY: 'auto'
}}
/>
)
);
afterEach(unmountComponent);
it('scrolling calls onScroll', function () {
const list = this.wrapper.find(`.${COMPONENT_CSS_CLASSES.base}`);
list.simulate('scroll');
expect(onScroll.callCount).to.equal(1);
});
});
});