diff --git a/openbis_ng_ui/package.json b/openbis_ng_ui/package.json index 9fb7d60bc2882f63e6a1e763a06bf04155434100..1846ade5180c31a1c342aa46fafe26f35559e9ce 100644 --- a/openbis_ng_ui/package.json +++ b/openbis_ng_ui/package.json @@ -50,7 +50,9 @@ "url-loader": "1.1.1", "webpack": "4.6.0", "webpack-cli": "2.0.14", - "webpack-dev-server": "^3.1.14" + "webpack-dev-server": "^3.1.14", + "enzyme": "3.9.0", + "enzyme-adapter-react-16": "1.12.1" }, "scripts": { "dev": "webpack-dev-server --hot --config webpack.config.dev.js", @@ -65,6 +67,7 @@ "reporters": [ "default", "jest-junit" - ] + ], + "setupTestFrameworkScriptFile": "<rootDir>srcTest/setupTests.js" } } diff --git a/openbis_ng_ui/src/components/browser/Browser.jsx b/openbis_ng_ui/src/components/browser/Browser.jsx index 90956174cb73e4486933f204908906449a075373..24ef17e8aca19ad69f1c10fd1f3ad88394cc54d0 100644 --- a/openbis_ng_ui/src/components/browser/Browser.jsx +++ b/openbis_ng_ui/src/components/browser/Browser.jsx @@ -1,7 +1,6 @@ import React from 'react' import {connect} from 'react-redux' import logger from '../../common/logger.js' -import store from '../../store/store.js' import * as selectors from '../../store/selectors/selectors.js' import * as actions from '../../store/actions/actions.js' @@ -9,12 +8,8 @@ import Loading from '../loading/Loading.jsx' import BrowserFilter from './BrowserFilter.jsx' import BrowserNodes from './BrowserNodes.jsx' -function getCurrentPage(){ - return selectors.getCurrentPage(store.getState()) -} - function mapStateToProps(state){ - let currentPage = getCurrentPage() + let currentPage = selectors.getCurrentPage(state) return { currentPage: currentPage, filter: selectors.getBrowserFilter(state, currentPage), @@ -22,30 +17,53 @@ function mapStateToProps(state){ } } -function mapDispatchToProps(dispatch){ - return { - init: (page) => { dispatch(actions.browserInit(page)) }, - release: (page) => { dispatch(actions.browserRelease(page)) }, - filterChange: (event) => { dispatch(actions.browserFilterChange(getCurrentPage(), event.currentTarget.value)) }, - nodeSelect: (id) => { dispatch(actions.browserNodeSelect(getCurrentPage(), id)) }, - nodeExpand: (id) => { dispatch(actions.browserNodeExpand(getCurrentPage(), id)) }, - nodeCollapse: (id) => { dispatch(actions.browserNodeCollapse(getCurrentPage(), id)) } - } -} - class Browser extends React.PureComponent { + constructor(props){ + super(props) + this.init = this.init.bind(this) + this.release = this.release.bind(this) + this.filterChange = this.filterChange.bind(this) + this.nodeSelect = this.nodeSelect.bind(this) + this.nodeExpand = this.nodeExpand.bind(this) + this.nodeCollapse = this.nodeCollapse.bind(this) + } + componentDidMount(){ - this.props.init(this.props.currentPage) + this.init(this.props.currentPage) } componentDidUpdate(previousProps){ if(this.props.currentPage !== previousProps.currentPage){ - this.props.release(previousProps.currentPage) - this.props.init(this.props.currentPage) + this.release(previousProps.currentPage) + this.init(this.props.currentPage) } } + init(page){ + this.props.dispatch(actions.browserInit(page)) + } + + release(page){ + this.props.dispatch(actions.browserRelease(page)) + } + + filterChange(event){ + this.props.dispatch(actions.browserFilterChange(this.props.currentPage, event.currentTarget.value)) + } + + nodeSelect(id){ + this.props.dispatch(actions.browserNodeSelect(this.props.currentPage, id)) + } + + nodeExpand(id){ + this.props.dispatch(actions.browserNodeExpand(this.props.currentPage, id)) + } + + nodeCollapse(id){ + this.props.dispatch(actions.browserNodeCollapse(this.props.currentPage, id)) + } + render() { logger.log(logger.DEBUG, 'Browser.render') @@ -53,13 +71,13 @@ class Browser extends React.PureComponent { <Loading loading={this.props.loading}> <BrowserFilter filter={this.props.filter} - filterChange={this.props.filterChange} + filterChange={this.filterChange} /> <BrowserNodes nodes={this.props.nodes} - nodeSelect={this.props.nodeSelect} - nodeExpand={this.props.nodeExpand} - nodeCollapse={this.props.nodeCollapse} + nodeSelect={this.nodeSelect} + nodeExpand={this.nodeExpand} + nodeCollapse={this.nodeCollapse} level={0} /> </Loading>) @@ -67,4 +85,4 @@ class Browser extends React.PureComponent { } -export default connect(mapStateToProps, mapDispatchToProps)(Browser) +export default connect(mapStateToProps, null)(Browser) diff --git a/openbis_ng_ui/src/components/browser/BrowserNode.jsx b/openbis_ng_ui/src/components/browser/BrowserNode.jsx new file mode 100644 index 0000000000000000000000000000000000000000..5d4939acefff299db1da43077fae347c2d0cd9af --- /dev/null +++ b/openbis_ng_ui/src/components/browser/BrowserNode.jsx @@ -0,0 +1,52 @@ +import React from 'react' +import ListItem from '@material-ui/core/ListItem' +import ListItemIcon from '@material-ui/core/ListItemIcon' +import ListItemText from '@material-ui/core/ListItemText' +import Collapse from '@material-ui/core/Collapse' +import ChevronRightIcon from '@material-ui/icons/ChevronRight' +import ExpandMoreIcon from '@material-ui/icons/ExpandMore' +import BrowserNodes from './BrowserNodes.jsx' +import logger from '../../common/logger.js' + +class BrowserNode extends React.Component { + + render() { + logger.log(logger.DEBUG, 'BrowserNode.render') + + const {node, level} = this.props + + return (<div> + <ListItem + button + selected={node.selected} + style={{paddingLeft: level * 20 + 'px'}}> + {this.renderIcon(node)} + {this.renderText(node)} + </ListItem> + {node.children && node.children.length > 0 && + <Collapse in={node.expanded} mountOnEnter={true} unmountOnExit={true}> + <BrowserNodes {...this.props} nodes={node.children} level={level + 1} /> + </Collapse>} + </div>) + } + + renderIcon(node){ + if(node.children && node.children.length > 0){ + if(node.expanded){ + return (<ListItemIcon><ExpandMoreIcon onClick={() => this.props.nodeCollapse(node.id)}/></ListItemIcon>) + }else{ + return (<ListItemIcon><ChevronRightIcon onClick={() => this.props.nodeExpand(node.id)}/></ListItemIcon>) + } + }else{ + return null + } + } + + renderText(node){ + logger.log(logger.DEBUG, 'BrowserNode.renderText "' + node.text + '"') + return <ListItemText primary={node.text} inset={true} onClick={() => this.props.nodeSelect(node.id)} /> + } + +} + +export default BrowserNode diff --git a/openbis_ng_ui/src/components/browser/BrowserNodes.jsx b/openbis_ng_ui/src/components/browser/BrowserNodes.jsx index 3f14f95d79e29aceb0fbaf07c5ffb2156dbc9969..de1a76e3646ef278fef13fb2cf89fed2e4fce4ac 100644 --- a/openbis_ng_ui/src/components/browser/BrowserNodes.jsx +++ b/openbis_ng_ui/src/components/browser/BrowserNodes.jsx @@ -1,12 +1,7 @@ import React from 'react' import {withStyles} from '@material-ui/core/styles' import List from '@material-ui/core/List' -import ListItem from '@material-ui/core/ListItem' -import ListItemIcon from '@material-ui/core/ListItemIcon' -import ListItemText from '@material-ui/core/ListItemText' -import Collapse from '@material-ui/core/Collapse' -import ChevronRightIcon from '@material-ui/icons/ChevronRight' -import ExpandMoreIcon from '@material-ui/icons/ExpandMore' +import BrowserNode from './BrowserNode.jsx' import logger from '../../common/logger.js' const styles = () => ({ @@ -26,42 +21,12 @@ class BrowserNodes extends React.Component { return (<List className={classes.browserList}> { this.props.nodes.map(node => { - return (<div key={node.id + '-parent'}> - <ListItem - button - key={node.id} - selected={node.selected} - style={{paddingLeft: this.props.level * 20 + 'px'}}> - {this.renderIcon(node)} - {this.renderText(node)} - </ListItem> - {node.children && node.children.length > 0 && - <Collapse key={node.id + '-collapse'} in={node.expanded} mountOnEnter={true} unmountOnExit={true}> - <BrowserNodes {...this.props} nodes={node.children} level={this.props.level + 1} /> - </Collapse>} - </div>) + return <BrowserNode {...this.props} key={node.id} node={node} level={this.props.level} /> }) } </List>) } - renderIcon(node){ - if(node.children && node.children.length > 0){ - if(node.expanded){ - return (<ListItemIcon><ExpandMoreIcon onClick={() => this.props.nodeCollapse(node.id)}/></ListItemIcon>) - }else{ - return (<ListItemIcon><ChevronRightIcon onClick={() => this.props.nodeExpand(node.id)}/></ListItemIcon>) - } - }else{ - return null - } - } - - renderText(node){ - logger.log(logger.DEBUG, 'BrowserNode.renderText "' + node.text + '"') - return <ListItemText primary={node.text} inset={true} onClick={() => this.props.nodeSelect(node.id)} /> - } - } export default withStyles(styles)(BrowserNodes) diff --git a/openbis_ng_ui/src/components/content/Content.jsx b/openbis_ng_ui/src/components/content/Content.jsx index a42a21fd6f5bdc99f28111dd7b29bfc8f24d99d7..c4c6979d6dff76f86d2d26874765f048bd309ccb 100644 --- a/openbis_ng_ui/src/components/content/Content.jsx +++ b/openbis_ng_ui/src/components/content/Content.jsx @@ -2,7 +2,6 @@ import React from 'react' import ContentTabs from './ContentTabs.jsx' import {connect} from 'react-redux' import logger from '../../common/logger.js' -import store from '../../store/store.js' import * as objectType from '../../store/consts/objectType.js' import * as selectors from '../../store/selectors/selectors.js' import * as actions from '../../store/actions/actions.js' @@ -23,26 +22,30 @@ const objectTypeToComponent = { [objectType.GROUP]: Group, } -function getCurrentPage(){ - return selectors.getCurrentPage(store.getState()) -} - function mapStateToProps(state){ - let currentPage = getCurrentPage() + let currentPage = selectors.getCurrentPage(state) return { + currentPage: currentPage, openObjects: selectors.getOpenObjects(state, currentPage), selectedObject: selectors.getSelectedObject(state, currentPage) } } -function mapDispatchToProps(dispatch){ - return { - objectSelect: (type, id) => { dispatch(actions.objectOpen(getCurrentPage(), type, id)) }, - objectClose: (type, id) => { dispatch(actions.objectClose(getCurrentPage(), type, id)) } +class Content extends React.Component { + + constructor(props){ + super(props) + this.objectSelect = this.objectSelect.bind(this) + this.objectClose = this.objectClose.bind(this) } -} -class Content extends React.Component { + objectSelect(type, id){ + this.props.dispatch(actions.objectOpen(this.props.currentPage, type, id)) + } + + objectClose(type, id){ + this.props.dispatch(actions.objectClose(this.props.currentPage, type, id)) + } render() { logger.log(logger.DEBUG, 'Content.render') @@ -54,10 +57,10 @@ class Content extends React.Component { <ContentTabs objects={this.props.openObjects} selectedObject={this.props.selectedObject} - objectSelect={this.props.objectSelect} - objectClose={this.props.objectClose} /> + objectSelect={this.objectSelect} + objectClose={this.objectClose} /> {ObjectContent && - <ObjectContent /> + <ObjectContent objectId={this.props.selectedObject.id} /> } </div> ) @@ -65,4 +68,4 @@ class Content extends React.Component { } -export default connect(mapStateToProps, mapDispatchToProps)(Content) +export default connect(mapStateToProps, null)(Content) diff --git a/openbis_ng_ui/src/components/content/objectType/ObjectType.jsx b/openbis_ng_ui/src/components/content/objectType/ObjectType.jsx index 43cf63499ebef7d09e49e7ad87eb4c39656910ff..24c2f85bd445e5b59ad80594bb3460a4790db4a3 100644 --- a/openbis_ng_ui/src/components/content/objectType/ObjectType.jsx +++ b/openbis_ng_ui/src/components/content/objectType/ObjectType.jsx @@ -1,11 +1,60 @@ +import _ from 'lodash' import React from 'react' +import TextField from '@material-ui/core/TextField' +import Checkbox from '@material-ui/core/Checkbox' +import Button from '@material-ui/core/Button' import logger from '../../../common/logger.js' +import openbis from '../../../services/openbis.js' + class ObjectType extends React.Component { + constructor(props){ + super(props) + this.state = { + description: '', + listable: false + } + } + + componentDidMount(){ + } + + handleChange(name){ + return event => { + let value = _.has(event.target, 'checked') ? event.target.checked : event.target.value + this.setState({ [name]: value }) + } + } + render() { logger.log(logger.DEBUG, 'ObjectType.render') - return <div>ObjectType</div> + return ( + <div> + {this.props.objectId} + <form> + <div> + <TextField + label='Description' + value={this.state.description} + onChange={this.handleChange('description')} + /> + </div> + <div> + <Checkbox + checked={this.state.listable} + value='listable' + onChange={this.handleChange('listable')} + /> + </div> + <div> + <Button variant='contained' color='primary'> + Save + </Button> + </div> + </form> + </div> + ) } } diff --git a/openbis_ng_ui/srcTest/store/sagas/fixture.js b/openbis_ng_ui/srcTest/common/fixture.js similarity index 100% rename from openbis_ng_ui/srcTest/store/sagas/fixture.js rename to openbis_ng_ui/srcTest/common/fixture.js diff --git a/openbis_ng_ui/srcTest/components/browser/browser.test.js b/openbis_ng_ui/srcTest/components/browser/browser.test.js new file mode 100644 index 0000000000000000000000000000000000000000..25da2f03b625e9dc95a5efaccadff95ccf3fe456 --- /dev/null +++ b/openbis_ng_ui/srcTest/components/browser/browser.test.js @@ -0,0 +1,96 @@ +import React from 'react' +import { mount } from 'enzyme' +import Browser from '../../../src/components/browser/Browser.jsx' +import openbis from '../../../src/services/openbis.js' +import * as actions from '../../../src/store/actions/actions.js' +import * as pages from '../../../src/store/consts/pages.js' +import { createStore } from '../../../src/store/store.js' +import * as fixture from '../../common/fixture.js' + +jest.mock('../../../src/services/openbis.js') + +let store = null + +beforeEach(() => { + jest.resetAllMocks() + store = createStore() +}) + +describe('browser', () => { + + test('test', () => { + openbis.getUsers.mockReturnValue({ + objects: [ fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO ] + }) + + openbis.getGroups.mockReturnValue({ + objects: [ fixture.TEST_GROUP_DTO, fixture.ANOTHER_GROUP_DTO, fixture.ALL_USERS_GROUP_DTO ] + }) + + store.dispatch(actions.currentPageChange(pages.USERS)) + store.dispatch(actions.browserInit(pages.USERS)) + + let wrapper = mount(<Browser store={store}/>) + expectFilter(wrapper, '') + expectNodes(wrapper, [ + { level: 0, text: 'Users'}, + { level: 0, text: 'Groups'} + ]) + + simulateNodeIconClick(wrapper, 'users') + + expectFilter(wrapper, '') + expectNodes(wrapper, [ + { level: 0, text: 'Users'}, + { level: 1, text: fixture.ANOTHER_USER_DTO.userId}, + { level: 1, text: fixture.TEST_USER_DTO.userId}, + { level: 0, text: 'Groups'} + ]) + + simulateFilterChange(wrapper, fixture.ANOTHER_GROUP_DTO.code.toUpperCase()) + wrapper.update() + + expectFilter(wrapper, fixture.ANOTHER_GROUP_DTO.code.toUpperCase()) + expectNodes(wrapper, [ + { level: 0, text: 'Users'}, + { level: 1, text: fixture.ANOTHER_USER_DTO.userId}, + { level: 2, text: fixture.ANOTHER_GROUP_DTO.code}, + { level: 0, text: 'Groups'}, + { level: 1, text: fixture.ANOTHER_GROUP_DTO.code} + ]) + }) + +}) + +function simulateNodeIconClick(wrapper, id){ + wrapper.findWhere(node => { + return node.name() === 'BrowserNode' && node.prop('node').id === id + }).find('ListItemIcon').first().simulate('click') +} + +function simulateFilterChange(wrapper, filter){ + let input = wrapper.find('BrowserFilter').find('input') + input.instance().value = filter + input.simulate('change') +} + +function expectFilter(wrapper, expectedFilter){ + const actualFilter = wrapper.find('BrowserFilter').map(node => { + return node.prop('filter') + })[0] + expect(actualFilter).toEqual(expectedFilter) +} + +function expectNodes(wrapper, expectedNodes){ + const actualNodes = wrapper.find('BrowserNode').map(node => { + const text = node.prop('node').text + const selected = node.prop('node').selected + const level = node.prop('level') + return { + text, + level, + selected + } + }) + expect(actualNodes).toMatchObject(expectedNodes) +} diff --git a/openbis_ng_ui/srcTest/setupTests.js b/openbis_ng_ui/srcTest/setupTests.js new file mode 100644 index 0000000000000000000000000000000000000000..3d6cd1d53a1142ea92e6ccec2b7a953c682e3f10 --- /dev/null +++ b/openbis_ng_ui/srcTest/setupTests.js @@ -0,0 +1,4 @@ +import { configure } from 'enzyme' +import Adapter from 'enzyme-adapter-react-16' + +configure({ adapter: new Adapter() }) diff --git a/openbis_ng_ui/srcTest/store/sagas/app.test.js b/openbis_ng_ui/srcTest/store/sagas/app.test.js index f65389c2a787965bf5b7b91b29a9d1a2e726824f..331ffcf079022eae9ac4131c6a493328155a8432 100644 --- a/openbis_ng_ui/srcTest/store/sagas/app.test.js +++ b/openbis_ng_ui/srcTest/store/sagas/app.test.js @@ -3,7 +3,7 @@ import * as actions from '../../../src/store/actions/actions.js' import * as selectors from '../../../src/store/selectors/selectors.js' import * as pages from '../../../src/store/consts/pages.js' import { createStore } from '../../../src/store/store.js' -import * as fixture from './fixture.js' +import * as fixture from '../../common/fixture.js' jest.mock('../../../src/services/openbis.js') diff --git a/openbis_ng_ui/srcTest/store/sagas/browser.test.js b/openbis_ng_ui/srcTest/store/sagas/browser.test.js index 924d97c12cd802a54151d8550b83521a12cc8793..6af2867a30fd2b10413c9420d6e0fdf8448d04f3 100644 --- a/openbis_ng_ui/srcTest/store/sagas/browser.test.js +++ b/openbis_ng_ui/srcTest/store/sagas/browser.test.js @@ -6,7 +6,7 @@ import * as pages from '../../../src/store/consts/pages.js' import * as objectType from '../../../src/store/consts/objectType.js' import * as common from '../../../src/store/common/browser.js' import { createStore } from '../../../src/store/store.js' -import * as fixture from './fixture.js' +import * as fixture from '../../common/fixture.js' jest.mock('../../../src/services/openbis.js') diff --git a/openbis_ng_ui/srcTest/store/sagas/page.test.js b/openbis_ng_ui/srcTest/store/sagas/page.test.js index bb59b99375e3f026323fb22c62991c67e7d88077..162488835d0022d7c56f480656d0d5d6cbf4a4d5 100644 --- a/openbis_ng_ui/srcTest/store/sagas/page.test.js +++ b/openbis_ng_ui/srcTest/store/sagas/page.test.js @@ -3,7 +3,7 @@ import * as selectors from '../../../src/store/selectors/selectors.js' import * as objectType from '../../../src/store/consts/objectType.js' import * as pages from '../../../src/store/consts/pages.js' import { createStore } from '../../../src/store/store.js' -import * as fixture from './fixture.js' +import * as fixture from '../../common/fixture.js' jest.mock('../../../src/services/openbis.js')