diff --git a/integration/specs/Tree/tree-1.spec.js b/integration/specs/Tree/tree-1.spec.js new file mode 100644 index 000000000..0d578e4fa --- /dev/null +++ b/integration/specs/Tree/tree-1.spec.js @@ -0,0 +1,64 @@ +const PageTree = require('../../../src/components/Tree/pageObject'); +const { TAB_KEY, ENTER_KEY, SPACE_KEY } = require('../../constants'); + +const TREE = '#tree-component-1'; + +describe('Tree basic', () => { + beforeAll(() => { + browser.url('/#!/Tree/1'); + }); + beforeEach(() => { + browser.refresh(); + const component = $(TREE); + component.waitForExist(); + }); + + it('should expand the node when it is collapse and its button icon is clicked', () => { + const tree = new PageTree(TREE); + const node = tree.getNode(3); + node.click(); + expect(node.isExpanded()).toBe(true); + }); + it('should collapse the node when it is expand and its button icon is clicked', () => { + const tree = new PageTree(TREE); + const node = tree.getNode(2); + node.click(); + expect(node.isExpanded()).toBe(false); + }); + it('should move focus to the next button icon when the first button icon is focused and press tab', () => { + const tree = new PageTree(TREE); + const firstNode = tree.getNode(2); + const secondNode = tree.getNode(3); + firstNode.click(); + browser.keys(TAB_KEY); + expect(secondNode.hasFocus()).toBe(true); + }); + it('should expand the node when its button icon is focused, press enter and the node was initially expanded', () => { + const tree = new PageTree(TREE); + const node = tree.getNode(2); + node.click(); + browser.keys(ENTER_KEY); + expect(node.isExpanded()).toBe(true); + }); + it('should collapse the node when its button icon is focused, press enter and the node was initially collapse', () => { + const tree = new PageTree(TREE); + const node = tree.getNode(3); + node.click(); + browser.keys(ENTER_KEY); + expect(node.isExpanded()).toBe(false); + }); + it('should expand the node when its button icon is focused, press space and the node was initially expanded', () => { + const tree = new PageTree(TREE); + const node = tree.getNode(2); + node.click(); + browser.keys(SPACE_KEY); + expect(node.isExpanded()).toBe(true); + }); + it('should collapse the node when its button icon is focused, press space and the node was initially collapse', () => { + const tree = new PageTree(TREE); + const node = tree.getNode(3); + node.click(); + browser.keys(SPACE_KEY); + expect(node.isExpanded()).toBe(false); + }); +}); diff --git a/src/components/Tree/__test__/child.spec.js b/src/components/Tree/__test__/child.spec.js new file mode 100644 index 000000000..7b486200c --- /dev/null +++ b/src/components/Tree/__test__/child.spec.js @@ -0,0 +1,24 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import Child from './../child'; + +describe('', () => { + it('should render the PrimitiveCheckbox component when isChecked prop has the right value', () => { + [true, false, 'indeterminate'].forEach(value => { + const component = mount(); + expect(component.find('PrimitiveCheckbox').exists()).toBe(true); + }); + }); + it('should not render the PrimitiveCheckbox component when isChecked prop has the wrong value', () => { + ['indeterminates', 'one', 'six'].forEach(value => { + const component = mount(); + expect(component.find('PrimitiveCheckbox').exists()).toBe(false); + }); + }); + it('should render the TreeChildren component when children prop is not undefined', () => { + const children = [{ label: 'Tree Item' }, { label: 'Tree Item' }]; + // eslint-disable-next-line react/no-children-prop + const component = mount(); + expect(component.find('TreeChildren').exists()).toBe(true); + }); +}); diff --git a/src/components/Tree/__test__/expandCollapseButton.spec.js b/src/components/Tree/__test__/expandCollapseButton.spec.js new file mode 100644 index 000000000..e0a063dba --- /dev/null +++ b/src/components/Tree/__test__/expandCollapseButton.spec.js @@ -0,0 +1,40 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import ExpandCollapseButton from './../expandCollapseButton'; + +describe('', () => { + it('should return the Spinner component when isLoading prop is true', () => { + const component = mount(); + expect(component.find('Spinner').exists()).toBe(true); + }); + it('should return the ButtonIcon component when hasChildren prop is true', () => { + const component = mount(); + expect(component.find('ButtonIcon').exists()).toBe(true); + }); + it('should set the right icon when isExpanded prop is true', () => { + const component = mount(); + expect( + component + .find('ButtonIcon') + .find('DownArrow') + .exists(), + ).toBe(true); + }); + it('should set the right icon when isExpanded prop is false', () => { + const component = mount(); + expect( + component + .find('ButtonIcon') + .find('RightArrow') + .exists(), + ).toBe(true); + }); + it('should fire onclick callback when ButtonIcon is clicked', () => { + const onClickMock = jest.fn(); + const component = mount( + , + ); + component.find('ButtonIcon').simulate('click'); + expect(onClickMock).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/components/Tree/__test__/tree.spec.js b/src/components/Tree/__test__/tree.spec.js new file mode 100644 index 000000000..734ca6756 --- /dev/null +++ b/src/components/Tree/__test__/tree.spec.js @@ -0,0 +1,67 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import Tree from '../index'; + +const data = [ + { label: 'Tree Item', isChecked: false }, + { label: 'Tree Item', isChecked: false }, + { + label: 'Tree Branch', + isLoading: false, + isExpanded: true, + isChecked: false, + children: [ + { label: 'Tree Item', isChecked: false }, + { + label: 'Tree Branch', + isLoading: false, + isExpanded: true, + isChecked: false, + children: [{ label: 'Tree Item', isChecked: false }], + }, + ], + }, + { + label: 'Tree Branch', + isExpanded: true, + isChecked: false, + children: [ + { label: 'Tree Item', isChecked: false }, + { + label: 'Tree Branch', + isLoading: false, + isExpanded: true, + isChecked: false, + children: [{ label: 'Tree Item', isChecked: false }], + }, + ], + }, +]; + +describe('', () => { + it('should call onExpandCollapse with the right parameters when the button is clicked', () => { + const nodePath = [2, 1]; + const onExpandCollapsekMock = jest.fn(); + const component = mount(); + component + .find('ButtonIcon') + .at(1) + .simulate('click'); + expect(onExpandCollapsekMock).toHaveBeenCalledWith({ nodePath }); + }); + it('should call onSelect with the right parameters when the node is selected', () => { + const nodePath = [2]; + const onSelectMock = jest.fn(); + const component = mount(); + component + .find('PrimitiveCheckbox') + .at(2) + .find('input') + .simulate('change'); + expect(onSelectMock).toHaveBeenCalledWith({ nodePath }); + }); + it('should render the correct number of children', () => { + const component = mount(); + expect(component.find('Child').length).toBe(10); + }); +}); diff --git a/src/components/Tree/child.js b/src/components/Tree/child.js index fa8c2422a..350f040f0 100644 --- a/src/components/Tree/child.js +++ b/src/components/Tree/child.js @@ -26,8 +26,8 @@ export default function Child(props) { const hasCheckbox = typeof isChecked === 'boolean' || isChecked === 'indeterminate'; const hasIcon = !!icon; return ( - - + + { + it('should return the right node when nodePath has only one element', () => { + const nodePath = [2]; + const expectedNode = { + label: 'Tree Branch', + isExpanded: true, + children: [ + { label: 'Tree Item' }, + { + label: 'Tree Branch', + isLoading: false, + children: [{ label: 'Tree Item' }], + }, + ], + }; + expect(getNode(tree, nodePath)).toStrictEqual(expectedNode); + }); + it('should return the right node when nodePath has more than one element', () => { + const nodePath = [2, 1]; + const expectedNode = { + label: 'Tree Branch', + isLoading: false, + children: [{ label: 'Tree Item' }], + }; + expect(getNode(tree, nodePath)).toStrictEqual(expectedNode); + }); +}); diff --git a/src/components/Tree/index.d.ts b/src/components/Tree/index.d.ts index ba8954e61..b291c5848 100644 --- a/src/components/Tree/index.d.ts +++ b/src/components/Tree/index.d.ts @@ -9,8 +9,10 @@ interface DataItem { label?: ReactNode; icon?: ReactNode; isExpanded?: boolean; + isLoading?: boolean; isChecked?: boolean | 'indeterminate'; children?: DataItem[]; + id?: string; } export interface TreeProps extends BaseProps { diff --git a/src/components/Tree/index.js b/src/components/Tree/index.js index 456d54ec7..9990b306d 100644 --- a/src/components/Tree/index.js +++ b/src/components/Tree/index.js @@ -9,10 +9,10 @@ import getNode from './helpers/getNode'; * @category Layout */ export default function Tree(props) { - const { data, onExpandCollapse, onSelect, className, style } = props; + const { data, onExpandCollapse, onSelect, className, style, id } = props; return ( - + {}, className: undefined, style: undefined, + id: undefined, }; /** diff --git a/src/components/Tree/pageObject/index.js b/src/components/Tree/pageObject/index.js new file mode 100644 index 000000000..37d77700e --- /dev/null +++ b/src/components/Tree/pageObject/index.js @@ -0,0 +1,33 @@ +const PageNodeItem = require('./node'); + +/** + * Tree page object class. + * @class + */ +class PageTree { + /** + * Create a new Tree page object. + * @constructor + * @param {string} rootElement - The selector of the Tree root element. + */ + constructor(rootElement) { + this.rootElement = rootElement; + } + + /** + * Returns a new Node page object of the element in item position. + * @method + * @param {number} itemPosition - The base 0 index of the Node. + */ + getNode(itemPosition) { + const items = $(this.rootElement).$$('[data-id="node-element-li"]'); + if (items[itemPosition]) { + return new PageNodeItem( + `${this.rootElement} [data-id="node-element-li"]:nth-child(${itemPosition + 1})`, + ); + } + return null; + } +} + +module.exports = PageTree; diff --git a/src/components/Tree/pageObject/node.js b/src/components/Tree/pageObject/node.js new file mode 100644 index 000000000..9984b7990 --- /dev/null +++ b/src/components/Tree/pageObject/node.js @@ -0,0 +1,60 @@ +/** + * Node page object class. + * @class + */ +class PageNodeItem { + /** + * Create a new Node page object. + * @constructor + * @param {string} rootElement - The selector of the Node root element. + */ + constructor(rootElement) { + this.rootElement = rootElement; + } + + /** + * Clicks the button icon element. + * @method + */ + click() { + $(this.rootElement) + .$('[data-id="node-element"] > button') + .click(); + } + + /** + * Returns true when the button icon has focus. + * @method + * @returns {bool} + */ + hasFocus() { + return $(this.rootElement) + .$('[data-id="node-element"] > button') + .isFocused(); + } + + /** + * Returns true when the node is expanded, false otherwise. + * @method + * @returns {bool} + */ + isExpanded() { + return $(this.rootElement) + .$('[data-id="node-element-li"]') + .isDisplayed(); + } + + /** + * Returns the label of the node. + * @method + * @returns {string} + */ + getLabel() { + return $(this.rootElement) + .$('[data-id="node-element"]') + .$('h1') + .getText(); + } +} + +module.exports = PageNodeItem; diff --git a/src/components/Tree/readme.md b/src/components/Tree/readme.md index fbe6e9027..1c3966ba6 100644 --- a/src/components/Tree/readme.md +++ b/src/components/Tree/readme.md @@ -36,6 +36,7 @@ setState({ data: state.data }); }