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')