diff --git a/openbis_ng_ui/srcTest/js/components/AppComponentLogin.test.js b/openbis_ng_ui/srcTest/js/components/AppComponentLogin.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..0d10d5cb63298a7c5d891d9c6e83ebc95a7dcc0c
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/AppComponentLogin.test.js
@@ -0,0 +1,67 @@
+import AppComponentTest from '@srcTest/js/components/AppComponentTest.js'
+
+let common = null
+
+beforeEach(() => {
+  common = new AppComponentTest()
+  common.beforeEach()
+})
+
+describe(AppComponentTest.SUITE, () => {
+  test('login', testLogin)
+})
+
+async function testLogin() {
+  const app = await common.mount()
+
+  app.expectJSON({
+    login: {
+      user: {
+        value: null,
+        enabled: true
+      },
+      password: {
+        value: null,
+        enabled: true
+      },
+      button: {
+        enabled: true
+      }
+    },
+    menu: null,
+    types: null,
+    users: null
+  })
+
+  await common.login(app)
+
+  app.expectJSON({
+    login: null,
+    menu: {
+      tabs: [
+        {
+          label: 'Types',
+          selected: true
+        }
+      ]
+    },
+    types: {
+      browser: {
+        filter: {
+          value: null
+        },
+        nodes: [
+          { level: 0, text: 'Object Types' },
+          { level: 0, text: 'Collection Types' },
+          { level: 0, text: 'Data Set Types' },
+          { level: 0, text: 'Material Types' },
+          { level: 0, text: 'Vocabulary Types' }
+        ]
+      },
+      content: {
+        tabs: []
+      }
+    },
+    users: null
+  })
+}
diff --git a/openbis_ng_ui/srcTest/js/components/AppComponent.test.js b/openbis_ng_ui/srcTest/js/components/AppComponentOpenCloseTypes.test.js
similarity index 58%
rename from openbis_ng_ui/srcTest/js/components/AppComponent.test.js
rename to openbis_ng_ui/srcTest/js/components/AppComponentOpenCloseTypes.test.js
index 91d7772cafd252c4937b8533009e189dcebc2597..91ad207e4ade20a1485cfc2d9349f71a03ab048a 100644
--- a/openbis_ng_ui/srcTest/js/components/AppComponent.test.js
+++ b/openbis_ng_ui/srcTest/js/components/AppComponentOpenCloseTypes.test.js
@@ -1,89 +1,18 @@
-import React from 'react'
-import ComponentTest from '@srcTest/js/components/common/ComponentTest.js'
-import AppWrapper from '@srcTest/js/components/wrapper/AppWrapper.js'
-import App from '@src/js/components/App.jsx'
+import AppComponentTest from '@srcTest/js/components/AppComponentTest.js'
 import openbis from '@srcTest/js/services/openbis.js'
 import fixture from '@srcTest/js/common/fixture.js'
 
 let common = null
 
 beforeEach(() => {
-  common = new ComponentTest(
-    () => <App />,
-    wrapper => new AppWrapper(wrapper)
-  )
+  common = new AppComponentTest()
   common.beforeEach()
-
-  openbis.login.mockReturnValue(Promise.resolve('testSession'))
-  openbis.mockSearchSampleTypes([])
-  openbis.mockSearchExperimentTypes([])
-  openbis.mockSearchDataSetTypes([])
-  openbis.mockSearchMaterialTypes([])
-  openbis.mockSearchVocabularies([])
-  openbis.mockSearchPersons([])
-  openbis.mockSearchGroups([])
 })
 
-describe('app', () => {
-  test('login', testLogin)
+describe(AppComponentTest.SUITE, () => {
   test('open/close types', testOpenCloseTypes)
 })
 
-async function testLogin() {
-  const app = await common.mount()
-
-  app.expectJSON({
-    login: {
-      user: {
-        value: null,
-        enabled: true
-      },
-      password: {
-        value: null,
-        enabled: true
-      },
-      button: {
-        enabled: true
-      }
-    },
-    menu: null,
-    types: null,
-    users: null
-  })
-
-  await login(app)
-
-  app.expectJSON({
-    login: null,
-    menu: {
-      tabs: [
-        {
-          label: 'Types',
-          selected: true
-        }
-      ]
-    },
-    types: {
-      browser: {
-        filter: {
-          value: null
-        },
-        nodes: [
-          { level: 0, text: 'Object Types' },
-          { level: 0, text: 'Collection Types' },
-          { level: 0, text: 'Data Set Types' },
-          { level: 0, text: 'Material Types' },
-          { level: 0, text: 'Vocabulary Types' }
-        ]
-      },
-      content: {
-        tabs: []
-      }
-    },
-    users: null
-  })
-}
-
 async function testOpenCloseTypes() {
   openbis.mockSearchSampleTypes([
     fixture.TEST_SAMPLE_TYPE_DTO,
@@ -92,7 +21,7 @@ async function testOpenCloseTypes() {
 
   const app = await common.mount()
 
-  await login(app)
+  await common.login(app)
 
   app.getTypes().getBrowser().getNodes()[0].getIcon().click()
   await app.update()
@@ -172,10 +101,3 @@ async function testOpenCloseTypes() {
     }
   })
 }
-
-async function login(app) {
-  app.getLogin().getUser().change('testUser')
-  app.getLogin().getPassword().change('testPassword')
-  app.getLogin().getButton().click()
-  await app.update()
-}
diff --git a/openbis_ng_ui/srcTest/js/components/AppComponentTest.js b/openbis_ng_ui/srcTest/js/components/AppComponentTest.js
new file mode 100644
index 0000000000000000000000000000000000000000..ea692cf30a149542938cfa9e57ceeb82d2b99536
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/AppComponentTest.js
@@ -0,0 +1,36 @@
+import React from 'react'
+import ComponentTest from '@srcTest/js/components/common/ComponentTest.js'
+import AppWrapper from '@srcTest/js/components/wrapper/AppWrapper.js'
+import App from '@src/js/components/App.jsx'
+import openbis from '@srcTest/js/services/openbis.js'
+
+export default class AppComponentTest extends ComponentTest {
+  static SUITE = 'AppComponent'
+
+  constructor() {
+    super(
+      () => <App />,
+      wrapper => new AppWrapper(wrapper)
+    )
+  }
+
+  async beforeEach() {
+    super.beforeEach()
+
+    openbis.login.mockReturnValue(Promise.resolve('testSession'))
+    openbis.mockSearchSampleTypes([])
+    openbis.mockSearchExperimentTypes([])
+    openbis.mockSearchDataSetTypes([])
+    openbis.mockSearchMaterialTypes([])
+    openbis.mockSearchVocabularies([])
+    openbis.mockSearchPersons([])
+    openbis.mockSearchGroups([])
+  }
+
+  async login(app) {
+    app.getLogin().getUser().change('testUser')
+    app.getLogin().getPassword().change('testPassword')
+    app.getLogin().getButton().click()
+    await app.update()
+  }
+}
diff --git a/openbis_ng_ui/srcTest/js/components/login/LoginComponent.test.js b/openbis_ng_ui/srcTest/js/components/login/LoginComponent.test.js
deleted file mode 100644
index 4fd904e0fe8e6542d6ec2d3eaa2dab395d560461..0000000000000000000000000000000000000000
--- a/openbis_ng_ui/srcTest/js/components/login/LoginComponent.test.js
+++ /dev/null
@@ -1,95 +0,0 @@
-import React from 'react'
-import ComponentTest from '@srcTest/js/components/common/ComponentTest.js'
-import Login from '@src/js/components/login/Login.jsx'
-import LoginWrapper from '@srcTest/js/components/login/wrapper/LoginWrapper.js'
-
-let common = null
-
-beforeEach(() => {
-  common = new ComponentTest(
-    () => <Login />,
-    wrapper => new LoginWrapper(wrapper)
-  )
-  common.beforeEach()
-})
-
-describe('login', () => {
-  test('test', async () => {
-    const login = await common.mount()
-
-    login.expectJSON({
-      user: {
-        value: null,
-        focused: true,
-        error: null
-      },
-      password: {
-        value: null,
-        focused: false,
-        error: null
-      },
-      button: {
-        enabled: true
-      }
-    })
-
-    login.getButton().click()
-    await login.update()
-
-    login.expectJSON({
-      user: {
-        value: null,
-        focused: true,
-        error: 'User cannot be empty'
-      },
-      password: {
-        value: null,
-        focused: false,
-        error: 'Password cannot be empty'
-      },
-      button: {
-        enabled: true
-      }
-    })
-
-    login.getUser().change('testUser')
-    login.getButton().click()
-    await login.update()
-
-    login.expectJSON({
-      user: {
-        value: 'testUser',
-        focused: false,
-        error: null
-      },
-      password: {
-        value: null,
-        focused: true,
-        error: 'Password cannot be empty'
-      },
-      button: {
-        enabled: true
-      }
-    })
-
-    login.getPassword().change('testPassword')
-    login.getButton().click()
-    await login.update()
-
-    login.expectJSON({
-      user: {
-        value: 'testUser',
-        focused: false,
-        error: null
-      },
-      password: {
-        value: 'testPassword',
-        focused: true,
-        error: null
-      },
-      button: {
-        enabled: true
-      }
-    })
-  })
-})
diff --git a/openbis_ng_ui/srcTest/js/components/login/LoginComponentLogin.test.js b/openbis_ng_ui/srcTest/js/components/login/LoginComponentLogin.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..900bdee2ab153222ba8b54f712fc857f36b037bb
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/login/LoginComponentLogin.test.js
@@ -0,0 +1,91 @@
+import LoginComponentTest from '@srcTest/js/components/login/LoginComponentTest.js'
+
+let common = null
+
+beforeEach(() => {
+  common = new LoginComponentTest()
+  common.beforeEach()
+})
+
+describe(LoginComponentTest.SUITE, () => {
+  test('login', testLogin)
+})
+
+async function testLogin() {
+  const login = await common.mount()
+
+  login.expectJSON({
+    user: {
+      value: null,
+      focused: true,
+      error: null
+    },
+    password: {
+      value: null,
+      focused: false,
+      error: null
+    },
+    button: {
+      enabled: true
+    }
+  })
+
+  login.getButton().click()
+  await login.update()
+
+  login.expectJSON({
+    user: {
+      value: null,
+      focused: true,
+      error: 'User cannot be empty'
+    },
+    password: {
+      value: null,
+      focused: false,
+      error: 'Password cannot be empty'
+    },
+    button: {
+      enabled: true
+    }
+  })
+
+  login.getUser().change('testUser')
+  login.getButton().click()
+  await login.update()
+
+  login.expectJSON({
+    user: {
+      value: 'testUser',
+      focused: false,
+      error: null
+    },
+    password: {
+      value: null,
+      focused: true,
+      error: 'Password cannot be empty'
+    },
+    button: {
+      enabled: true
+    }
+  })
+
+  login.getPassword().change('testPassword')
+  login.getButton().click()
+  await login.update()
+
+  login.expectJSON({
+    user: {
+      value: 'testUser',
+      focused: false,
+      error: null
+    },
+    password: {
+      value: 'testPassword',
+      focused: true,
+      error: null
+    },
+    button: {
+      enabled: true
+    }
+  })
+}
diff --git a/openbis_ng_ui/srcTest/js/components/login/LoginComponentTest.js b/openbis_ng_ui/srcTest/js/components/login/LoginComponentTest.js
new file mode 100644
index 0000000000000000000000000000000000000000..930ffef13a5fceacd3e4f66c0be39633499630ea
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/login/LoginComponentTest.js
@@ -0,0 +1,15 @@
+import React from 'react'
+import ComponentTest from '@srcTest/js/components/common/ComponentTest.js'
+import Login from '@src/js/components/login/Login.jsx'
+import LoginWrapper from '@srcTest/js/components/login/wrapper/LoginWrapper.js'
+
+export default class LoginComponentTest extends ComponentTest {
+  static SUITE = 'LoginComponent'
+
+  constructor() {
+    super(
+      () => <Login />,
+      wrapper => new LoginWrapper(wrapper)
+    )
+  }
+}
diff --git a/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserComponent.test.js b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserComponent.test.js
deleted file mode 100644
index ab13b2a73d81b17292ec1c3077d1f719ed49fffb..0000000000000000000000000000000000000000
--- a/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserComponent.test.js
+++ /dev/null
@@ -1,235 +0,0 @@
-import React from 'react'
-import ComponentTest from '@srcTest/js/components/common/ComponentTest.js'
-import BrowserWrapper from '@srcTest/js/components/common/browser/wrapper/BrowserWrapper.js'
-import TypeBrowser from '@src/js/components/types/browser/TypeBrowser.jsx'
-import openbis from '@srcTest/js/services/openbis.js'
-import fixture from '@srcTest/js/common/fixture.js'
-
-let common = null
-
-beforeEach(() => {
-  common = new ComponentTest(
-    () => <TypeBrowser />,
-    wrapper => new BrowserWrapper(wrapper)
-  )
-  common.beforeEach()
-
-  openbis.mockSearchSampleTypes([
-    fixture.TEST_SAMPLE_TYPE_DTO,
-    fixture.ANOTHER_SAMPLE_TYPE_DTO
-  ])
-
-  openbis.mockSearchExperimentTypes([fixture.TEST_EXPERIMENT_TYPE_DTO])
-  openbis.mockSearchDataSetTypes([fixture.TEST_DATA_SET_TYPE_DTO])
-
-  openbis.mockSearchMaterialTypes([
-    fixture.TEST_MATERIAL_TYPE_DTO,
-    fixture.ANOTHER_MATERIAL_TYPE_DTO
-  ])
-
-  openbis.mockSearchVocabularies([
-    fixture.TEST_VOCABULARY_DTO,
-    fixture.ANOTHER_VOCABULARY_DTO
-  ])
-})
-
-describe('type browser', () => {
-  test('load', testLoad)
-  test('open/close', testOpenClose)
-  test('filter', testFilter)
-  test('select entity kind', testSelectEntityKind)
-  test('select entity type', testSelectEntityType)
-})
-
-async function testLoad() {
-  const browser = await common.mount()
-
-  browser.expectJSON({
-    filter: {
-      value: null
-    },
-    nodes: [
-      { level: 0, text: 'Object Types' },
-      { level: 0, text: 'Collection Types' },
-      { level: 0, text: 'Data Set Types' },
-      { level: 0, text: 'Material Types' },
-      { level: 0, text: 'Vocabulary Types' }
-    ]
-  })
-}
-
-async function testOpenClose() {
-  const browser = await common.mount()
-
-  browser.getNodes()[0].getIcon().click()
-  await browser.update()
-
-  browser.expectJSON({
-    filter: {
-      value: null
-    },
-    nodes: [
-      { level: 0, text: 'Object Types' },
-      { level: 1, text: fixture.ANOTHER_SAMPLE_TYPE_DTO.code },
-      { level: 1, text: fixture.TEST_SAMPLE_TYPE_DTO.code },
-      { level: 0, text: 'Collection Types' },
-      { level: 0, text: 'Data Set Types' },
-      { level: 0, text: 'Material Types' },
-      { level: 0, text: 'Vocabulary Types' }
-    ]
-  })
-
-  browser.getNodes()[0].getIcon().click()
-  await browser.update()
-
-  browser.expectJSON({
-    filter: {
-      value: null
-    },
-    nodes: [
-      { level: 0, text: 'Object Types' },
-      { level: 0, text: 'Collection Types' },
-      { level: 0, text: 'Data Set Types' },
-      { level: 0, text: 'Material Types' },
-      { level: 0, text: 'Vocabulary Types' }
-    ]
-  })
-}
-
-async function testFilter() {
-  const browser = await common.mount()
-
-  browser.getFilter().change('ANOTHER')
-  await browser.update()
-
-  browser.expectJSON({
-    filter: {
-      value: 'ANOTHER'
-    },
-    nodes: [
-      { level: 0, text: 'Object Types' },
-      { level: 1, text: fixture.ANOTHER_SAMPLE_TYPE_DTO.code },
-      { level: 0, text: 'Material Types' },
-      { level: 1, text: fixture.ANOTHER_MATERIAL_TYPE_DTO.code },
-      { level: 0, text: 'Vocabulary Types' },
-      { level: 1, text: fixture.ANOTHER_VOCABULARY_DTO.code }
-    ]
-  })
-
-  browser.getFilter().getClearIcon().click()
-  await browser.update()
-
-  browser.expectJSON({
-    filter: {
-      value: null
-    },
-    nodes: [
-      { level: 0, text: 'Object Types' },
-      { level: 1, text: fixture.ANOTHER_SAMPLE_TYPE_DTO.code },
-      { level: 1, text: fixture.TEST_SAMPLE_TYPE_DTO.code },
-      { level: 0, text: 'Collection Types' },
-      { level: 1, text: fixture.TEST_EXPERIMENT_TYPE_DTO.code },
-      { level: 0, text: 'Data Set Types' },
-      { level: 1, text: fixture.TEST_DATA_SET_TYPE_DTO.code },
-      { level: 0, text: 'Material Types' },
-      { level: 1, text: fixture.ANOTHER_MATERIAL_TYPE_DTO.code },
-      { level: 1, text: fixture.TEST_MATERIAL_TYPE_DTO.code },
-      { level: 0, text: 'Vocabulary Types' },
-      { level: 1, text: fixture.ANOTHER_VOCABULARY_DTO.code },
-      { level: 1, text: fixture.TEST_VOCABULARY_DTO.code }
-    ]
-  })
-}
-
-async function testSelectEntityKind() {
-  const browser = await common.mount()
-
-  browser.expectJSON({
-    nodes: [
-      { level: 0, text: 'Object Types', selected: false },
-      { level: 0, text: 'Collection Types', selected: false },
-      { level: 0, text: 'Data Set Types', selected: false },
-      { level: 0, text: 'Material Types', selected: false },
-      { level: 0, text: 'Vocabulary Types', selected: false }
-    ],
-    buttons: {
-      add: {
-        enabled: false
-      },
-      remove: {
-        enabled: false
-      }
-    }
-  })
-
-  browser.getNodes()[0].click()
-  await browser.update()
-
-  browser.expectJSON({
-    nodes: [
-      { level: 0, text: 'Object Types', selected: true },
-      { level: 0, text: 'Collection Types', selected: false },
-      { level: 0, text: 'Data Set Types', selected: false },
-      { level: 0, text: 'Material Types', selected: false },
-      { level: 0, text: 'Vocabulary Types', selected: false }
-    ],
-    buttons: {
-      add: {
-        enabled: true
-      },
-      remove: {
-        enabled: false
-      }
-    }
-  })
-}
-
-async function testSelectEntityType() {
-  const browser = await common.mount()
-
-  browser.getNodes()[0].getIcon().click()
-  await browser.update()
-
-  browser.expectJSON({
-    nodes: [
-      { level: 0, text: 'Object Types', selected: false },
-      { level: 1, text: fixture.ANOTHER_SAMPLE_TYPE_DTO.code, selected: false },
-      { level: 1, text: fixture.TEST_SAMPLE_TYPE_DTO.code, selected: false },
-      { level: 0, text: 'Collection Types', selected: false },
-      { level: 0, text: 'Data Set Types', selected: false },
-      { level: 0, text: 'Material Types', selected: false },
-      { level: 0, text: 'Vocabulary Types', selected: false }
-    ],
-    buttons: {
-      add: {
-        enabled: false
-      },
-      remove: {
-        enabled: false
-      }
-    }
-  })
-
-  browser.getNodes()[1].click()
-  await browser.update()
-
-  browser.expectJSON({
-    nodes: [
-      { level: 0, text: 'Object Types', selected: false },
-      { level: 1, text: fixture.ANOTHER_SAMPLE_TYPE_DTO.code, selected: true },
-      { level: 1, text: fixture.TEST_SAMPLE_TYPE_DTO.code, selected: false },
-      { level: 0, text: 'Collection Types', selected: false },
-      { level: 0, text: 'Data Set Types', selected: false },
-      { level: 0, text: 'Material Types', selected: false },
-      { level: 0, text: 'Vocabulary Types', selected: false }
-    ],
-    buttons: {
-      add: {
-        enabled: false
-      },
-      remove: {
-        enabled: true
-      }
-    }
-  })
-}
diff --git a/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserComponentFilter.test.js b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserComponentFilter.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..d492b88bc3b51f5f654bc4665ccb748b44d273bc
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserComponentFilter.test.js
@@ -0,0 +1,58 @@
+import TypeBrowserComponentTest from '@srcTest/js/components/types/browser/TypeBrowserComponentTest.js'
+import fixture from '@srcTest/js/common/fixture.js'
+
+let common = null
+
+beforeEach(() => {
+  common = new TypeBrowserComponentTest()
+  common.beforeEach()
+})
+
+describe(TypeBrowserComponentTest.SUITE, () => {
+  test('filter', testFilter)
+})
+
+async function testFilter() {
+  const browser = await common.mount()
+
+  browser.getFilter().change('ANOTHER')
+  await browser.update()
+
+  browser.expectJSON({
+    filter: {
+      value: 'ANOTHER'
+    },
+    nodes: [
+      { level: 0, text: 'Object Types' },
+      { level: 1, text: fixture.ANOTHER_SAMPLE_TYPE_DTO.code },
+      { level: 0, text: 'Material Types' },
+      { level: 1, text: fixture.ANOTHER_MATERIAL_TYPE_DTO.code },
+      { level: 0, text: 'Vocabulary Types' },
+      { level: 1, text: fixture.ANOTHER_VOCABULARY_DTO.code }
+    ]
+  })
+
+  browser.getFilter().getClearIcon().click()
+  await browser.update()
+
+  browser.expectJSON({
+    filter: {
+      value: null
+    },
+    nodes: [
+      { level: 0, text: 'Object Types' },
+      { level: 1, text: fixture.ANOTHER_SAMPLE_TYPE_DTO.code },
+      { level: 1, text: fixture.TEST_SAMPLE_TYPE_DTO.code },
+      { level: 0, text: 'Collection Types' },
+      { level: 1, text: fixture.TEST_EXPERIMENT_TYPE_DTO.code },
+      { level: 0, text: 'Data Set Types' },
+      { level: 1, text: fixture.TEST_DATA_SET_TYPE_DTO.code },
+      { level: 0, text: 'Material Types' },
+      { level: 1, text: fixture.ANOTHER_MATERIAL_TYPE_DTO.code },
+      { level: 1, text: fixture.TEST_MATERIAL_TYPE_DTO.code },
+      { level: 0, text: 'Vocabulary Types' },
+      { level: 1, text: fixture.ANOTHER_VOCABULARY_DTO.code },
+      { level: 1, text: fixture.TEST_VOCABULARY_DTO.code }
+    ]
+  })
+}
diff --git a/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserComponentLoad.test.js b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserComponentLoad.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..6516511132d2148d54a38d9fc6ccd3d849f201d6
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserComponentLoad.test.js
@@ -0,0 +1,29 @@
+import TypeBrowserComponentTest from '@srcTest/js/components/types/browser/TypeBrowserComponentTest.js'
+
+let common = null
+
+beforeEach(() => {
+  common = new TypeBrowserComponentTest()
+  common.beforeEach()
+})
+
+describe(TypeBrowserComponentTest.SUITE, () => {
+  test('load', testLoad)
+})
+
+async function testLoad() {
+  const browser = await common.mount()
+
+  browser.expectJSON({
+    filter: {
+      value: null
+    },
+    nodes: [
+      { level: 0, text: 'Object Types' },
+      { level: 0, text: 'Collection Types' },
+      { level: 0, text: 'Data Set Types' },
+      { level: 0, text: 'Material Types' },
+      { level: 0, text: 'Vocabulary Types' }
+    ]
+  })
+}
diff --git a/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserComponentOpenClose.test.js b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserComponentOpenClose.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..d389cd3e8c3f3611902c868a8419dd766afc4349
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserComponentOpenClose.test.js
@@ -0,0 +1,51 @@
+import TypeBrowserComponentTest from '@srcTest/js/components/types/browser/TypeBrowserComponentTest.js'
+import fixture from '@srcTest/js/common/fixture.js'
+
+let common = null
+
+beforeEach(() => {
+  common = new TypeBrowserComponentTest()
+  common.beforeEach()
+})
+
+describe(TypeBrowserComponentTest.SUITE, () => {
+  test('open/close', testOpenClose)
+})
+
+async function testOpenClose() {
+  const browser = await common.mount()
+
+  browser.getNodes()[0].getIcon().click()
+  await browser.update()
+
+  browser.expectJSON({
+    filter: {
+      value: null
+    },
+    nodes: [
+      { level: 0, text: 'Object Types' },
+      { level: 1, text: fixture.ANOTHER_SAMPLE_TYPE_DTO.code },
+      { level: 1, text: fixture.TEST_SAMPLE_TYPE_DTO.code },
+      { level: 0, text: 'Collection Types' },
+      { level: 0, text: 'Data Set Types' },
+      { level: 0, text: 'Material Types' },
+      { level: 0, text: 'Vocabulary Types' }
+    ]
+  })
+
+  browser.getNodes()[0].getIcon().click()
+  await browser.update()
+
+  browser.expectJSON({
+    filter: {
+      value: null
+    },
+    nodes: [
+      { level: 0, text: 'Object Types' },
+      { level: 0, text: 'Collection Types' },
+      { level: 0, text: 'Data Set Types' },
+      { level: 0, text: 'Material Types' },
+      { level: 0, text: 'Vocabulary Types' }
+    ]
+  })
+}
diff --git a/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserComponentSelect.test.js b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserComponentSelect.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..1e05a49bea1237e227dcdcac83fac4d421cd1e36
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserComponentSelect.test.js
@@ -0,0 +1,107 @@
+import TypeBrowserComponentTest from '@srcTest/js/components/types/browser/TypeBrowserComponentTest.js'
+import fixture from '@srcTest/js/common/fixture.js'
+
+let common = null
+
+beforeEach(() => {
+  common = new TypeBrowserComponentTest()
+  common.beforeEach()
+})
+
+describe(TypeBrowserComponentTest.SUITE, () => {
+  test('select entity kind', testSelectEntityKind)
+  test('select entity type', testSelectEntityType)
+})
+
+async function testSelectEntityKind() {
+  const browser = await common.mount()
+
+  browser.expectJSON({
+    nodes: [
+      { level: 0, text: 'Object Types', selected: false },
+      { level: 0, text: 'Collection Types', selected: false },
+      { level: 0, text: 'Data Set Types', selected: false },
+      { level: 0, text: 'Material Types', selected: false },
+      { level: 0, text: 'Vocabulary Types', selected: false }
+    ],
+    buttons: {
+      add: {
+        enabled: false
+      },
+      remove: {
+        enabled: false
+      }
+    }
+  })
+
+  browser.getNodes()[0].click()
+  await browser.update()
+
+  browser.expectJSON({
+    nodes: [
+      { level: 0, text: 'Object Types', selected: true },
+      { level: 0, text: 'Collection Types', selected: false },
+      { level: 0, text: 'Data Set Types', selected: false },
+      { level: 0, text: 'Material Types', selected: false },
+      { level: 0, text: 'Vocabulary Types', selected: false }
+    ],
+    buttons: {
+      add: {
+        enabled: true
+      },
+      remove: {
+        enabled: false
+      }
+    }
+  })
+}
+
+async function testSelectEntityType() {
+  const browser = await common.mount()
+
+  browser.getNodes()[0].getIcon().click()
+  await browser.update()
+
+  browser.expectJSON({
+    nodes: [
+      { level: 0, text: 'Object Types', selected: false },
+      { level: 1, text: fixture.ANOTHER_SAMPLE_TYPE_DTO.code, selected: false },
+      { level: 1, text: fixture.TEST_SAMPLE_TYPE_DTO.code, selected: false },
+      { level: 0, text: 'Collection Types', selected: false },
+      { level: 0, text: 'Data Set Types', selected: false },
+      { level: 0, text: 'Material Types', selected: false },
+      { level: 0, text: 'Vocabulary Types', selected: false }
+    ],
+    buttons: {
+      add: {
+        enabled: false
+      },
+      remove: {
+        enabled: false
+      }
+    }
+  })
+
+  browser.getNodes()[1].click()
+  await browser.update()
+
+  browser.expectJSON({
+    nodes: [
+      { level: 0, text: 'Object Types', selected: false },
+      { level: 1, text: fixture.ANOTHER_SAMPLE_TYPE_DTO.code, selected: true },
+      { level: 1, text: fixture.TEST_SAMPLE_TYPE_DTO.code, selected: false },
+      { level: 0, text: 'Collection Types', selected: false },
+      { level: 0, text: 'Data Set Types', selected: false },
+      { level: 0, text: 'Material Types', selected: false },
+      { level: 0, text: 'Vocabulary Types', selected: false }
+    ],
+    buttons: {
+      add: {
+        enabled: false
+      },
+      remove: {
+        enabled: true
+      }
+    }
+  })
+}
diff --git a/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserComponentTest.js b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserComponentTest.js
new file mode 100644
index 0000000000000000000000000000000000000000..eb943ad8621690cd2b97e034935f39c648e297bb
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserComponentTest.js
@@ -0,0 +1,39 @@
+import React from 'react'
+import ComponentTest from '@srcTest/js/components/common/ComponentTest.js'
+import BrowserWrapper from '@srcTest/js/components/common/browser/wrapper/BrowserWrapper.js'
+import TypeBrowser from '@src/js/components/types/browser/TypeBrowser.jsx'
+import openbis from '@srcTest/js/services/openbis.js'
+import fixture from '@srcTest/js/common/fixture.js'
+
+export default class TypeBrowserComponentTest extends ComponentTest {
+  static SUITE = 'TypeBrowserComponent'
+
+  constructor() {
+    super(
+      () => <TypeBrowser />,
+      wrapper => new BrowserWrapper(wrapper)
+    )
+  }
+
+  async beforeEach() {
+    super.beforeEach()
+
+    openbis.mockSearchSampleTypes([
+      fixture.TEST_SAMPLE_TYPE_DTO,
+      fixture.ANOTHER_SAMPLE_TYPE_DTO
+    ])
+
+    openbis.mockSearchExperimentTypes([fixture.TEST_EXPERIMENT_TYPE_DTO])
+    openbis.mockSearchDataSetTypes([fixture.TEST_DATA_SET_TYPE_DTO])
+
+    openbis.mockSearchMaterialTypes([
+      fixture.TEST_MATERIAL_TYPE_DTO,
+      fixture.ANOTHER_MATERIAL_TYPE_DTO
+    ])
+
+    openbis.mockSearchVocabularies([
+      fixture.TEST_VOCABULARY_DTO,
+      fixture.ANOTHER_VOCABULARY_DTO
+    ])
+  }
+}
diff --git a/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserController.test.js b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserController.test.js
deleted file mode 100644
index 964a707d722dbd2ce49fd7a158d18cb5c28cf77f..0000000000000000000000000000000000000000
--- a/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserController.test.js
+++ /dev/null
@@ -1,186 +0,0 @@
-import TypeBrowserController from '@src/js/components/types/browser/TypeBrowserController.js'
-import ComponentContext from '@srcTest/js/components/common/ComponentContext.js'
-import openbis from '@srcTest/js/services/openbis.js'
-import pages from '@src/js/common/consts/pages.js'
-import objectType from '@src/js/common/consts/objectType.js'
-import actions from '@src/js/store/actions/actions.js'
-import fixture from '@srcTest/js/common/fixture.js'
-
-let context = null
-let controller = null
-
-beforeEach(() => {
-  jest.resetAllMocks()
-
-  context = new ComponentContext()
-  controller = new TypeBrowserController()
-  controller.init(context)
-
-  openbis.mockSearchSampleTypes([
-    fixture.TEST_SAMPLE_TYPE_DTO,
-    fixture.ANOTHER_SAMPLE_TYPE_DTO
-  ])
-
-  openbis.mockSearchExperimentTypes([fixture.TEST_EXPERIMENT_TYPE_DTO])
-  openbis.mockSearchDataSetTypes([fixture.TEST_DATA_SET_TYPE_DTO])
-
-  openbis.mockSearchMaterialTypes([
-    fixture.TEST_MATERIAL_TYPE_DTO,
-    fixture.ANOTHER_MATERIAL_TYPE_DTO
-  ])
-
-  openbis.mockSearchVocabularies([
-    fixture.TEST_VOCABULARY_DTO,
-    fixture.ANOTHER_VOCABULARY_DTO
-  ])
-})
-
-describe('browser', () => {
-  test('load', async () => {
-    await controller.load()
-
-    expect(controller.getNodes()).toMatchObject([
-      {
-        text: 'Object Types',
-        expanded: false,
-        selected: false
-      },
-      {
-        text: 'Collection Types',
-        expanded: false,
-        selected: false
-      },
-      {
-        text: 'Data Set Types',
-        expanded: false,
-        selected: false
-      },
-      {
-        text: 'Material Types',
-        expanded: false,
-        selected: false
-      },
-      {
-        text: 'Vocabulary Types',
-        expanded: false,
-        selected: false
-      }
-    ])
-
-    context.expectNoActions()
-  })
-
-  test('filter', async () => {
-    await controller.load()
-
-    controller.filterChange('ANOTHER')
-
-    expect(controller.getNodes()).toMatchObject([
-      {
-        text: 'Object Types',
-        expanded: true,
-        selected: false,
-        children: [
-          {
-            text: fixture.ANOTHER_SAMPLE_TYPE_DTO.code,
-            expanded: false,
-            selected: false
-          }
-        ]
-      },
-      {
-        text: 'Material Types',
-        expanded: true,
-        selected: false,
-        children: [
-          {
-            text: fixture.ANOTHER_MATERIAL_TYPE_DTO.code,
-            expanded: false,
-            selected: false
-          }
-        ]
-      },
-      {
-        text: 'Vocabulary Types',
-        expanded: true,
-        selected: false,
-        children: [
-          {
-            text: fixture.ANOTHER_VOCABULARY_DTO.code,
-            expanded: false,
-            selected: false
-          }
-        ]
-      }
-    ])
-
-    context.expectNoActions()
-  })
-
-  test('add node', async () => {
-    await controller.load()
-
-    controller.nodeSelect('objectTypes')
-    controller.nodeAdd()
-
-    expectNewTypeAction(objectType.NEW_OBJECT_TYPE)
-  })
-
-  test('remove node', async () => {
-    openbis.mockSearchPropertyTypes([fixture.TEST_PROPERTY_TYPE_1_DTO])
-    openbis.deleteSampleTypes.mockReturnValue(Promise.resolve())
-
-    await controller.load()
-
-    expect(controller.isRemoveNodeDialogOpen()).toBe(false)
-    expect(openbis.deleteSampleTypes).toHaveBeenCalledTimes(0)
-
-    controller.nodeSelect('objectTypes/' + fixture.TEST_SAMPLE_TYPE_DTO.code)
-    controller.nodeRemove()
-
-    expect(controller.isRemoveNodeDialogOpen()).toBe(true)
-    expect(openbis.deleteSampleTypes).toHaveBeenCalledTimes(0)
-
-    await controller.nodeRemoveConfirm()
-
-    expect(controller.isRemoveNodeDialogOpen()).toBe(false)
-
-    const createDeleteTypeOperation = typeCode => {
-      const id = new openbis.EntityTypePermId(typeCode)
-      const options = new openbis.SampleTypeDeletionOptions()
-      options.setReason('deleted via ng_ui')
-      return new openbis.DeleteSampleTypesOperation([id], options)
-    }
-
-    const createDeletePropertyTypeOperation = propertyTypeCode => {
-      const id = new openbis.PropertyTypePermId(propertyTypeCode)
-      const options = new openbis.PropertyTypeDeletionOptions()
-      options.setReason('deleted via ng_ui')
-      return new openbis.DeletePropertyTypesOperation([id], options)
-    }
-
-    const options = new openbis.SynchronousOperationExecutionOptions()
-    options.setExecuteInOrder(true)
-
-    expect(openbis.executeOperations).toHaveBeenCalledWith(
-      [
-        createDeleteTypeOperation(fixture.TEST_SAMPLE_TYPE_DTO.code),
-        createDeletePropertyTypeOperation(fixture.TEST_PROPERTY_TYPE_1_DTO.code)
-      ],
-      options
-    )
-
-    expectDeleteTypeAction(
-      objectType.OBJECT_TYPE,
-      fixture.TEST_SAMPLE_TYPE_DTO.code
-    )
-  })
-})
-
-function expectNewTypeAction(type) {
-  context.expectAction(actions.objectNew(pages.TYPES, type))
-}
-
-function expectDeleteTypeAction(type, id) {
-  context.expectAction(actions.objectDelete(pages.TYPES, type, id))
-}
diff --git a/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserControllerAddNode.test.js b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserControllerAddNode.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..e6ac9bb3162fe327716ead33e26bca957beb7f16
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserControllerAddNode.test.js
@@ -0,0 +1,22 @@
+import TypeBrowserControllerTest from '@srcTest/js/components/types/browser/TypeBrowserControllerTest.js'
+import objectType from '@src/js/common/consts/objectType.js'
+
+let common = null
+
+beforeEach(() => {
+  common = new TypeBrowserControllerTest()
+  common.beforeEach()
+})
+
+describe(TypeBrowserControllerTest.SUITE, () => {
+  test('add node', testAddNode)
+})
+
+async function testAddNode() {
+  await common.controller.load()
+
+  common.controller.nodeSelect('objectTypes')
+  common.controller.nodeAdd()
+
+  common.expectNewTypeAction(objectType.NEW_OBJECT_TYPE)
+}
diff --git a/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserControllerFilter.test.js b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserControllerFilter.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..3de48fb648ea6b1904fb22a2a71bd0f55e7107d3
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserControllerFilter.test.js
@@ -0,0 +1,60 @@
+import TypeBrowserControllerTest from '@srcTest/js/components/types/browser/TypeBrowserControllerTest.js'
+import fixture from '@srcTest/js/common/fixture.js'
+
+let common = null
+
+beforeEach(() => {
+  common = new TypeBrowserControllerTest()
+  common.beforeEach()
+})
+
+describe(TypeBrowserControllerTest.SUITE, () => {
+  test('filter', testFilter)
+})
+
+async function testFilter() {
+  await common.controller.load()
+
+  common.controller.filterChange('ANOTHER')
+
+  expect(common.controller.getNodes()).toMatchObject([
+    {
+      text: 'Object Types',
+      expanded: true,
+      selected: false,
+      children: [
+        {
+          text: fixture.ANOTHER_SAMPLE_TYPE_DTO.code,
+          expanded: false,
+          selected: false
+        }
+      ]
+    },
+    {
+      text: 'Material Types',
+      expanded: true,
+      selected: false,
+      children: [
+        {
+          text: fixture.ANOTHER_MATERIAL_TYPE_DTO.code,
+          expanded: false,
+          selected: false
+        }
+      ]
+    },
+    {
+      text: 'Vocabulary Types',
+      expanded: true,
+      selected: false,
+      children: [
+        {
+          text: fixture.ANOTHER_VOCABULARY_DTO.code,
+          expanded: false,
+          selected: false
+        }
+      ]
+    }
+  ])
+
+  common.context.expectNoActions()
+}
diff --git a/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserControllerLoad.test.js b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserControllerLoad.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..85c4b3a9138ca1795bf51bff08deb7979569822c
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserControllerLoad.test.js
@@ -0,0 +1,46 @@
+import TypeBrowserControllerTest from '@srcTest/js/components/types/browser/TypeBrowserControllerTest.js'
+
+let common = null
+
+beforeEach(() => {
+  common = new TypeBrowserControllerTest()
+  common.beforeEach()
+})
+
+describe(TypeBrowserControllerTest.SUITE, () => {
+  test('load', testLoad)
+})
+
+async function testLoad() {
+  await common.controller.load()
+
+  expect(common.controller.getNodes()).toMatchObject([
+    {
+      text: 'Object Types',
+      expanded: false,
+      selected: false
+    },
+    {
+      text: 'Collection Types',
+      expanded: false,
+      selected: false
+    },
+    {
+      text: 'Data Set Types',
+      expanded: false,
+      selected: false
+    },
+    {
+      text: 'Material Types',
+      expanded: false,
+      selected: false
+    },
+    {
+      text: 'Vocabulary Types',
+      expanded: false,
+      selected: false
+    }
+  ])
+
+  common.context.expectNoActions()
+}
diff --git a/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserControllerRemoveNode.test.js b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserControllerRemoveNode.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..1efd40bd8951934d2f222d913fd1c23a3863bd30
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserControllerRemoveNode.test.js
@@ -0,0 +1,67 @@
+import TypeBrowserControllerTest from '@srcTest/js/components/types/browser/TypeBrowserControllerTest.js'
+import objectType from '@src/js/common/consts/objectType.js'
+import openbis from '@srcTest/js/services/openbis.js'
+import fixture from '@srcTest/js/common/fixture.js'
+
+let common = null
+
+beforeEach(() => {
+  common = new TypeBrowserControllerTest()
+  common.beforeEach()
+})
+
+describe(TypeBrowserControllerTest.SUITE, () => {
+  test('remove node', testRemoveNode)
+})
+
+async function testRemoveNode() {
+  openbis.mockSearchPropertyTypes([fixture.TEST_PROPERTY_TYPE_1_DTO])
+  openbis.deleteSampleTypes.mockReturnValue(Promise.resolve())
+
+  await common.controller.load()
+
+  expect(common.controller.isRemoveNodeDialogOpen()).toBe(false)
+  expect(openbis.deleteSampleTypes).toHaveBeenCalledTimes(0)
+
+  common.controller.nodeSelect(
+    'objectTypes/' + fixture.TEST_SAMPLE_TYPE_DTO.code
+  )
+  common.controller.nodeRemove()
+
+  expect(common.controller.isRemoveNodeDialogOpen()).toBe(true)
+  expect(openbis.deleteSampleTypes).toHaveBeenCalledTimes(0)
+
+  await common.controller.nodeRemoveConfirm()
+
+  expect(common.controller.isRemoveNodeDialogOpen()).toBe(false)
+
+  const createDeleteTypeOperation = typeCode => {
+    const id = new openbis.EntityTypePermId(typeCode)
+    const options = new openbis.SampleTypeDeletionOptions()
+    options.setReason('deleted via ng_ui')
+    return new openbis.DeleteSampleTypesOperation([id], options)
+  }
+
+  const createDeletePropertyTypeOperation = propertyTypeCode => {
+    const id = new openbis.PropertyTypePermId(propertyTypeCode)
+    const options = new openbis.PropertyTypeDeletionOptions()
+    options.setReason('deleted via ng_ui')
+    return new openbis.DeletePropertyTypesOperation([id], options)
+  }
+
+  const options = new openbis.SynchronousOperationExecutionOptions()
+  options.setExecuteInOrder(true)
+
+  expect(openbis.executeOperations).toHaveBeenCalledWith(
+    [
+      createDeleteTypeOperation(fixture.TEST_SAMPLE_TYPE_DTO.code),
+      createDeletePropertyTypeOperation(fixture.TEST_PROPERTY_TYPE_1_DTO.code)
+    ],
+    options
+  )
+
+  common.expectDeleteTypeAction(
+    objectType.OBJECT_TYPE,
+    fixture.TEST_SAMPLE_TYPE_DTO.code
+  )
+}
diff --git a/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserControllerTest.js b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserControllerTest.js
new file mode 100644
index 0000000000000000000000000000000000000000..d7fd9e3c177dda3433fb8351692b021ae7cb2948
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/types/browser/TypeBrowserControllerTest.js
@@ -0,0 +1,44 @@
+import TypeBrowserController from '@src/js/components/types/browser/TypeBrowserController.js'
+import ComponentContext from '@srcTest/js/components/common/ComponentContext.js'
+import openbis from '@srcTest/js/services/openbis.js'
+import pages from '@src/js/common/consts/pages.js'
+import actions from '@src/js/store/actions/actions.js'
+import fixture from '@srcTest/js/common/fixture.js'
+
+export default class TypeBrowserControllerTest {
+  static SUITE = 'TypeBrowserController'
+
+  beforeEach() {
+    jest.resetAllMocks()
+
+    this.context = new ComponentContext()
+    this.controller = new TypeBrowserController()
+    this.controller.init(this.context)
+
+    openbis.mockSearchSampleTypes([
+      fixture.TEST_SAMPLE_TYPE_DTO,
+      fixture.ANOTHER_SAMPLE_TYPE_DTO
+    ])
+
+    openbis.mockSearchExperimentTypes([fixture.TEST_EXPERIMENT_TYPE_DTO])
+    openbis.mockSearchDataSetTypes([fixture.TEST_DATA_SET_TYPE_DTO])
+
+    openbis.mockSearchMaterialTypes([
+      fixture.TEST_MATERIAL_TYPE_DTO,
+      fixture.ANOTHER_MATERIAL_TYPE_DTO
+    ])
+
+    openbis.mockSearchVocabularies([
+      fixture.TEST_VOCABULARY_DTO,
+      fixture.ANOTHER_VOCABULARY_DTO
+    ])
+  }
+
+  expectNewTypeAction(type) {
+    this.context.expectAction(actions.objectNew(pages.TYPES, type))
+  }
+
+  expectDeleteTypeAction(type, id) {
+    this.context.expectAction(actions.objectDelete(pages.TYPES, type, id))
+  }
+}
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentAddProperty.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentAddProperty.test.js
index 8c55ac5593ea263b229e2262c0fbcb7f50d0fe08..30f7729caa72317050c2ab47292fbca3c9d5eb9e 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentAddProperty.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentAddProperty.test.js
@@ -10,7 +10,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('TypeFormComponentAddProperty', () => {
+describe(TypeFormComponentTest.SUITE, () => {
   test('add local property', testAddLocalProperty)
   test('add new global property', testAddNewGlobalProperty)
   test('add existing global property', testAddExistingGlobalProperty)
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentAddSection.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentAddSection.test.js
index 3672245a920a5c261e9b18dc2fecbc732f857377..8bf337318cea1ae01a526863a4c4a87146288aa3 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentAddSection.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentAddSection.test.js
@@ -8,7 +8,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('TypeFormComponentAddSection', () => {
+describe(TypeFormComponentTest.SUITE, () => {
   test('add section', testAddSection)
 })
 
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentChangeProperty.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentChangeProperty.test.js
index b8a36cefa3d5a5e9758e4afcdd12c229470a7851..4675d2303fec59c06cb47b60923fbc49f05f0300 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentChangeProperty.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentChangeProperty.test.js
@@ -9,7 +9,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('TypeFormComponentChangeProperty', () => {
+describe(TypeFormComponentTest.SUITE, () => {
   test('change property', testChangeProperty)
   test('convert property', testConvertProperty)
 })
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentChangeSection.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentChangeSection.test.js
index 939dd4cb2e7fbbbcdd2488ba02856a95c74ccb06..7531cddcf5d54915d05d8ed536e314e6de25bbb3 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentChangeSection.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentChangeSection.test.js
@@ -8,7 +8,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('TypeFormComponentChangeSection', () => {
+describe(TypeFormComponentTest.SUITE, () => {
   test('change section', testChangeSection)
 })
 
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentChangeType.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentChangeType.test.js
index b10103afa1dfd8e735534d2a216e15ddb7692891..6b61509eb75efc7172fd9e7057b691e84103e54a 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentChangeType.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentChangeType.test.js
@@ -8,7 +8,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('TypeFormComponentChangeType', () => {
+describe(TypeFormComponentTest.SUITE, () => {
   test('change type', testChangeType)
 })
 
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentInternal.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentInternal.test.js
index c5f24cc6256585fa255722e3a1d436df8d1569e8..ecf0117db81c1efe3566faaa1e90112499433302 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentInternal.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentInternal.test.js
@@ -10,7 +10,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('TypeFormComponentInternal', () => {
+describe(TypeFormComponentTest.SUITE, () => {
   test('internal', testInternal)
 })
 
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentLoad.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentLoad.test.js
index 7dd76a5801edd2ce4d40e0fc4febb1ba4de14b09..d13c97bbcc84fa6fd21432094ff725f4e17032a7 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentLoad.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentLoad.test.js
@@ -8,7 +8,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('TypeFormComponentLoad', () => {
+describe(TypeFormComponentTest.SUITE, () => {
   test('load new', testLoadNew)
   test('load existing', testLoadExisting)
 })
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentRemoveProperty.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentRemoveProperty.test.js
index 50005ad1f45e6585c3c567922c16477803db0ac6..269feb492fb51b1007382b371a7efb47a0dafa51 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentRemoveProperty.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentRemoveProperty.test.js
@@ -8,7 +8,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('TypeFormComponentRemoveProperty', () => {
+describe(TypeFormComponentTest.SUITE, () => {
   test('remove property', testRemoveProperty)
 })
 
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentRemoveSection.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentRemoveSection.test.js
index 365ba639299ad341763dad753f4cac761949d98f..5d7da08e00f16dc7c11b86fa30a3021921efa304 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentRemoveSection.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentRemoveSection.test.js
@@ -8,7 +8,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('TypeFormComponentRemoveSection', () => {
+describe(TypeFormComponentTest.SUITE, () => {
   test('remove section', testRemoveSection)
 })
 
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentSelectProperty.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentSelectProperty.test.js
index abdfea7d26db7c26b3a0b55ae4c97f1ed77d18b0..1e3d1ff021beb7bc5fc751996c099a7eddcb0cf8 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentSelectProperty.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentSelectProperty.test.js
@@ -10,7 +10,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('TypeFormComponentSelectProperty', () => {
+describe(TypeFormComponentTest.SUITE, () => {
   test('select property local unused', testSelectPropertyLocalUnused)
   test('select property local used', testSelectPropertyLocalUsed)
   test('select property global unused', testSelectPropertyGlobalUnused)
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentSelectSection.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentSelectSection.test.js
index 3baaeebd81161171a52a6d5f51914e81866e2754..90e7b10167009e3688af816e7c943b480a1d71ac 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentSelectSection.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentSelectSection.test.js
@@ -8,7 +8,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('TypeFormComponentSelectSection', () => {
+describe(TypeFormComponentTest.SUITE, () => {
   test('select section', testSelectSection)
 })
 
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentTest.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentTest.js
index b0476dd17c2604ad45d7bc3778acdd6d8c544115..13931192b824a1b43703e2ef07ffe1a32270964e 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentTest.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentTest.js
@@ -9,6 +9,8 @@ import objectTypes from '@src/js/common/consts/objectType.js'
 jest.mock('@src/js/components/types/form/TypeFormFacade')
 
 export default class TypeFormComponentTest extends ComponentTest {
+  static SUITE = 'TypeFormComponent'
+
   constructor() {
     super(
       object => <TypeForm object={object} controller={this.controller} />,
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentValidate.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentValidate.test.js
index ad0a59e4a23609c365610fa0cca0a68afe10e1bd..80548a6776632091e12e6dcd5db572e1a80eb23d 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentValidate.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormComponentValidate.test.js
@@ -7,7 +7,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('TypeFormComponentValidate', () => {
+describe(TypeFormComponentTest.SUITE, () => {
   test('validate type', testValidateType)
   test('validate property', testValidateProperty)
   test('validate type and property', testValidateTypeAndProperty)
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerAddProperty.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerAddProperty.test.js
index a1884dc71e5e2a589a55ee6b7f79619de0da5f1c..f65e8d3341a234f7706f3447e52af558d95dde53 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerAddProperty.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerAddProperty.test.js
@@ -1,209 +1,200 @@
-import ComponentContext from '@srcTest/js/components/common/ComponentContext.js'
-import TypeFormControler from '@src/js/components/types/form/TypeFormController.js'
+import TypeFormControllerTest from '@srcTest/js/components/types/form/TypeFormControllerTest.js'
 import TypeFormSelectionType from '@src/js/components/types/form/TypeFormSelectionType.js'
-import TypeFormFacade from '@src/js/components/types/form/TypeFormFacade'
 import objectTypes from '@src/js/common/consts/objectType.js'
 import fixture from '@srcTest/js/common/fixture.js'
 
-jest.mock('@src/js/components/types/form/TypeFormFacade')
-
-let context = null
-let facade = null
-let controller = null
+let common = null
 
 beforeEach(() => {
-  jest.resetAllMocks()
-  context = new ComponentContext()
-  context.setProps({
-    object: {
-      id: 'TEST_OBJECT_ID',
-      type: objectTypes.OBJECT_TYPE
-    }
+  common = new TypeFormControllerTest()
+  common.beforeEach()
+  common.init({
+    id: 'TEST_OBJECT_ID',
+    type: objectTypes.OBJECT_TYPE
   })
-  facade = new TypeFormFacade()
-  controller = new TypeFormControler(facade)
-  controller.init(context)
 })
 
 afterEach(() => {
-  expect(facade.loadType).toHaveBeenCalledWith(context.getProps().object)
-  expect(facade.loadUsages).toHaveBeenCalledWith(context.getProps().object)
+  common.afterEach()
 })
 
-describe('TypeFormController.handleAddProperty', () => {
-  test('add with a section selected', async () => {
-    facade.loadType.mockReturnValue(
-      Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
-    )
-    facade.loadUsages.mockReturnValue(Promise.resolve({}))
+describe(TypeFormControllerTest.SUITE, () => {
+  test('add property with a section selected', testAddWithSectionSelected)
+  test('add property with a property selected', testAddWithPropertySelected)
+})
 
-    await controller.load()
-    controller.handleSelectionChange(TypeFormSelectionType.SECTION, {
-      id: 'section-1'
-    })
+async function testAddWithSectionSelected() {
+  common.facade.loadType.mockReturnValue(
+    Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
+  )
+  common.facade.loadUsages.mockReturnValue(Promise.resolve({}))
 
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.SECTION,
-        params: {
-          id: 'section-1'
-        }
+  await common.controller.load()
+  common.controller.handleSelectionChange(TypeFormSelectionType.SECTION, {
+    id: 'section-1'
+  })
+
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.SECTION,
+      params: {
+        id: 'section-1'
+      }
+    },
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
+      },
+      {
+        id: 'property-1',
+        code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
       },
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
-        },
-        {
-          id: 'property-1',
-          code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        }
-      ],
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
+      }
+    ],
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
+  })
 
-    controller.handleAddProperty()
+  common.controller.handleAddProperty()
 
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.PROPERTY,
-        params: {
-          id: 'property-3'
-        }
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.PROPERTY,
+      params: {
+        id: 'property-3'
+      }
+    },
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
+      },
+      {
+        id: 'property-1',
+        code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
+      },
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
       },
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
-        },
-        {
-          id: 'property-1',
-          code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        },
-        {
-          id: 'property-3',
-          code: { value: null }
-        }
-      ],
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2', 'property-3']
-        }
-      ]
-    })
+      {
+        id: 'property-3',
+        code: { value: null }
+      }
+    ],
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2', 'property-3']
+      }
+    ]
   })
+}
 
-  test('add with a property selected', async () => {
-    facade.loadType.mockReturnValue(
-      Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
-    )
-    facade.loadUsages.mockReturnValue(Promise.resolve({}))
+async function testAddWithPropertySelected() {
+  common.facade.loadType.mockReturnValue(
+    Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
+  )
+  common.facade.loadUsages.mockReturnValue(Promise.resolve({}))
 
-    await controller.load()
-    controller.handleSelectionChange(TypeFormSelectionType.PROPERTY, {
-      id: 'property-1'
-    })
+  await common.controller.load()
+  common.controller.handleSelectionChange(TypeFormSelectionType.PROPERTY, {
+    id: 'property-1'
+  })
 
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.PROPERTY,
-        params: {
-          id: 'property-1'
-        }
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.PROPERTY,
+      params: {
+        id: 'property-1'
+      }
+    },
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
+      },
+      {
+        id: 'property-1',
+        code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
       },
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
-        },
-        {
-          id: 'property-1',
-          code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        }
-      ],
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
+      }
+    ],
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
+  })
 
-    controller.handleAddProperty()
+  common.controller.handleAddProperty()
 
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.PROPERTY,
-        params: {
-          id: 'property-3'
-        }
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.PROPERTY,
+      params: {
+        id: 'property-3'
+      }
+    },
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
       },
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
-        },
-        {
-          id: 'property-1',
-          code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        },
-        {
-          id: 'property-3',
-          code: { value: null }
-        }
-      ],
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-3', 'property-2']
-        }
-      ]
-    })
+      {
+        id: 'property-1',
+        code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
+      },
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
+      },
+      {
+        id: 'property-3',
+        code: { value: null }
+      }
+    ],
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-3', 'property-2']
+      }
+    ]
   })
-})
+}
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerAddSection.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerAddSection.test.js
index dbbbcb0ecd16f76db8b9691c5145c4ddbb2f699b..368cefd8146a458d5c480f2303c953a560ca9a24 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerAddSection.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerAddSection.test.js
@@ -1,208 +1,200 @@
-import ComponentContext from '@srcTest/js/components/common/ComponentContext.js'
-import TypeFormControler from '@src/js/components/types/form/TypeFormController.js'
+import TypeFormControllerTest from '@srcTest/js/components/types/form/TypeFormControllerTest.js'
 import TypeFormSelectionType from '@src/js/components/types/form/TypeFormSelectionType.js'
-import TypeFormFacade from '@src/js/components/types/form/TypeFormFacade'
 import objectTypes from '@src/js/common/consts/objectType.js'
 import fixture from '@srcTest/js/common/fixture.js'
 
-jest.mock('@src/js/components/types/form/TypeFormFacade')
-
-let context = null
-let facade = null
-let controller = null
+let common = null
 
 beforeEach(() => {
-  jest.resetAllMocks()
-  context = new ComponentContext()
-  context.setProps({
-    object: {
-      id: 'TEST_OBJECT_ID',
-      type: objectTypes.OBJECT_TYPE
-    }
+  common = new TypeFormControllerTest()
+  common.beforeEach()
+  common.init({
+    id: 'TEST_OBJECT_ID',
+    type: objectTypes.OBJECT_TYPE
   })
-  facade = new TypeFormFacade()
-  controller = new TypeFormControler(facade)
-  controller.init(context)
 })
 
 afterEach(() => {
-  expect(facade.loadType).toHaveBeenCalledWith(context.getProps().object)
-  expect(facade.loadUsages).toHaveBeenCalledWith(context.getProps().object)
+  common.afterEach()
+})
+
+describe(TypeFormControllerTest.SUITE, () => {
+  test('add section with nothing selected', testAddWithNothingSelected)
+  test('add section with a property selected', testAddWithPropertySelected)
+  test('add section with a section selected', testAddWithSectionSelected)
 })
 
-describe('TypeFormController.handleAddProperty', () => {
-  test('add with nothing selected', async () => {
-    facade.loadType.mockReturnValue(
-      Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
-    )
-    facade.loadUsages.mockReturnValue(Promise.resolve({}))
-
-    await controller.load()
-
-    expect(context.getState()).toMatchObject({
-      selection: null,
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
-
-    controller.handleAddSection()
-
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.SECTION,
-        params: {
-          id: 'section-2'
-        }
+async function testAddWithNothingSelected() {
+  common.facade.loadType.mockReturnValue(
+    Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
+  )
+  common.facade.loadUsages.mockReturnValue(Promise.resolve({}))
+
+  await common.controller.load()
+
+  expect(common.context.getState()).toMatchObject({
+    selection: null,
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
       },
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        },
-        {
-          id: 'section-2',
-          name: { value: null },
-          properties: []
-        }
-      ]
-    })
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
   })
 
-  test('add with a property selected', async () => {
-    facade.loadType.mockReturnValue(
-      Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
-    )
-    facade.loadUsages.mockReturnValue(Promise.resolve({}))
-
-    await controller.load()
-    controller.handleSelectionChange(TypeFormSelectionType.PROPERTY, {
-      id: 'property-0'
-    })
-
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.PROPERTY,
-        params: {
-          id: 'property-0'
-        }
+  common.controller.handleAddSection()
+
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.SECTION,
+      params: {
+        id: 'section-2'
+      }
+    },
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
       },
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
-
-    controller.handleAddSection()
-
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.SECTION,
-        params: {
-          id: 'section-2'
-        }
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      },
+      {
+        id: 'section-2',
+        name: { value: null },
+        properties: []
+      }
+    ]
+  })
+}
+
+async function testAddWithPropertySelected() {
+  common.facade.loadType.mockReturnValue(
+    Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
+  )
+  common.facade.loadUsages.mockReturnValue(Promise.resolve({}))
+
+  await common.controller.load()
+  common.controller.handleSelectionChange(TypeFormSelectionType.PROPERTY, {
+    id: 'property-0'
+  })
+
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.PROPERTY,
+      params: {
+        id: 'property-0'
+      }
+    },
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
       },
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-2',
-          name: { value: null },
-          properties: []
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
   })
 
-  test('add with a section selected', async () => {
-    facade.loadType.mockReturnValue(
-      Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
-    )
-    facade.loadUsages.mockReturnValue(Promise.resolve({}))
-
-    await controller.load()
-    controller.handleSelectionChange(TypeFormSelectionType.SECTION, {
-      id: 'section-0'
-    })
-
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.SECTION,
-        params: {
-          id: 'section-0'
-        }
+  common.controller.handleAddSection()
+
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.SECTION,
+      params: {
+        id: 'section-2'
+      }
+    },
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
       },
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
-
-    controller.handleAddSection()
-
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.SECTION,
-        params: {
-          id: 'section-2'
-        }
+      {
+        id: 'section-2',
+        name: { value: null },
+        properties: []
       },
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-2',
-          name: { value: null },
-          properties: []
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
   })
-})
+}
+
+async function testAddWithSectionSelected() {
+  common.facade.loadType.mockReturnValue(
+    Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
+  )
+  common.facade.loadUsages.mockReturnValue(Promise.resolve({}))
+
+  await common.controller.load()
+  common.controller.handleSelectionChange(TypeFormSelectionType.SECTION, {
+    id: 'section-0'
+  })
+
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.SECTION,
+      params: {
+        id: 'section-0'
+      }
+    },
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
+  })
+
+  common.controller.handleAddSection()
+
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.SECTION,
+      params: {
+        id: 'section-2'
+      }
+    },
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-2',
+        name: { value: null },
+        properties: []
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
+  })
+}
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerChange.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerChange.test.js
index 54d63ebcd39b971e77b04cc71a72826af9e8c7e4..e47eacf5d3e1a7e9e505023648703c6f70cdb5b4 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerChange.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerChange.test.js
@@ -1,155 +1,147 @@
-import ComponentContext from '@srcTest/js/components/common/ComponentContext.js'
-import TypeFormControler from '@src/js/components/types/form/TypeFormController.js'
+import TypeFormControllerTest from '@srcTest/js/components/types/form/TypeFormControllerTest.js'
 import TypeFormSelectionType from '@src/js/components/types/form/TypeFormSelectionType.js'
-import TypeFormFacade from '@src/js/components/types/form/TypeFormFacade'
 import objectTypes from '@src/js/common/consts/objectType.js'
 import fixture from '@srcTest/js/common/fixture.js'
 
-jest.mock('@src/js/components/types/form/TypeFormFacade')
-
-let context = null
-let facade = null
-let controller = null
+let common = null
 
 beforeEach(() => {
-  jest.resetAllMocks()
-  context = new ComponentContext()
-  context.setProps({
-    object: {
-      id: 'TEST_OBJECT_ID',
-      type: objectTypes.OBJECT_TYPE
-    }
+  common = new TypeFormControllerTest()
+  common.beforeEach()
+  common.init({
+    id: 'TEST_OBJECT_ID',
+    type: objectTypes.OBJECT_TYPE
   })
-  facade = new TypeFormFacade()
-  controller = new TypeFormControler(facade)
-  controller.init(context)
 })
 
 afterEach(() => {
-  expect(facade.loadType).toHaveBeenCalledWith(context.getProps().object)
-  expect(facade.loadUsages).toHaveBeenCalledWith(context.getProps().object)
+  common.afterEach()
 })
 
-describe('TypeFormController.handleChange', () => {
-  test('type', async () => {
-    facade.loadType.mockReturnValue(
-      Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
-    )
-    facade.loadUsages.mockReturnValue(Promise.resolve({}))
+describe(TypeFormControllerTest.SUITE, () => {
+  test('change type', testChangeType)
+  test('change section', testChangeSection)
+  test('change property', testChangeProperty)
+})
 
-    await controller.load()
+async function testChangeType() {
+  common.facade.loadType.mockReturnValue(
+    Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
+  )
+  common.facade.loadUsages.mockReturnValue(Promise.resolve({}))
 
-    expect(context.getState()).toMatchObject({
-      type: {
-        description: { value: 'TEST_DESCRIPTION' }
+  await common.controller.load()
+
+  expect(common.context.getState()).toMatchObject({
+    type: {
+      description: { value: 'TEST_DESCRIPTION' }
+    }
+  })
+
+  common.controller.handleChange(TypeFormSelectionType.TYPE, {
+    field: 'description',
+    value: 'NEW_DESCRIPTION'
+  })
+
+  expect(common.context.getState()).toMatchObject({
+    type: {
+      description: { value: 'NEW_DESCRIPTION' }
+    }
+  })
+}
+
+async function testChangeSection() {
+  common.facade.loadType.mockReturnValue(
+    Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
+  )
+  common.facade.loadUsages.mockReturnValue(Promise.resolve({}))
+
+  await common.controller.load()
+
+  expect(common.context.getState()).toMatchObject({
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
       }
-    })
+    ]
+  })
 
-    controller.handleChange(TypeFormSelectionType.TYPE, {
-      field: 'description',
-      value: 'NEW_DESCRIPTION'
-    })
+  common.controller.handleChange(TypeFormSelectionType.SECTION, {
+    id: 'section-1',
+    field: 'name',
+    value: 'TEST_NAME'
+  })
 
-    expect(context.getState()).toMatchObject({
-      type: {
-        description: { value: 'NEW_DESCRIPTION' }
+  expect(common.context.getState()).toMatchObject({
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_NAME' },
+        properties: ['property-1', 'property-2']
       }
-    })
+    ]
+  })
+}
+
+async function testChangeProperty() {
+  common.facade.loadType.mockReturnValue(
+    Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
+  )
+  common.facade.loadUsages.mockReturnValue(Promise.resolve({}))
+
+  await common.controller.load()
+
+  expect(common.context.getState()).toMatchObject({
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
+      },
+      {
+        id: 'property-1',
+        code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() },
+        description: { value: null }
+      },
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
+      }
+    ]
   })
 
-  test('section', async () => {
-    facade.loadType.mockReturnValue(
-      Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
-    )
-    facade.loadUsages.mockReturnValue(Promise.resolve({}))
-
-    await controller.load()
-
-    expect(context.getState()).toMatchObject({
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
-
-    controller.handleChange(TypeFormSelectionType.SECTION, {
-      id: 'section-1',
-      field: 'name',
-      value: 'TEST_NAME'
-    })
-
-    expect(context.getState()).toMatchObject({
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_NAME' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
+  common.controller.handleChange(TypeFormSelectionType.PROPERTY, {
+    id: 'property-1',
+    field: 'description',
+    value: 'TEST_DESCRIPTION'
   })
 
-  test('property', async () => {
-    facade.loadType.mockReturnValue(
-      Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
-    )
-    facade.loadUsages.mockReturnValue(Promise.resolve({}))
-
-    await controller.load()
-
-    expect(context.getState()).toMatchObject({
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
-        },
-        {
-          id: 'property-1',
-          code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() },
-          description: { value: null }
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        }
-      ]
-    })
-
-    controller.handleChange(TypeFormSelectionType.PROPERTY, {
-      id: 'property-1',
-      field: 'description',
-      value: 'TEST_DESCRIPTION'
-    })
-
-    expect(context.getState()).toMatchObject({
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
-        },
-        {
-          id: 'property-1',
-          code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() },
-          description: { value: 'TEST_DESCRIPTION' }
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        }
-      ]
-    })
+  expect(common.context.getState()).toMatchObject({
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
+      },
+      {
+        id: 'property-1',
+        code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() },
+        description: { value: 'TEST_DESCRIPTION' }
+      },
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
+      }
+    ]
   })
-})
+}
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerLoad.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerLoad.test.js
index 6e64884070f2800fe8597d2173c7d40fada87e1b..91bbd1df4adedcacc617d3ec8616fd57d2d7b72e 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerLoad.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerLoad.test.js
@@ -1,11 +1,287 @@
-import ComponentContext from '@srcTest/js/components/common/ComponentContext.js'
-import TypeFormControler from '@src/js/components/types/form/TypeFormController.js'
+import TypeFormControllerTest from '@srcTest/js/components/types/form/TypeFormControllerTest.js'
 import TypeFormSelectionType from '@src/js/components/types/form/TypeFormSelectionType.js'
-import TypeFormFacade from '@src/js/components/types/form/TypeFormFacade'
 import objectTypes from '@src/js/common/consts/objectType.js'
 import openbis from '@srcTest/js/services/openbis.js'
 
-jest.mock('@src/js/components/types/form/TypeFormFacade')
+let common = null
+
+beforeEach(() => {
+  common = new TypeFormControllerTest()
+  common.beforeEach()
+})
+
+describe(TypeFormControllerTest.SUITE, () => {
+  test('load successful existing', testLoadSuccessfulExisting)
+  test('load successful new', testLoadSuccessfulNew)
+  test('load maintains section selection', testLoadMaintainsSectionSelection)
+  test('load maintains property selection', testLoadMaintainsPropertySelection)
+})
+
+async function testLoadSuccessfulExisting() {
+  common.init({
+    id: 'TEST_OBJECT_ID',
+    type: objectTypes.OBJECT_TYPE
+  })
+
+  common.facade.loadType.mockReturnValue(Promise.resolve(TEST_SAMPLE_TYPE_DTO))
+  common.facade.loadUsages.mockReturnValue(
+    Promise.resolve({
+      type: 10,
+      propertyLocal: {
+        [TEST_PROPERTY_TYPE_1_DTO.getCode()]: 1,
+        [TEST_PROPERTY_TYPE_2_DTO.getCode()]: 2
+      },
+      propertyGlobal: {
+        [TEST_PROPERTY_TYPE_1_DTO.getCode()]: 10,
+        [TEST_PROPERTY_TYPE_2_DTO.getCode()]: 20
+      }
+    })
+  )
+
+  await common.controller.load()
+
+  expect(common.context.getState()).toMatchObject({
+    loading: false,
+    selection: null,
+    type: {
+      code: { value: 'TEST_TYPE' },
+      description: { value: 'TEST_DESCRIPTION' },
+      listable: { value: true },
+      showContainer: { value: true },
+      showParents: { value: true },
+      showParentMetadata: { value: true },
+      autoGeneratedCode: { value: true },
+      generatedCodePrefix: { value: 'TEST_PREFIX' },
+      subcodeUnique: { value: true },
+      validationPlugin: { value: 'TEST_PLUGIN_2' },
+      usages: 10
+    },
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: 'TEST_PROPERTY_TYPE_1' },
+        label: { value: 'TEST_LABEL_1' },
+        description: { value: 'TEST_DESCRIPTION_1' },
+        dataType: { value: 'INTEGER' },
+        plugin: { value: 'TEST_PLUGIN_1' },
+        mandatory: { value: true },
+        showInEditView: { value: true },
+        showRawValueInForms: { value: true },
+        usagesLocal: 1,
+        usagesGlobal: 10,
+        section: 'section-0'
+      },
+      {
+        id: 'property-1',
+        code: { value: 'TEST_PROPERTY_TYPE_2' },
+        label: { value: 'TEST_LABEL_2' },
+        description: { value: 'TEST_DESCRIPTION_2' },
+        dataType: { value: 'CONTROLLEDVOCABULARY' },
+        vocabulary: { value: 'TEST_VOCABULARY' },
+        mandatory: { value: true },
+        showInEditView: { value: false },
+        showRawValueInForms: { value: true },
+        usagesLocal: 2,
+        usagesGlobal: 20,
+        section: 'section-1'
+      },
+      {
+        id: 'property-2',
+        code: { value: 'TEST_PROPERTY_TYPE_3' },
+        label: { value: 'TEST_LABEL_3' },
+        description: { value: 'TEST_DESCRIPTION_3' },
+        dataType: { value: 'MATERIAL' },
+        materialType: { value: 'TEST_MATERIAL_TYPE' },
+        mandatory: { value: false },
+        showInEditView: { value: true },
+        showRawValueInForms: { value: false },
+        usagesLocal: 0,
+        usagesGlobal: 0,
+        section: 'section-1'
+      }
+    ],
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
+  })
+
+  expect(common.facade.loadType).toHaveBeenCalledWith(
+    common.context.getProps().object
+  )
+  expect(common.facade.loadUsages).toHaveBeenCalledWith(
+    common.context.getProps().object
+  )
+}
+
+async function testLoadSuccessfulNew() {
+  common.init({
+    id: 'TEST_OBJECT_ID',
+    type: objectTypes.NEW_OBJECT_TYPE
+  })
+
+  await common.controller.load()
+
+  expect(common.context.getState()).toMatchObject({
+    loading: false,
+    selection: null,
+    type: {
+      code: { value: null },
+      description: { value: null },
+      listable: { value: true },
+      showContainer: { value: false },
+      showParents: { value: true },
+      showParentMetadata: { value: false },
+      autoGeneratedCode: { value: true },
+      generatedCodePrefix: { value: null },
+      subcodeUnique: { value: false },
+      validationPlugin: { value: null },
+      usages: 0
+    },
+    properties: [],
+    sections: []
+  })
+}
+
+async function testLoadMaintainsSectionSelection() {
+  common.init({
+    id: 'TEST_OBJECT_ID',
+    type: objectTypes.OBJECT_TYPE
+  })
+
+  common.facade.loadType.mockReturnValue(
+    Promise.resolve(new openbis.SampleType())
+  )
+  common.facade.loadUsages.mockReturnValue({})
+
+  await common.controller.load()
+
+  common.controller.handleAddSection()
+  common.controller.handleAddProperty()
+  common.controller.handleAddProperty()
+  common.controller.handleAddSection()
+  common.controller.handleAddProperty()
+  common.controller.handleOrderChange(TypeFormSelectionType.SECTION, {
+    fromIndex: 0,
+    toIndex: 1
+  })
+  common.controller.handleSelectionChange(TypeFormSelectionType.SECTION, {
+    id: 'section-0'
+  })
+
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.SECTION,
+      params: { id: 'section-0' }
+    },
+    sections: [
+      {
+        id: 'section-1',
+        properties: ['property-2']
+      },
+      {
+        id: 'section-0',
+        properties: ['property-0', 'property-1']
+      }
+    ]
+  })
+
+  common.facade.loadType.mockReturnValue(Promise.resolve(TEST_SAMPLE_TYPE_DTO))
+  common.facade.loadUsages.mockReturnValue({})
+
+  await common.controller.load()
+
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.SECTION,
+      params: { id: 'section-1' }
+    },
+    sections: [
+      {
+        id: 'section-0',
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        properties: ['property-1', 'property-2']
+      }
+    ]
+  })
+}
+
+async function testLoadMaintainsPropertySelection() {
+  common.init({
+    id: 'TEST_OBJECT_ID',
+    type: objectTypes.OBJECT_TYPE
+  })
+
+  common.facade.loadType.mockReturnValue(
+    Promise.resolve(new openbis.SampleType())
+  )
+  common.facade.loadUsages.mockReturnValue({})
+
+  await common.controller.load()
+
+  common.controller.handleAddSection()
+  common.controller.handleAddProperty()
+  common.controller.handleAddProperty()
+  common.controller.handleAddSection()
+  common.controller.handleAddProperty()
+  common.controller.handleOrderChange(TypeFormSelectionType.SECTION, {
+    fromIndex: 0,
+    toIndex: 1
+  })
+  common.controller.handleSelectionChange(TypeFormSelectionType.PROPERTY, {
+    id: 'property-1'
+  })
+
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.PROPERTY,
+      params: { id: 'property-1' }
+    },
+    sections: [
+      {
+        id: 'section-1',
+        properties: ['property-2']
+      },
+      {
+        id: 'section-0',
+        properties: ['property-0', 'property-1']
+      }
+    ]
+  })
+
+  common.facade.loadType.mockReturnValue(Promise.resolve(TEST_SAMPLE_TYPE_DTO))
+  common.facade.loadUsages.mockReturnValue({})
+
+  await common.controller.load()
+
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.PROPERTY,
+      params: { id: 'property-2' }
+    },
+    sections: [
+      {
+        id: 'section-0',
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        properties: ['property-1', 'property-2']
+      }
+    ]
+  })
+}
 
 const TEST_PLUGIN_1 = new openbis.Plugin()
 TEST_PLUGIN_1.setName('TEST_PLUGIN_1')
@@ -77,286 +353,3 @@ TEST_SAMPLE_TYPE_DTO.setPropertyAssignments([
   TEST_PROPERTY_ASSIGNMENT_2,
   TEST_PROPERTY_ASSIGNMENT_3
 ])
-
-let context = null
-let facade = null
-let controller = null
-
-beforeEach(() => {
-  jest.resetAllMocks()
-
-  context = new ComponentContext()
-  facade = new TypeFormFacade()
-  controller = new TypeFormControler(facade)
-})
-
-describe('TypeFormController.load', () => {
-  test('successful existing', async () => {
-    facade.loadType.mockReturnValue(Promise.resolve(TEST_SAMPLE_TYPE_DTO))
-    facade.loadUsages.mockReturnValue(
-      Promise.resolve({
-        type: 10,
-        propertyLocal: {
-          [TEST_PROPERTY_TYPE_1_DTO.getCode()]: 1,
-          [TEST_PROPERTY_TYPE_2_DTO.getCode()]: 2
-        },
-        propertyGlobal: {
-          [TEST_PROPERTY_TYPE_1_DTO.getCode()]: 10,
-          [TEST_PROPERTY_TYPE_2_DTO.getCode()]: 20
-        }
-      })
-    )
-
-    context.setProps({
-      object: {
-        id: 'TEST_OBJECT_ID',
-        type: objectTypes.OBJECT_TYPE
-      }
-    })
-
-    controller.init(context)
-    await controller.load()
-
-    expect(context.getState()).toMatchObject({
-      loading: false,
-      selection: null,
-      type: {
-        code: { value: 'TEST_TYPE' },
-        description: { value: 'TEST_DESCRIPTION' },
-        listable: { value: true },
-        showContainer: { value: true },
-        showParents: { value: true },
-        showParentMetadata: { value: true },
-        autoGeneratedCode: { value: true },
-        generatedCodePrefix: { value: 'TEST_PREFIX' },
-        subcodeUnique: { value: true },
-        validationPlugin: { value: 'TEST_PLUGIN_2' },
-        usages: 10
-      },
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: 'TEST_PROPERTY_TYPE_1' },
-          label: { value: 'TEST_LABEL_1' },
-          description: { value: 'TEST_DESCRIPTION_1' },
-          dataType: { value: 'INTEGER' },
-          plugin: { value: 'TEST_PLUGIN_1' },
-          mandatory: { value: true },
-          showInEditView: { value: true },
-          showRawValueInForms: { value: true },
-          usagesLocal: 1,
-          usagesGlobal: 10,
-          section: 'section-0'
-        },
-        {
-          id: 'property-1',
-          code: { value: 'TEST_PROPERTY_TYPE_2' },
-          label: { value: 'TEST_LABEL_2' },
-          description: { value: 'TEST_DESCRIPTION_2' },
-          dataType: { value: 'CONTROLLEDVOCABULARY' },
-          vocabulary: { value: 'TEST_VOCABULARY' },
-          mandatory: { value: true },
-          showInEditView: { value: false },
-          showRawValueInForms: { value: true },
-          usagesLocal: 2,
-          usagesGlobal: 20,
-          section: 'section-1'
-        },
-        {
-          id: 'property-2',
-          code: { value: 'TEST_PROPERTY_TYPE_3' },
-          label: { value: 'TEST_LABEL_3' },
-          description: { value: 'TEST_DESCRIPTION_3' },
-          dataType: { value: 'MATERIAL' },
-          materialType: { value: 'TEST_MATERIAL_TYPE' },
-          mandatory: { value: false },
-          showInEditView: { value: true },
-          showRawValueInForms: { value: false },
-          usagesLocal: 0,
-          usagesGlobal: 0,
-          section: 'section-1'
-        }
-      ],
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
-
-    expect(facade.loadType).toHaveBeenCalledWith(context.getProps().object)
-    expect(facade.loadUsages).toHaveBeenCalledWith(context.getProps().object)
-  })
-
-  test('successful new', async () => {
-    context.setProps({
-      object: {
-        id: 'TEST_OBJECT_ID',
-        type: objectTypes.NEW_OBJECT_TYPE
-      }
-    })
-    controller.init(context)
-    await controller.load()
-
-    expect(context.getState()).toMatchObject({
-      loading: false,
-      selection: null,
-      type: {
-        code: { value: null },
-        description: { value: null },
-        listable: { value: true },
-        showContainer: { value: false },
-        showParents: { value: true },
-        showParentMetadata: { value: false },
-        autoGeneratedCode: { value: true },
-        generatedCodePrefix: { value: null },
-        subcodeUnique: { value: false },
-        validationPlugin: { value: null },
-        usages: 0
-      },
-      properties: [],
-      sections: []
-    })
-  })
-
-  test('maintain section selection', async () => {
-    const emptyType = new openbis.SampleType()
-
-    context.setProps({
-      object: {
-        id: 'TEST_OBJECT_ID',
-        type: objectTypes.OBJECT_TYPE
-      }
-    })
-    controller.init(context)
-
-    facade.loadType.mockReturnValue(Promise.resolve(emptyType))
-    facade.loadUsages.mockReturnValue({})
-    await controller.load()
-
-    controller.handleAddSection()
-    controller.handleAddProperty()
-    controller.handleAddProperty()
-    controller.handleAddSection()
-    controller.handleAddProperty()
-    controller.handleOrderChange(TypeFormSelectionType.SECTION, {
-      fromIndex: 0,
-      toIndex: 1
-    })
-    controller.handleSelectionChange(TypeFormSelectionType.SECTION, {
-      id: 'section-0'
-    })
-
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.SECTION,
-        params: { id: 'section-0' }
-      },
-      sections: [
-        {
-          id: 'section-1',
-          properties: ['property-2']
-        },
-        {
-          id: 'section-0',
-          properties: ['property-0', 'property-1']
-        }
-      ]
-    })
-
-    facade.loadType.mockReturnValue(Promise.resolve(TEST_SAMPLE_TYPE_DTO))
-    facade.loadUsages.mockReturnValue({})
-    await controller.load()
-
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.SECTION,
-        params: { id: 'section-1' }
-      },
-      sections: [
-        {
-          id: 'section-0',
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
-  })
-
-  test('maintain property selection', async () => {
-    const emptyType = new openbis.SampleType()
-
-    context.setProps({
-      object: {
-        id: 'TEST_OBJECT_ID',
-        type: objectTypes.OBJECT_TYPE
-      }
-    })
-    controller.init(context)
-
-    facade.loadType.mockReturnValue(Promise.resolve(emptyType))
-    facade.loadUsages.mockReturnValue({})
-    await controller.load()
-
-    controller.handleAddSection()
-    controller.handleAddProperty()
-    controller.handleAddProperty()
-    controller.handleAddSection()
-    controller.handleAddProperty()
-    controller.handleOrderChange(TypeFormSelectionType.SECTION, {
-      fromIndex: 0,
-      toIndex: 1
-    })
-    controller.handleSelectionChange(TypeFormSelectionType.PROPERTY, {
-      id: 'property-1'
-    })
-
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.PROPERTY,
-        params: { id: 'property-1' }
-      },
-      sections: [
-        {
-          id: 'section-1',
-          properties: ['property-2']
-        },
-        {
-          id: 'section-0',
-          properties: ['property-0', 'property-1']
-        }
-      ]
-    })
-
-    facade.loadType.mockReturnValue(Promise.resolve(TEST_SAMPLE_TYPE_DTO))
-    facade.loadUsages.mockReturnValue({})
-    await controller.load()
-
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.PROPERTY,
-        params: { id: 'property-2' }
-      },
-      sections: [
-        {
-          id: 'section-0',
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
-  })
-})
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerOrderChange.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerOrderChange.test.js
index 92b87fef7af5d70539189bdc5cbc2efbcb835536..ac74ebc5c2849b1a19ffdacd8ebe3d1582920432 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerOrderChange.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerOrderChange.test.js
@@ -1,169 +1,161 @@
-import ComponentContext from '@srcTest/js/components/common/ComponentContext.js'
-import TypeFormControler from '@src/js/components/types/form/TypeFormController.js'
+import TypeFormControllerTest from '@srcTest/js/components/types/form/TypeFormControllerTest.js'
 import TypeFormSelectionType from '@src/js/components/types/form/TypeFormSelectionType.js'
-import TypeFormFacade from '@src/js/components/types/form/TypeFormFacade'
 import objectTypes from '@src/js/common/consts/objectType.js'
 import fixture from '@srcTest/js/common/fixture.js'
 
-jest.mock('@src/js/components/types/form/TypeFormFacade')
-
-let context = null
-let facade = null
-let controller = null
+let common = null
 
 beforeEach(() => {
-  jest.resetAllMocks()
-  context = new ComponentContext()
-  context.setProps({
-    object: {
-      id: 'TEST_OBJECT_ID',
-      type: objectTypes.OBJECT_TYPE
-    }
+  common = new TypeFormControllerTest()
+  common.beforeEach()
+  common.init({
+    id: 'TEST_OBJECT_ID',
+    type: objectTypes.OBJECT_TYPE
   })
-  facade = new TypeFormFacade()
-  controller = new TypeFormControler(facade)
-  controller.init(context)
 })
 
 afterEach(() => {
-  expect(facade.loadType).toHaveBeenCalledWith(context.getProps().object)
-  expect(facade.loadUsages).toHaveBeenCalledWith(context.getProps().object)
+  common.afterEach()
+})
+
+describe(TypeFormControllerTest.SUITE, () => {
+  test('move section', testMoveSection)
+  test('move property within section', testMovePropertyWithinSection)
+  test('move property between sections', testMovePropertyBetweenSections)
 })
 
-describe('TypeFormController.handleOrderChange', () => {
-  test('section', async () => {
-    facade.loadType.mockReturnValue(
-      Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
-    )
-    facade.loadUsages.mockReturnValue(Promise.resolve({}))
-
-    await controller.load()
-
-    expect(context.getState()).toMatchObject({
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
-
-    controller.handleOrderChange(TypeFormSelectionType.SECTION, {
-      fromIndex: 0,
-      toIndex: 1
-    })
-
-    expect(context.getState()).toMatchObject({
-      sections: [
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        },
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        }
-      ]
-    })
+async function testMoveSection() {
+  common.facade.loadType.mockReturnValue(
+    Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
+  )
+  common.facade.loadUsages.mockReturnValue(Promise.resolve({}))
+
+  await common.controller.load()
+
+  expect(common.context.getState()).toMatchObject({
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
   })
 
-  test('property within section', async () => {
-    facade.loadType.mockReturnValue(
-      Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
-    )
-    facade.loadUsages.mockReturnValue(Promise.resolve({}))
-
-    await controller.load()
-
-    expect(context.getState()).toMatchObject({
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
-
-    controller.handleOrderChange(TypeFormSelectionType.PROPERTY, {
-      fromSectionId: 'section-1',
-      toSectionId: 'section-1',
-      fromIndex: 0,
-      toIndex: 1
-    })
-
-    expect(context.getState()).toMatchObject({
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-2', 'property-1']
-        }
-      ]
-    })
+  common.controller.handleOrderChange(TypeFormSelectionType.SECTION, {
+    fromIndex: 0,
+    toIndex: 1
   })
 
-  test('property between sections', async () => {
-    facade.loadType.mockReturnValue(
-      Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
-    )
-    facade.loadUsages.mockReturnValue(Promise.resolve({}))
-
-    await controller.load()
-
-    expect(context.getState()).toMatchObject({
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
-
-    controller.handleOrderChange(TypeFormSelectionType.PROPERTY, {
-      fromSectionId: 'section-1',
-      toSectionId: 'section-0',
-      fromIndex: 1,
-      toIndex: 0
-    })
-
-    expect(context.getState()).toMatchObject({
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-2', 'property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1']
-        }
-      ]
-    })
+  expect(common.context.getState()).toMatchObject({
+    sections: [
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      },
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      }
+    ]
   })
-})
+}
+
+async function testMovePropertyWithinSection() {
+  common.facade.loadType.mockReturnValue(
+    Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
+  )
+  common.facade.loadUsages.mockReturnValue(Promise.resolve({}))
+
+  await common.controller.load()
+
+  expect(common.context.getState()).toMatchObject({
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
+  })
+
+  common.controller.handleOrderChange(TypeFormSelectionType.PROPERTY, {
+    fromSectionId: 'section-1',
+    toSectionId: 'section-1',
+    fromIndex: 0,
+    toIndex: 1
+  })
+
+  expect(common.context.getState()).toMatchObject({
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-2', 'property-1']
+      }
+    ]
+  })
+}
+
+async function testMovePropertyBetweenSections() {
+  common.facade.loadType.mockReturnValue(
+    Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
+  )
+  common.facade.loadUsages.mockReturnValue(Promise.resolve({}))
+
+  await common.controller.load()
+
+  expect(common.context.getState()).toMatchObject({
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
+  })
+
+  common.controller.handleOrderChange(TypeFormSelectionType.PROPERTY, {
+    fromSectionId: 'section-1',
+    toSectionId: 'section-0',
+    fromIndex: 1,
+    toIndex: 0
+  })
+
+  expect(common.context.getState()).toMatchObject({
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-2', 'property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1']
+      }
+    ]
+  })
+}
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerRemove.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerRemove.test.js
index 14b925509d1138ced2ea757521e22cf29406f59e..ba9ed49bf1821d1949cc51a537e3be70b283988a 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerRemove.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerRemove.test.js
@@ -1,703 +1,698 @@
-import ComponentContext from '@srcTest/js/components/common/ComponentContext.js'
-import TypeFormControler from '@src/js/components/types/form/TypeFormController.js'
+import TypeFormControllerTest from '@srcTest/js/components/types/form/TypeFormControllerTest.js'
 import TypeFormSelectionType from '@src/js/components/types/form/TypeFormSelectionType.js'
-import TypeFormFacade from '@src/js/components/types/form/TypeFormFacade'
 import objectTypes from '@src/js/common/consts/objectType.js'
 import fixture from '@srcTest/js/common/fixture.js'
 
-jest.mock('@src/js/components/types/form/TypeFormFacade')
-
-let context = null
-let facade = null
-let controller = null
+let common = null
 
 beforeEach(() => {
-  jest.resetAllMocks()
-  context = new ComponentContext()
-  context.setProps({
-    object: {
-      id: 'TEST_OBJECT_ID',
-      type: objectTypes.OBJECT_TYPE
-    }
+  common = new TypeFormControllerTest()
+  common.beforeEach()
+  common.init({
+    id: 'TEST_OBJECT_ID',
+    type: objectTypes.OBJECT_TYPE
   })
-  facade = new TypeFormFacade()
-  controller = new TypeFormControler(facade)
-  controller.init(context)
 })
 
 afterEach(() => {
-  expect(facade.loadType).toHaveBeenCalledWith(context.getProps().object)
-  expect(facade.loadUsages).toHaveBeenCalledWith(context.getProps().object)
+  common.afterEach()
 })
 
-describe('TypeFormController.handleRemove', () => {
-  test('section not used', async () => {
-    facade.loadType.mockReturnValue(
-      Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
-    )
-    facade.loadUsages.mockReturnValue(Promise.resolve({}))
+describe(TypeFormControllerTest.SUITE, () => {
+  test('remove section not used', testRemoveSectionNotUsed)
+  test('remove section used and confirmed', testRemoveSectionUsedAndConfirmed)
+  test('remove section used and cancelled', testRemoveSectionUsedAndCancelled)
+  test('remove property not used', testRemovePropertyNotUsed)
+  test('remove property used and confirmed', testRemovePropertyUsedAndConfirmed)
+  test('remove property used and cancelled', testRemovePropertyUsedAndCancelled)
+})
 
-    await controller.load()
-    controller.handleSelectionChange(TypeFormSelectionType.SECTION, {
-      id: 'section-0'
-    })
+async function testRemoveSectionNotUsed() {
+  common.facade.loadType.mockReturnValue(
+    Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
+  )
+  common.facade.loadUsages.mockReturnValue(Promise.resolve({}))
 
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.SECTION,
-        params: {
-          id: 'section-0'
-        }
-      },
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
-        },
-        {
-          id: 'property-1',
-          code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        }
-      ],
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
+  await common.controller.load()
+  common.controller.handleSelectionChange(TypeFormSelectionType.SECTION, {
+    id: 'section-0'
+  })
 
-    controller.handleRemove()
-
-    expect(context.getState()).toMatchObject({
-      selection: null,
-      properties: [
-        {
-          id: 'property-1',
-          code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        }
-      ],
-      sections: [
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.SECTION,
+      params: {
+        id: 'section-0'
+      }
+    },
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
+      },
+      {
+        id: 'property-1',
+        code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
+      },
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
+      }
+    ],
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
   })
 
-  test('section used and confirmed', async () => {
-    facade.loadType.mockReturnValue(
-      Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
-    )
-    facade.loadUsages.mockReturnValue(
-      Promise.resolve({
-        propertyLocal: {
-          [fixture.TEST_PROPERTY_TYPE_1_DTO.getCode()]: 1
-        },
-        propertyGlobal: {
-          [fixture.TEST_PROPERTY_TYPE_1_DTO.getCode()]: 10
-        }
-      })
-    )
-
-    await controller.load()
-    controller.handleSelectionChange(TypeFormSelectionType.SECTION, {
-      id: 'section-0'
-    })
+  common.controller.handleRemove()
 
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.SECTION,
-        params: {
-          id: 'section-0'
-        }
-      },
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() },
-          usagesLocal: 1,
-          usagesGlobal: 10
-        },
-        {
-          id: 'property-1',
-          code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        }
-      ],
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
+  expect(common.context.getState()).toMatchObject({
+    selection: null,
+    properties: [
+      {
+        id: 'property-1',
+        code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
+      },
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
+      }
+    ],
+    sections: [
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
+  })
+}
+
+async function testRemoveSectionUsedAndConfirmed() {
+  common.facade.loadType.mockReturnValue(
+    Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
+  )
+  common.facade.loadUsages.mockReturnValue(
+    Promise.resolve({
+      propertyLocal: {
+        [fixture.TEST_PROPERTY_TYPE_1_DTO.getCode()]: 1
+      },
+      propertyGlobal: {
+        [fixture.TEST_PROPERTY_TYPE_1_DTO.getCode()]: 10
+      }
     })
+  )
 
-    controller.handleRemove()
-
-    expect(context.getState()).toMatchObject({
-      removeSectionDialogOpen: true,
-      selection: {
-        type: TypeFormSelectionType.SECTION,
-        params: {
-          id: 'section-0'
-        }
-      },
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() },
-          usagesLocal: 1,
-          usagesGlobal: 10
-        },
-        {
-          id: 'property-1',
-          code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        }
-      ],
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
+  await common.controller.load()
+  common.controller.handleSelectionChange(TypeFormSelectionType.SECTION, {
+    id: 'section-0'
+  })
 
-    controller.handleRemoveConfirm()
-
-    expect(context.getState()).toMatchObject({
-      removeSectionDialogOpen: false,
-      selection: null,
-      properties: [
-        {
-          id: 'property-1',
-          code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        }
-      ],
-      sections: [
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.SECTION,
+      params: {
+        id: 'section-0'
+      }
+    },
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() },
+        usagesLocal: 1,
+        usagesGlobal: 10
+      },
+      {
+        id: 'property-1',
+        code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
+      },
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
+      }
+    ],
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
   })
 
-  test('section used and cancelled', async () => {
-    facade.loadType.mockReturnValue(
-      Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
-    )
-    facade.loadUsages.mockReturnValue(
-      Promise.resolve({
-        propertyLocal: {
-          [fixture.TEST_PROPERTY_TYPE_1_DTO.getCode()]: 1
-        },
-        propertyGlobal: {
-          [fixture.TEST_PROPERTY_TYPE_1_DTO.getCode()]: 10
-        }
-      })
-    )
-
-    await controller.load()
-    controller.handleSelectionChange(TypeFormSelectionType.SECTION, {
-      id: 'section-0'
-    })
+  common.controller.handleRemove()
+
+  expect(common.context.getState()).toMatchObject({
+    removeSectionDialogOpen: true,
+    selection: {
+      type: TypeFormSelectionType.SECTION,
+      params: {
+        id: 'section-0'
+      }
+    },
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() },
+        usagesLocal: 1,
+        usagesGlobal: 10
+      },
+      {
+        id: 'property-1',
+        code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
+      },
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
+      }
+    ],
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
+  })
 
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.SECTION,
-        params: {
-          id: 'section-0'
-        }
-      },
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() },
-          usagesLocal: 1,
-          usagesGlobal: 10
-        },
-        {
-          id: 'property-1',
-          code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        }
-      ],
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
+  common.controller.handleRemoveConfirm()
 
-    controller.handleRemove()
-
-    expect(context.getState()).toMatchObject({
-      removeSectionDialogOpen: true,
-      selection: {
-        type: TypeFormSelectionType.SECTION,
-        params: {
-          id: 'section-0'
-        }
-      },
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() },
-          usagesLocal: 1,
-          usagesGlobal: 10
-        },
-        {
-          id: 'property-1',
-          code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        }
-      ],
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
+  expect(common.context.getState()).toMatchObject({
+    removeSectionDialogOpen: false,
+    selection: null,
+    properties: [
+      {
+        id: 'property-1',
+        code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
+      },
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
+      }
+    ],
+    sections: [
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
+  })
+}
+
+async function testRemoveSectionUsedAndCancelled() {
+  common.facade.loadType.mockReturnValue(
+    Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
+  )
+  common.facade.loadUsages.mockReturnValue(
+    Promise.resolve({
+      propertyLocal: {
+        [fixture.TEST_PROPERTY_TYPE_1_DTO.getCode()]: 1
+      },
+      propertyGlobal: {
+        [fixture.TEST_PROPERTY_TYPE_1_DTO.getCode()]: 10
+      }
     })
+  )
 
-    controller.handleRemoveCancel()
-
-    expect(context.getState()).toMatchObject({
-      removeSectionDialogOpen: false,
-      selection: {
-        type: TypeFormSelectionType.SECTION,
-        params: {
-          id: 'section-0'
-        }
-      },
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() },
-          usagesLocal: 1,
-          usagesGlobal: 10
-        },
-        {
-          id: 'property-1',
-          code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        }
-      ],
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
+  await common.controller.load()
+  common.controller.handleSelectionChange(TypeFormSelectionType.SECTION, {
+    id: 'section-0'
   })
 
-  test('property not used', async () => {
-    facade.loadType.mockReturnValue(
-      Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
-    )
-    facade.loadUsages.mockReturnValue(Promise.resolve({}))
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.SECTION,
+      params: {
+        id: 'section-0'
+      }
+    },
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() },
+        usagesLocal: 1,
+        usagesGlobal: 10
+      },
+      {
+        id: 'property-1',
+        code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
+      },
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
+      }
+    ],
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
+  })
 
-    await controller.load()
-    controller.handleSelectionChange(TypeFormSelectionType.PROPERTY, {
-      id: 'property-1'
-    })
+  common.controller.handleRemove()
+
+  expect(common.context.getState()).toMatchObject({
+    removeSectionDialogOpen: true,
+    selection: {
+      type: TypeFormSelectionType.SECTION,
+      params: {
+        id: 'section-0'
+      }
+    },
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() },
+        usagesLocal: 1,
+        usagesGlobal: 10
+      },
+      {
+        id: 'property-1',
+        code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
+      },
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
+      }
+    ],
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
+  })
 
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.PROPERTY,
-        params: {
-          id: 'property-1'
-        }
-      },
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
-        },
-        {
-          id: 'property-1',
-          code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        }
-      ],
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
+  common.controller.handleRemoveCancel()
+
+  expect(common.context.getState()).toMatchObject({
+    removeSectionDialogOpen: false,
+    selection: {
+      type: TypeFormSelectionType.SECTION,
+      params: {
+        id: 'section-0'
+      }
+    },
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() },
+        usagesLocal: 1,
+        usagesGlobal: 10
+      },
+      {
+        id: 'property-1',
+        code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
+      },
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
+      }
+    ],
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
+  })
+}
 
-    controller.handleRemove()
-
-    expect(context.getState()).toMatchObject({
-      selection: null,
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        }
-      ],
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-2']
-        }
-      ]
-    })
+async function testRemovePropertyNotUsed() {
+  common.facade.loadType.mockReturnValue(
+    Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
+  )
+  common.facade.loadUsages.mockReturnValue(Promise.resolve({}))
+
+  await common.controller.load()
+  common.controller.handleSelectionChange(TypeFormSelectionType.PROPERTY, {
+    id: 'property-1'
   })
 
-  test('property used and confirmed', async () => {
-    facade.loadType.mockReturnValue(
-      Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
-    )
-    facade.loadUsages.mockReturnValue(
-      Promise.resolve({
-        propertyLocal: {
-          [fixture.TEST_PROPERTY_TYPE_2_DTO.getCode()]: 1
-        },
-        propertyGlobal: {
-          [fixture.TEST_PROPERTY_TYPE_2_DTO.getCode()]: 10
-        }
-      })
-    )
-
-    await controller.load()
-    controller.handleSelectionChange(TypeFormSelectionType.PROPERTY, {
-      id: 'property-1'
-    })
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.PROPERTY,
+      params: {
+        id: 'property-1'
+      }
+    },
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
+      },
+      {
+        id: 'property-1',
+        code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() }
+      },
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
+      }
+    ],
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
+  })
 
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.PROPERTY,
-        params: {
-          id: 'property-1'
-        }
-      },
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
-        },
-        {
-          id: 'property-1',
-          code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() },
-          usagesLocal: 1,
-          usagesGlobal: 10
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        }
-      ],
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
+  common.controller.handleRemove()
 
-    controller.handleRemove()
-
-    expect(context.getState()).toMatchObject({
-      removePropertyDialogOpen: true,
-      selection: {
-        type: TypeFormSelectionType.PROPERTY,
-        params: {
-          id: 'property-1'
-        }
-      },
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
-        },
-        {
-          id: 'property-1',
-          code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() },
-          usagesLocal: 1,
-          usagesGlobal: 10
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        }
-      ],
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
+  expect(common.context.getState()).toMatchObject({
+    selection: null,
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
+      },
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
+      }
+    ],
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-2']
+      }
+    ]
+  })
+}
+
+async function testRemovePropertyUsedAndConfirmed() {
+  common.facade.loadType.mockReturnValue(
+    Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
+  )
+  common.facade.loadUsages.mockReturnValue(
+    Promise.resolve({
+      propertyLocal: {
+        [fixture.TEST_PROPERTY_TYPE_2_DTO.getCode()]: 1
+      },
+      propertyGlobal: {
+        [fixture.TEST_PROPERTY_TYPE_2_DTO.getCode()]: 10
+      }
     })
+  )
 
-    controller.handleRemoveConfirm()
-
-    expect(context.getState()).toMatchObject({
-      removePropertyDialogOpen: false,
-      selection: null,
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        }
-      ],
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-2']
-        }
-      ]
-    })
+  await common.controller.load()
+  common.controller.handleSelectionChange(TypeFormSelectionType.PROPERTY, {
+    id: 'property-1'
   })
 
-  test('property used and cancelled', async () => {
-    facade.loadType.mockReturnValue(
-      Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
-    )
-    facade.loadUsages.mockReturnValue(
-      Promise.resolve({
-        propertyLocal: {
-          [fixture.TEST_PROPERTY_TYPE_2_DTO.getCode()]: 1
-        },
-        propertyGlobal: {
-          [fixture.TEST_PROPERTY_TYPE_2_DTO.getCode()]: 10
-        }
-      })
-    )
-
-    await controller.load()
-    controller.handleSelectionChange(TypeFormSelectionType.PROPERTY, {
-      id: 'property-1'
-    })
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.PROPERTY,
+      params: {
+        id: 'property-1'
+      }
+    },
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
+      },
+      {
+        id: 'property-1',
+        code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() },
+        usagesLocal: 1,
+        usagesGlobal: 10
+      },
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
+      }
+    ],
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
+  })
 
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.PROPERTY,
-        params: {
-          id: 'property-1'
-        }
-      },
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
-        },
-        {
-          id: 'property-1',
-          code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() },
-          usagesLocal: 1,
-          usagesGlobal: 10
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        }
-      ],
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
+  common.controller.handleRemove()
+
+  expect(common.context.getState()).toMatchObject({
+    removePropertyDialogOpen: true,
+    selection: {
+      type: TypeFormSelectionType.PROPERTY,
+      params: {
+        id: 'property-1'
+      }
+    },
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
+      },
+      {
+        id: 'property-1',
+        code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() },
+        usagesLocal: 1,
+        usagesGlobal: 10
+      },
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
+      }
+    ],
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
+  })
 
-    controller.handleRemove()
-
-    expect(context.getState()).toMatchObject({
-      removePropertyDialogOpen: true,
-      selection: {
-        type: TypeFormSelectionType.PROPERTY,
-        params: {
-          id: 'property-1'
-        }
-      },
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
-        },
-        {
-          id: 'property-1',
-          code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() },
-          usagesLocal: 1,
-          usagesGlobal: 10
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        }
-      ],
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
-    })
+  common.controller.handleRemoveConfirm()
 
-    controller.handleRemoveCancel()
-
-    expect(context.getState()).toMatchObject({
-      removePropertyDialogOpen: false,
-      selection: {
-        type: TypeFormSelectionType.PROPERTY,
-        params: {
-          id: 'property-1'
-        }
-      },
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
-        },
-        {
-          id: 'property-1',
-          code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() },
-          usagesLocal: 1,
-          usagesGlobal: 10
-        },
-        {
-          id: 'property-2',
-          code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
-        }
-      ],
-      sections: [
-        {
-          id: 'section-0',
-          name: { value: 'TEST_SECTION_1' },
-          properties: ['property-0']
-        },
-        {
-          id: 'section-1',
-          name: { value: 'TEST_SECTION_2' },
-          properties: ['property-1', 'property-2']
-        }
-      ]
+  expect(common.context.getState()).toMatchObject({
+    removePropertyDialogOpen: false,
+    selection: null,
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
+      },
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
+      }
+    ],
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-2']
+      }
+    ]
+  })
+}
+
+async function testRemovePropertyUsedAndCancelled() {
+  common.facade.loadType.mockReturnValue(
+    Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
+  )
+  common.facade.loadUsages.mockReturnValue(
+    Promise.resolve({
+      propertyLocal: {
+        [fixture.TEST_PROPERTY_TYPE_2_DTO.getCode()]: 1
+      },
+      propertyGlobal: {
+        [fixture.TEST_PROPERTY_TYPE_2_DTO.getCode()]: 10
+      }
     })
+  )
+
+  await common.controller.load()
+  common.controller.handleSelectionChange(TypeFormSelectionType.PROPERTY, {
+    id: 'property-1'
   })
-})
+
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.PROPERTY,
+      params: {
+        id: 'property-1'
+      }
+    },
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
+      },
+      {
+        id: 'property-1',
+        code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() },
+        usagesLocal: 1,
+        usagesGlobal: 10
+      },
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
+      }
+    ],
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
+  })
+
+  common.controller.handleRemove()
+
+  expect(common.context.getState()).toMatchObject({
+    removePropertyDialogOpen: true,
+    selection: {
+      type: TypeFormSelectionType.PROPERTY,
+      params: {
+        id: 'property-1'
+      }
+    },
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
+      },
+      {
+        id: 'property-1',
+        code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() },
+        usagesLocal: 1,
+        usagesGlobal: 10
+      },
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
+      }
+    ],
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
+  })
+
+  common.controller.handleRemoveCancel()
+
+  expect(common.context.getState()).toMatchObject({
+    removePropertyDialogOpen: false,
+    selection: {
+      type: TypeFormSelectionType.PROPERTY,
+      params: {
+        id: 'property-1'
+      }
+    },
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: fixture.TEST_PROPERTY_TYPE_1_DTO.getCode() }
+      },
+      {
+        id: 'property-1',
+        code: { value: fixture.TEST_PROPERTY_TYPE_2_DTO.getCode() },
+        usagesLocal: 1,
+        usagesGlobal: 10
+      },
+      {
+        id: 'property-2',
+        code: { value: fixture.TEST_PROPERTY_TYPE_3_DTO.getCode() }
+      }
+    ],
+    sections: [
+      {
+        id: 'section-0',
+        name: { value: 'TEST_SECTION_1' },
+        properties: ['property-0']
+      },
+      {
+        id: 'section-1',
+        name: { value: 'TEST_SECTION_2' },
+        properties: ['property-1', 'property-2']
+      }
+    ]
+  })
+}
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerSave.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerSave.test.js
index 9b442c6b76ba5ebd9bd8a55219ec2cf3260ad806..602803bfdb5d3bdfab9a2d58175afda919eeac2e 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerSave.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerSave.test.js
@@ -1,440 +1,389 @@
-import ComponentContext from '@srcTest/js/components/common/ComponentContext.js'
-import TypeFormControler from '@src/js/components/types/form/TypeFormController.js'
+import TypeFormControllerTest from '@srcTest/js/components/types/form/TypeFormControllerTest.js'
 import TypeFormSelectionType from '@src/js/components/types/form/TypeFormSelectionType.js'
-import TypeFormFacade from '@src/js/components/types/form/TypeFormFacade'
 import objectTypes from '@src/js/common/consts/objectType.js'
 import openbis from '@srcTest/js/services/openbis.js'
 
-jest.mock('@src/js/components/types/form/TypeFormFacade')
-
-const LOCAL_PROPERTY_TYPE = new openbis.PropertyType()
-LOCAL_PROPERTY_TYPE.setCode('TEST_TYPE.TEST_PROPERTY_TYPE')
-LOCAL_PROPERTY_TYPE.setLabel('TEST_LABEL')
-LOCAL_PROPERTY_TYPE.setDescription('TEST_DESCRIPTION')
-LOCAL_PROPERTY_TYPE.setDataType(openbis.DataType.INTEGER)
-
-const LOCAL_PROPERTY_ASSIGNMENT = new openbis.PropertyAssignment()
-LOCAL_PROPERTY_ASSIGNMENT.setPropertyType(LOCAL_PROPERTY_TYPE)
-
-const SAMPLE_TYPE_WITH_LOCAL_PROPERTY = new openbis.SampleType()
-SAMPLE_TYPE_WITH_LOCAL_PROPERTY.setCode('TEST_TYPE')
-SAMPLE_TYPE_WITH_LOCAL_PROPERTY.setGeneratedCodePrefix('TEST_PREFIX')
-SAMPLE_TYPE_WITH_LOCAL_PROPERTY.setPropertyAssignments([
-  LOCAL_PROPERTY_ASSIGNMENT
-])
-
-const GLOBAL_PROPERTY_TYPE = new openbis.PropertyType()
-GLOBAL_PROPERTY_TYPE.setCode('TEST_PROPERTY_TYPE')
-GLOBAL_PROPERTY_TYPE.setLabel('TEST_LABEL')
-GLOBAL_PROPERTY_TYPE.setDescription('TEST_DESCRIPTION')
-GLOBAL_PROPERTY_TYPE.setDataType(openbis.DataType.INTEGER)
-
-const GLOBAL_PROPERTY_ASSIGNMENT = new openbis.PropertyAssignment()
-GLOBAL_PROPERTY_ASSIGNMENT.setPropertyType(GLOBAL_PROPERTY_TYPE)
-
-const SAMPLE_TYPE_WITH_GLOBAL_PROPERTY = new openbis.SampleType()
-SAMPLE_TYPE_WITH_GLOBAL_PROPERTY.setCode('TEST_TYPE')
-SAMPLE_TYPE_WITH_GLOBAL_PROPERTY.setGeneratedCodePrefix('TEST_PREFIX')
-SAMPLE_TYPE_WITH_GLOBAL_PROPERTY.setPropertyAssignments([
-  GLOBAL_PROPERTY_ASSIGNMENT
-])
-
-let context = null
-let facade = null
-let controller = null
+let common = null
 
 beforeEach(() => {
-  jest.resetAllMocks()
-  context = new ComponentContext()
-  context.setProps({
-    object: {
-      id: 'TEST_OBJECT_ID',
-      type: objectTypes.OBJECT_TYPE
-    }
+  common = new TypeFormControllerTest()
+  common.beforeEach()
+  common.init({
+    id: 'TEST_OBJECT_ID',
+    type: objectTypes.OBJECT_TYPE
   })
-  facade = new TypeFormFacade()
-  controller = new TypeFormControler(facade)
-  controller.init(context)
 })
 
 afterEach(() => {
-  expect(facade.loadType).toHaveBeenCalledWith(context.getProps().object)
-  expect(facade.loadUsages).toHaveBeenCalledWith(context.getProps().object)
+  common.afterEach()
 })
 
-describe('TypeFormController.handleSave', () => {
-  test('validation', async () => {
-    const SAMPLE_TYPE = new openbis.SampleType()
-
-    facade.loadType.mockReturnValue(Promise.resolve(SAMPLE_TYPE))
-    facade.loadUsages.mockReturnValue(Promise.resolve({}))
-
-    await controller.load()
-    controller.handleAddSection()
-    controller.handleAddProperty()
-
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.PROPERTY,
-        params: {
-          id: 'property-0'
-        }
-      },
-      type: {
-        errors: 0
-      },
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: null },
-          dataType: { value: null },
-          label: { value: null },
-          description: { value: null },
-          errors: 0
-        }
-      ]
-    })
+describe(TypeFormControllerTest.SUITE, () => {
+  test('save add local property', testSaveAddLocalProperty)
+  test('save add global property', testSaveAddGlobalProperty)
+  test(
+    'save update local property assignment',
+    testSaveUpdateLocalPropertyAssignment
+  )
+  test(
+    'save update global property assignment',
+    testSaveUpdateGlobalPropertyAssignment
+  )
+  test(
+    'save update local property type if possible',
+    testSaveUpdateLocalPropertyTypeIfPossible
+  )
+  test(
+    'save update global property type if possible',
+    testSaveUpdateGlobalPropertyTypeIfPossible
+  )
+  test('save delete local property', testSaveDeleteLocalProperty)
+  test('save delete global property', testSaveDeleteGlobalProperty)
+  test(
+    'save delete local property last assignment',
+    testSaveDeleteLocalPropertyLastAssignment
+  )
+  test(
+    'save delete global property last assignment',
+    testSaveDeleteGlobalPropertyLastAssignment
+  )
+})
 
-    await controller.handleSave()
-
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.TYPE,
-        params: {
-          part: 'code'
-        }
-      },
-      type: {
-        code: {
-          error: 'Code cannot be empty'
-        },
-        generatedCodePrefix: {
-          error: 'Generated code prefix cannot be empty'
-        },
-        errors: 2
-      },
-      properties: [
-        {
-          id: 'property-0',
-          code: { value: null, error: 'Code cannot be empty' },
-          dataType: { value: null, error: 'Data Type cannot be empty' },
-          label: { value: null, error: 'Label cannot be empty' },
-          description: { value: null, error: 'Description cannot be empty' },
-          errors: 4
-        }
-      ]
-    })
+async function testSaveAddLocalProperty() {
+  await doTestAddProperty('local')
+}
+
+async function testSaveAddGlobalProperty() {
+  await doTestAddProperty('global')
+}
+
+async function testSaveUpdateLocalPropertyAssignment() {
+  await doTestUpdatePropertyAssignment(
+    SAMPLE_TYPE_WITH_LOCAL_PROPERTY,
+    LOCAL_PROPERTY_TYPE
+  )
+}
+
+async function testSaveUpdateGlobalPropertyAssignment() {
+  await doTestUpdatePropertyAssignment(
+    SAMPLE_TYPE_WITH_GLOBAL_PROPERTY,
+    GLOBAL_PROPERTY_TYPE
+  )
+}
+
+async function testSaveUpdateLocalPropertyTypeIfPossible() {
+  await doTestUpdatePropertyTypeIfPossible(
+    SAMPLE_TYPE_WITH_LOCAL_PROPERTY,
+    LOCAL_PROPERTY_TYPE,
+    LOCAL_PROPERTY_ASSIGNMENT
+  )
+}
+
+async function testSaveUpdateGlobalPropertyTypeIfPossible() {
+  await doTestUpdatePropertyTypeIfPossible(
+    SAMPLE_TYPE_WITH_GLOBAL_PROPERTY,
+    GLOBAL_PROPERTY_TYPE,
+    GLOBAL_PROPERTY_ASSIGNMENT
+  )
+}
+
+async function testSaveDeleteLocalProperty() {
+  await doTestDeleteProperty(
+    SAMPLE_TYPE_WITH_LOCAL_PROPERTY,
+    LOCAL_PROPERTY_TYPE
+  )
+}
+
+async function testSaveDeleteGlobalProperty() {
+  await doTestDeleteProperty(
+    SAMPLE_TYPE_WITH_GLOBAL_PROPERTY,
+    GLOBAL_PROPERTY_TYPE
+  )
+}
+
+async function testSaveDeleteLocalPropertyLastAssignment() {
+  await doTestDeletePropertyLastAssignment(
+    SAMPLE_TYPE_WITH_LOCAL_PROPERTY,
+    LOCAL_PROPERTY_TYPE
+  )
+}
+
+async function testSaveDeleteGlobalPropertyLastAssignment() {
+  await doTestDeletePropertyLastAssignment(
+    SAMPLE_TYPE_WITH_GLOBAL_PROPERTY,
+    GLOBAL_PROPERTY_TYPE
+  )
+}
+
+async function doTestAddProperty(scope) {
+  const SAMPLE_TYPE = new openbis.SampleType()
+  SAMPLE_TYPE.setCode('TEST_TYPE')
+  SAMPLE_TYPE.setGeneratedCodePrefix('TEST_PREFIX')
+
+  common.facade.loadType.mockReturnValue(Promise.resolve(SAMPLE_TYPE))
+  common.facade.loadUsages.mockReturnValue(Promise.resolve({}))
+  common.facade.loadGlobalPropertyTypes.mockReturnValue(Promise.resolve([]))
+  common.facade.executeOperations.mockReturnValue(Promise.resolve({}))
+
+  await common.controller.load()
+
+  common.controller.handleAddSection()
+  common.controller.handleAddProperty()
+
+  common.controller.handleChange(TypeFormSelectionType.PROPERTY, {
+    id: 'property-0',
+    field: 'scope',
+    value: scope
   })
-
-  test('add local property', async () => {
-    await testAddProperty('local')
+  common.controller.handleChange(TypeFormSelectionType.PROPERTY, {
+    id: 'property-0',
+    field: 'code',
+    value: 'NEW_CODE'
   })
-
-  test('add global property', async () => {
-    await testAddProperty('global')
+  common.controller.handleChange(TypeFormSelectionType.PROPERTY, {
+    id: 'property-0',
+    field: 'dataType',
+    value: 'VARCHAR'
   })
-
-  test('update local property assignment', async () => {
-    await testUpdatePropertyAssignment(
-      SAMPLE_TYPE_WITH_LOCAL_PROPERTY,
-      LOCAL_PROPERTY_TYPE
-    )
+  common.controller.handleChange(TypeFormSelectionType.PROPERTY, {
+    id: 'property-0',
+    field: 'label',
+    value: 'NEW_LABEL'
   })
-
-  test('update global property assignment', async () => {
-    await testUpdatePropertyAssignment(
-      SAMPLE_TYPE_WITH_GLOBAL_PROPERTY,
-      GLOBAL_PROPERTY_TYPE
-    )
+  common.controller.handleChange(TypeFormSelectionType.PROPERTY, {
+    id: 'property-0',
+    field: 'description',
+    value: 'NEW_DESCRIPTION'
   })
 
-  test('update local property type if possible', async () => {
-    await testUpdatePropertyTypeIfPossible(
-      SAMPLE_TYPE_WITH_LOCAL_PROPERTY,
-      LOCAL_PROPERTY_TYPE,
-      LOCAL_PROPERTY_ASSIGNMENT
-    )
-  })
+  await common.controller.handleSave()
 
-  test('update global property type if possible', async () => {
-    await testUpdatePropertyTypeIfPossible(
-      SAMPLE_TYPE_WITH_GLOBAL_PROPERTY,
-      GLOBAL_PROPERTY_TYPE,
-      GLOBAL_PROPERTY_ASSIGNMENT
-    )
-  })
+  const propertyTypeCode = scope === 'local' ? 'TEST_TYPE.NEW_CODE' : 'NEW_CODE'
 
-  test('delete local property', async () => {
-    await testDeleteProperty(
-      SAMPLE_TYPE_WITH_LOCAL_PROPERTY,
-      LOCAL_PROPERTY_TYPE
+  expectExecuteOperations([
+    createPropertyTypeOperation({
+      code: propertyTypeCode,
+      dataType: openbis.DataType.VARCHAR,
+      label: 'NEW_LABEL'
+    }),
+    setPropertyAssignmentOperation(
+      SAMPLE_TYPE.getCode(),
+      propertyTypeCode,
+      false
     )
-  })
+  ])
+}
 
-  test('delete global property', async () => {
-    await testDeleteProperty(
-      SAMPLE_TYPE_WITH_GLOBAL_PROPERTY,
-      GLOBAL_PROPERTY_TYPE
-    )
-  })
+async function doTestUpdatePropertyAssignment(type, propertyType) {
+  common.facade.loadType.mockReturnValue(Promise.resolve(type))
+  common.facade.loadUsages.mockReturnValue(Promise.resolve({}))
+  common.facade.executeOperations.mockReturnValue(Promise.resolve({}))
 
-  test('delete local property last assignment', async () => {
-    await testDeletePropertyLastAssignment(
-      SAMPLE_TYPE_WITH_LOCAL_PROPERTY,
-      LOCAL_PROPERTY_TYPE
-    )
-  })
+  await common.controller.load()
 
-  test('delete global property last assignment', async () => {
-    await testDeletePropertyLastAssignment(
-      SAMPLE_TYPE_WITH_GLOBAL_PROPERTY,
-      GLOBAL_PROPERTY_TYPE
-    )
+  common.controller.handleChange(TypeFormSelectionType.PROPERTY, {
+    id: 'property-0',
+    field: 'mandatory',
+    value: true
   })
 
-  async function testAddProperty(scope) {
-    const SAMPLE_TYPE = new openbis.SampleType()
-    SAMPLE_TYPE.setCode('TEST_TYPE')
-    SAMPLE_TYPE.setGeneratedCodePrefix('TEST_PREFIX')
-
-    facade.loadType.mockReturnValue(Promise.resolve(SAMPLE_TYPE))
-    facade.loadUsages.mockReturnValue(Promise.resolve({}))
-    facade.loadGlobalPropertyTypes.mockReturnValue(Promise.resolve([]))
-    facade.executeOperations.mockReturnValue(Promise.resolve({}))
+  await common.controller.handleSave()
 
-    await controller.load()
+  expectExecuteOperations([
+    setPropertyAssignmentOperation(type.getCode(), propertyType.getCode(), true)
+  ])
+}
 
-    controller.handleAddSection()
-    controller.handleAddProperty()
+async function doTestUpdatePropertyTypeIfPossible(
+  type,
+  propertyType,
+  propertyAssignment
+) {
+  common.facade.loadType.mockReturnValue(Promise.resolve(type))
+  common.facade.loadUsages.mockReturnValue(Promise.resolve({}))
+  common.facade.executeOperations.mockReturnValue(Promise.resolve({}))
 
-    controller.handleChange(TypeFormSelectionType.PROPERTY, {
-      id: 'property-0',
-      field: 'scope',
-      value: scope
-    })
-    controller.handleChange(TypeFormSelectionType.PROPERTY, {
-      id: 'property-0',
-      field: 'code',
-      value: 'NEW_CODE'
-    })
-    controller.handleChange(TypeFormSelectionType.PROPERTY, {
-      id: 'property-0',
-      field: 'dataType',
-      value: 'VARCHAR'
-    })
-    controller.handleChange(TypeFormSelectionType.PROPERTY, {
-      id: 'property-0',
-      field: 'label',
-      value: 'NEW_LABEL'
-    })
-    controller.handleChange(TypeFormSelectionType.PROPERTY, {
-      id: 'property-0',
-      field: 'description',
-      value: 'NEW_DESCRIPTION'
-    })
+  await common.controller.load()
 
-    await controller.handleSave()
-
-    const propertyTypeCode =
-      scope === 'local' ? 'TEST_TYPE.NEW_CODE' : 'NEW_CODE'
-
-    expectExecuteOperations([
-      createPropertyTypeOperation({
-        code: propertyTypeCode,
-        dataType: openbis.DataType.VARCHAR,
-        label: 'NEW_LABEL'
-      }),
-      setPropertyAssignmentOperation(
-        SAMPLE_TYPE.getCode(),
-        propertyTypeCode,
-        false
-      )
-    ])
-  }
-
-  async function testUpdatePropertyAssignment(type, propertyType) {
-    facade.loadType.mockReturnValue(Promise.resolve(type))
-    facade.loadUsages.mockReturnValue(Promise.resolve({}))
-    facade.executeOperations.mockReturnValue(Promise.resolve({}))
+  common.controller.handleChange(TypeFormSelectionType.PROPERTY, {
+    id: 'property-0',
+    field: 'label',
+    value: 'Updated label'
+  })
 
-    await controller.load()
+  await common.controller.handleSave()
 
-    controller.handleChange(TypeFormSelectionType.PROPERTY, {
-      id: 'property-0',
-      field: 'mandatory',
-      value: true
-    })
+  expectExecuteOperations([
+    updatePropertyTypeOperation(propertyType.getCode(), 'Updated label'),
+    setPropertyAssignmentOperation(
+      type.getCode(),
+      propertyType.getCode(),
+      propertyAssignment.isMandatory()
+    )
+  ])
+}
 
-    await controller.handleSave()
+async function doTestDeleteProperty(type, propertyType) {
+  common.facade.loadType.mockReturnValue(Promise.resolve(type))
+  common.facade.loadUsages.mockReturnValue(Promise.resolve({}))
+  common.facade.executeOperations.mockReturnValue(Promise.resolve({}))
 
-    expectExecuteOperations([
-      setPropertyAssignmentOperation(
-        type.getCode(),
-        propertyType.getCode(),
-        true
-      )
-    ])
-  }
+  await common.controller.load()
 
-  async function testUpdatePropertyTypeIfPossible(
-    type,
-    propertyType,
-    propertyAssignment
-  ) {
-    facade.loadType.mockReturnValue(Promise.resolve(type))
-    facade.loadUsages.mockReturnValue(Promise.resolve({}))
-    facade.executeOperations.mockReturnValue(Promise.resolve({}))
-
-    await controller.load()
-
-    controller.handleChange(TypeFormSelectionType.PROPERTY, {
-      id: 'property-0',
-      field: 'label',
-      value: 'Updated label'
+  common.controller.handleSelectionChange(TypeFormSelectionType.PROPERTY, {
+    id: 'property-0'
+  })
+  common.controller.handleRemove()
+
+  await common.controller.handleSave()
+
+  expectExecuteOperations([
+    deletePropertyAssignmentOperation(
+      type.getCode(),
+      propertyType.getCode(),
+      false
+    ),
+    setPropertyAssignmentOperation(type.getCode())
+  ])
+}
+
+async function doTestDeletePropertyLastAssignment(type, propertyType) {
+  common.facade.loadType.mockReturnValue(Promise.resolve(type))
+  common.facade.loadUsages.mockReturnValue(Promise.resolve({}))
+  common.facade.loadAssignments.mockReturnValue(
+    Promise.resolve({
+      [propertyType.getCode()]: 1
     })
+  )
+  common.facade.executeOperations.mockReturnValue(Promise.resolve({}))
 
-    await controller.handleSave()
+  await common.controller.load()
 
-    expectExecuteOperations([
-      updatePropertyTypeOperation(propertyType.getCode(), 'Updated label'),
-      setPropertyAssignmentOperation(
-        type.getCode(),
-        propertyType.getCode(),
-        propertyAssignment.isMandatory()
-      )
-    ])
-  }
-
-  async function testDeleteProperty(type, propertyType) {
-    facade.loadType.mockReturnValue(Promise.resolve(type))
-    facade.loadUsages.mockReturnValue(Promise.resolve({}))
-    facade.executeOperations.mockReturnValue(Promise.resolve({}))
-
-    await controller.load()
-
-    controller.handleSelectionChange(TypeFormSelectionType.PROPERTY, {
-      id: 'property-0'
-    })
-    controller.handleRemove()
-
-    await controller.handleSave()
-
-    expectExecuteOperations([
-      deletePropertyAssignmentOperation(
-        type.getCode(),
-        propertyType.getCode(),
-        false
-      ),
-      setPropertyAssignmentOperation(type.getCode())
-    ])
-  }
-
-  async function testDeletePropertyLastAssignment(type, propertyType) {
-    facade.loadType.mockReturnValue(Promise.resolve(type))
-    facade.loadUsages.mockReturnValue(Promise.resolve({}))
-    facade.loadAssignments.mockReturnValue(
-      Promise.resolve({
-        [propertyType.getCode()]: 1
-      })
+  common.controller.handleSelectionChange(TypeFormSelectionType.PROPERTY, {
+    id: 'property-0'
+  })
+  common.controller.handleRemove()
+
+  await common.controller.handleSave()
+
+  expectExecuteOperations([
+    deletePropertyAssignmentOperation(
+      type.getCode(),
+      propertyType.getCode(),
+      false
+    ),
+    deletePropertyTypeOperation(propertyType.getCode()),
+    setPropertyAssignmentOperation(type.getCode())
+  ])
+}
+
+function createPropertyTypeOperation({
+  code: propertyTypeCode,
+  dataType: propertyDataType,
+  vocabulary: propertyTypeVocabulary,
+  label: propertyTypeLabel
+}) {
+  const creation = new openbis.PropertyTypeCreation()
+  creation.setCode(propertyTypeCode)
+  creation.setDataType(propertyDataType)
+  if (propertyTypeVocabulary) {
+    creation.setVocabularyId(
+      new openbis.VocabularyPermId(propertyTypeVocabulary)
     )
-    facade.executeOperations.mockReturnValue(Promise.resolve({}))
-
-    await controller.load()
-
-    controller.handleSelectionChange(TypeFormSelectionType.PROPERTY, {
-      id: 'property-0'
-    })
-    controller.handleRemove()
-
-    await controller.handleSave()
-
-    expectExecuteOperations([
-      deletePropertyAssignmentOperation(
-        type.getCode(),
-        propertyType.getCode(),
-        false
-      ),
-      deletePropertyTypeOperation(propertyType.getCode()),
-      setPropertyAssignmentOperation(type.getCode())
-    ])
   }
-
-  function createPropertyTypeOperation({
-    code: propertyTypeCode,
-    dataType: propertyDataType,
-    vocabulary: propertyTypeVocabulary,
-    label: propertyTypeLabel
-  }) {
-    const creation = new openbis.PropertyTypeCreation()
-    creation.setCode(propertyTypeCode)
-    creation.setDataType(propertyDataType)
-    if (propertyTypeVocabulary) {
-      creation.setVocabularyId(
-        new openbis.VocabularyPermId(propertyTypeVocabulary)
-      )
-    }
-    creation.setLabel(propertyTypeLabel)
-    return new openbis.CreatePropertyTypesOperation([creation])
+  creation.setLabel(propertyTypeLabel)
+  return new openbis.CreatePropertyTypesOperation([creation])
+}
+
+function updatePropertyTypeOperation(propertyTypeCode, propertyTypeLabel) {
+  const update = new openbis.PropertyTypeUpdate()
+  update.setTypeId(new openbis.PropertyTypePermId(propertyTypeCode))
+  update.setLabel(propertyTypeLabel)
+  return new openbis.UpdatePropertyTypesOperation([update])
+}
+
+function deletePropertyTypeOperation(propertyTypeCode) {
+  const id = new openbis.PropertyTypePermId(propertyTypeCode)
+  const options = new openbis.PropertyTypeDeletionOptions()
+  options.setReason('deleted via ng_ui')
+  return new openbis.DeletePropertyTypesOperation([id], options)
+}
+
+function setPropertyAssignmentOperation(
+  typeCode,
+  propertyCode,
+  propertyMandatory
+) {
+  const assignments = []
+  if (propertyCode) {
+    let assignment = new openbis.PropertyAssignmentCreation()
+    assignment.setPropertyTypeId(new openbis.PropertyTypePermId(propertyCode))
+    assignment.setMandatory(propertyMandatory)
+    assignments.push(assignment)
   }
 
-  function updatePropertyTypeOperation(propertyTypeCode, propertyTypeLabel) {
-    const update = new openbis.PropertyTypeUpdate()
-    update.setTypeId(new openbis.PropertyTypePermId(propertyTypeCode))
-    update.setLabel(propertyTypeLabel)
-    return new openbis.UpdatePropertyTypesOperation([update])
-  }
-
-  function deletePropertyTypeOperation(propertyTypeCode) {
-    const id = new openbis.PropertyTypePermId(propertyTypeCode)
-    const options = new openbis.PropertyTypeDeletionOptions()
-    options.setReason('deleted via ng_ui')
-    return new openbis.DeletePropertyTypesOperation([id], options)
-  }
+  const update = new openbis.SampleTypeUpdate()
+  update.setTypeId(
+    new openbis.EntityTypePermId(typeCode, openbis.EntityKind.SAMPLE)
+  )
+  update.getPropertyAssignments().set(assignments)
+
+  return new openbis.UpdateSampleTypesOperation([update])
+}
+
+function deletePropertyAssignmentOperation(typeCode, propertyCode, force) {
+  const assignmentId = new openbis.PropertyAssignmentPermId(
+    new openbis.EntityTypePermId(typeCode, openbis.EntityKind.SAMPLE),
+    new openbis.PropertyTypePermId(propertyCode)
+  )
+
+  const update = new openbis.SampleTypeUpdate()
+  update.setTypeId(
+    new openbis.EntityTypePermId(typeCode, openbis.EntityKind.SAMPLE)
+  )
+  update.getPropertyAssignments().remove([assignmentId])
+  update.getPropertyAssignments().setForceRemovingAssignments(force)
+
+  return new openbis.UpdateSampleTypesOperation([update])
+}
+
+function expectExecuteOperations(expectedOperations) {
+  expect(common.facade.executeOperations).toHaveBeenCalledTimes(1)
+  const actualOperations = common.facade.executeOperations.mock.calls[0][0]
+  expect(actualOperations.length).toEqual(expectedOperations.length)
+  actualOperations.forEach((actualOperation, index) => {
+    expect(actualOperation).toMatchObject(expectedOperations[index])
+  })
+}
 
-  function setPropertyAssignmentOperation(
-    typeCode,
-    propertyCode,
-    propertyMandatory
-  ) {
-    const assignments = []
-    if (propertyCode) {
-      let assignment = new openbis.PropertyAssignmentCreation()
-      assignment.setPropertyTypeId(new openbis.PropertyTypePermId(propertyCode))
-      assignment.setMandatory(propertyMandatory)
-      assignments.push(assignment)
-    }
-
-    const update = new openbis.SampleTypeUpdate()
-    update.setTypeId(
-      new openbis.EntityTypePermId(typeCode, openbis.EntityKind.SAMPLE)
-    )
-    update.getPropertyAssignments().set(assignments)
+const LOCAL_PROPERTY_TYPE = new openbis.PropertyType()
+LOCAL_PROPERTY_TYPE.setCode('TEST_TYPE.TEST_PROPERTY_TYPE')
+LOCAL_PROPERTY_TYPE.setLabel('TEST_LABEL')
+LOCAL_PROPERTY_TYPE.setDescription('TEST_DESCRIPTION')
+LOCAL_PROPERTY_TYPE.setDataType(openbis.DataType.INTEGER)
 
-    return new openbis.UpdateSampleTypesOperation([update])
-  }
+const LOCAL_PROPERTY_ASSIGNMENT = new openbis.PropertyAssignment()
+LOCAL_PROPERTY_ASSIGNMENT.setPropertyType(LOCAL_PROPERTY_TYPE)
 
-  function deletePropertyAssignmentOperation(typeCode, propertyCode, force) {
-    const assignmentId = new openbis.PropertyAssignmentPermId(
-      new openbis.EntityTypePermId(typeCode, openbis.EntityKind.SAMPLE),
-      new openbis.PropertyTypePermId(propertyCode)
-    )
+const SAMPLE_TYPE_WITH_LOCAL_PROPERTY = new openbis.SampleType()
+SAMPLE_TYPE_WITH_LOCAL_PROPERTY.setCode('TEST_TYPE')
+SAMPLE_TYPE_WITH_LOCAL_PROPERTY.setGeneratedCodePrefix('TEST_PREFIX')
+SAMPLE_TYPE_WITH_LOCAL_PROPERTY.setPropertyAssignments([
+  LOCAL_PROPERTY_ASSIGNMENT
+])
 
-    const update = new openbis.SampleTypeUpdate()
-    update.setTypeId(
-      new openbis.EntityTypePermId(typeCode, openbis.EntityKind.SAMPLE)
-    )
-    update.getPropertyAssignments().remove([assignmentId])
-    update.getPropertyAssignments().setForceRemovingAssignments(force)
+const GLOBAL_PROPERTY_TYPE = new openbis.PropertyType()
+GLOBAL_PROPERTY_TYPE.setCode('TEST_PROPERTY_TYPE')
+GLOBAL_PROPERTY_TYPE.setLabel('TEST_LABEL')
+GLOBAL_PROPERTY_TYPE.setDescription('TEST_DESCRIPTION')
+GLOBAL_PROPERTY_TYPE.setDataType(openbis.DataType.INTEGER)
 
-    return new openbis.UpdateSampleTypesOperation([update])
-  }
+const GLOBAL_PROPERTY_ASSIGNMENT = new openbis.PropertyAssignment()
+GLOBAL_PROPERTY_ASSIGNMENT.setPropertyType(GLOBAL_PROPERTY_TYPE)
 
-  function expectExecuteOperations(expectedOperations) {
-    expect(facade.executeOperations).toHaveBeenCalledTimes(1)
-    const actualOperations = facade.executeOperations.mock.calls[0][0]
-    expect(actualOperations.length).toEqual(expectedOperations.length)
-    actualOperations.forEach((actualOperation, index) => {
-      expect(actualOperation).toMatchObject(expectedOperations[index])
-    })
-  }
-})
+const SAMPLE_TYPE_WITH_GLOBAL_PROPERTY = new openbis.SampleType()
+SAMPLE_TYPE_WITH_GLOBAL_PROPERTY.setCode('TEST_TYPE')
+SAMPLE_TYPE_WITH_GLOBAL_PROPERTY.setGeneratedCodePrefix('TEST_PREFIX')
+SAMPLE_TYPE_WITH_GLOBAL_PROPERTY.setPropertyAssignments([
+  GLOBAL_PROPERTY_ASSIGNMENT
+])
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerSelectionChange.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerSelectionChange.test.js
index 54f3c0ee0f36947b8f8d2daae53841ef67213693..a9064239bb76e1e799a6321334c9435a3a9e5f5a 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerSelectionChange.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerSelectionChange.test.js
@@ -1,97 +1,88 @@
-import ComponentContext from '@srcTest/js/components/common/ComponentContext.js'
-import TypeFormControler from '@src/js/components/types/form/TypeFormController.js'
+import TypeFormControllerTest from '@srcTest/js/components/types/form/TypeFormControllerTest.js'
 import TypeFormSelectionType from '@src/js/components/types/form/TypeFormSelectionType.js'
-import TypeFormFacade from '@src/js/components/types/form/TypeFormFacade'
 import objectTypes from '@src/js/common/consts/objectType.js'
 import fixture from '@srcTest/js/common/fixture.js'
 
-jest.mock('@src/js/components/types/form/TypeFormFacade')
-
-let context = null
-let facade = null
-let controller = null
+let common = null
 
 beforeEach(() => {
-  jest.resetAllMocks()
-  context = new ComponentContext()
-  context.setProps({
-    object: {
-      id: 'TEST_OBJECT_ID',
-      type: objectTypes.OBJECT_TYPE
-    }
+  common = new TypeFormControllerTest()
+  common.beforeEach()
+  common.init({
+    id: 'TEST_OBJECT_ID',
+    type: objectTypes.OBJECT_TYPE
   })
-  facade = new TypeFormFacade()
-  controller = new TypeFormControler(facade)
-  controller.init(context)
 })
 
 afterEach(() => {
-  expect(facade.loadType).toHaveBeenCalledWith(context.getProps().object)
-  expect(facade.loadUsages).toHaveBeenCalledWith(context.getProps().object)
+  common.afterEach()
 })
 
-describe('TypeFormController.handleSelectionChange', () => {
-  test('section', async () => {
-    facade.loadType.mockReturnValue(
-      Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
-    )
-    facade.loadUsages.mockReturnValue(Promise.resolve({}))
-
-    await controller.load()
-
-    expect(context.getState()).toMatchObject({
-      selection: null
-    })
-
-    controller.handleSelectionChange(TypeFormSelectionType.SECTION, {
-      id: 'section-0'
-    })
-
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.SECTION,
-        params: {
-          id: 'section-0'
-        }
+describe(TypeFormControllerTest.SUITE, () => {
+  test('select a section', testSelectSection)
+  test('select a property', testSelectProperty)
+})
+
+async function testSelectSection() {
+  common.facade.loadType.mockReturnValue(
+    Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
+  )
+  common.facade.loadUsages.mockReturnValue(Promise.resolve({}))
+
+  await common.controller.load()
+
+  expect(common.context.getState()).toMatchObject({
+    selection: null
+  })
+
+  common.controller.handleSelectionChange(TypeFormSelectionType.SECTION, {
+    id: 'section-0'
+  })
+
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.SECTION,
+      params: {
+        id: 'section-0'
       }
-    })
+    }
+  })
 
-    controller.handleSelectionChange()
+  common.controller.handleSelectionChange()
 
-    expect(context.getState()).toMatchObject({
-      selection: null
-    })
+  expect(common.context.getState()).toMatchObject({
+    selection: null
   })
+}
 
-  test('property', async () => {
-    facade.loadType.mockReturnValue(
-      Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
-    )
-    facade.loadUsages.mockReturnValue(Promise.resolve({}))
+async function testSelectProperty() {
+  common.facade.loadType.mockReturnValue(
+    Promise.resolve(fixture.TEST_SAMPLE_TYPE_DTO)
+  )
+  common.facade.loadUsages.mockReturnValue(Promise.resolve({}))
 
-    await controller.load()
+  await common.controller.load()
 
-    expect(context.getState()).toMatchObject({
-      selection: null
-    })
+  expect(common.context.getState()).toMatchObject({
+    selection: null
+  })
 
-    controller.handleSelectionChange(TypeFormSelectionType.PROPERTY, {
-      id: 'property-0'
-    })
+  common.controller.handleSelectionChange(TypeFormSelectionType.PROPERTY, {
+    id: 'property-0'
+  })
 
-    expect(context.getState()).toMatchObject({
-      selection: {
-        type: TypeFormSelectionType.PROPERTY,
-        params: {
-          id: 'property-0'
-        }
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.PROPERTY,
+      params: {
+        id: 'property-0'
       }
-    })
+    }
+  })
 
-    controller.handleSelectionChange()
+  common.controller.handleSelectionChange()
 
-    expect(context.getState()).toMatchObject({
-      selection: null
-    })
+  expect(common.context.getState()).toMatchObject({
+    selection: null
   })
-})
+}
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerTest.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerTest.js
new file mode 100644
index 0000000000000000000000000000000000000000..d2feec9a5387e79931b329b1d74563bf9b2a399e
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerTest.js
@@ -0,0 +1,32 @@
+import ComponentContext from '@srcTest/js/components/common/ComponentContext.js'
+import TypeFormControler from '@src/js/components/types/form/TypeFormController.js'
+import TypeFormFacade from '@src/js/components/types/form/TypeFormFacade'
+
+jest.mock('@src/js/components/types/form/TypeFormFacade')
+
+export default class TypeFormControllerTest {
+  static SUITE = 'TypeFormController'
+
+  beforeEach() {
+    jest.resetAllMocks()
+  }
+
+  init(object) {
+    this.context = new ComponentContext()
+    this.context.setProps({
+      object
+    })
+    this.facade = new TypeFormFacade()
+    this.controller = new TypeFormControler(this.facade)
+    this.controller.init(this.context)
+  }
+
+  afterEach() {
+    expect(this.facade.loadType).toHaveBeenCalledWith(
+      this.context.getProps().object
+    )
+    expect(this.facade.loadUsages).toHaveBeenCalledWith(
+      this.context.getProps().object
+    )
+  }
+}
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerValidate.test.js b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerValidate.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..11f6f3867516e629179fb41916a96ae214164f4a
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/types/form/TypeFormControllerValidate.test.js
@@ -0,0 +1,86 @@
+import TypeFormControllerTest from '@srcTest/js/components/types/form/TypeFormControllerTest.js'
+import TypeFormSelectionType from '@src/js/components/types/form/TypeFormSelectionType.js'
+import objectTypes from '@src/js/common/consts/objectType.js'
+import openbis from '@srcTest/js/services/openbis.js'
+
+let common = null
+
+beforeEach(() => {
+  common = new TypeFormControllerTest()
+  common.beforeEach()
+  common.init({
+    id: 'TEST_OBJECT_ID',
+    type: objectTypes.OBJECT_TYPE
+  })
+})
+
+afterEach(() => {
+  common.afterEach()
+})
+
+describe(TypeFormControllerTest.SUITE, () => {
+  test('validate', testValidate)
+})
+
+async function testValidate() {
+  const SAMPLE_TYPE = new openbis.SampleType()
+
+  common.facade.loadType.mockReturnValue(Promise.resolve(SAMPLE_TYPE))
+  common.facade.loadUsages.mockReturnValue(Promise.resolve({}))
+
+  await common.controller.load()
+  common.controller.handleAddSection()
+  common.controller.handleAddProperty()
+
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.PROPERTY,
+      params: {
+        id: 'property-0'
+      }
+    },
+    type: {
+      errors: 0
+    },
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: null },
+        dataType: { value: null },
+        label: { value: null },
+        description: { value: null },
+        errors: 0
+      }
+    ]
+  })
+
+  await common.controller.handleSave()
+
+  expect(common.context.getState()).toMatchObject({
+    selection: {
+      type: TypeFormSelectionType.TYPE,
+      params: {
+        part: 'code'
+      }
+    },
+    type: {
+      code: {
+        error: 'Code cannot be empty'
+      },
+      generatedCodePrefix: {
+        error: 'Generated code prefix cannot be empty'
+      },
+      errors: 2
+    },
+    properties: [
+      {
+        id: 'property-0',
+        code: { value: null, error: 'Code cannot be empty' },
+        dataType: { value: null, error: 'Data Type cannot be empty' },
+        label: { value: null, error: 'Label cannot be empty' },
+        description: { value: null, error: 'Description cannot be empty' },
+        errors: 4
+      }
+    ]
+  })
+}
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentAddTerm.test.js b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentAddTerm.test.js
index 3724edd6f8eb66c2a76cb422bde97fdb950e5499..7f663672d577f0f74f8f84a1c9cff801a6a7a3f8 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentAddTerm.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentAddTerm.test.js
@@ -8,7 +8,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('VocabularyFormComponentAddTerm', () => {
+describe(VocabularyFormComponentTest.SUITE, () => {
   test('add term', testAddTerm)
 })
 
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentChangeTerm.test.js b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentChangeTerm.test.js
index a09437f00d013fcf1457c3f8fbeb33c2c4d056dd..7f2d0bac3aa42836191caafd38ff395ef171fa56 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentChangeTerm.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentChangeTerm.test.js
@@ -8,7 +8,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('VocabularyFormComponentChangeTerm', () => {
+describe(VocabularyFormComponentTest.SUITE, () => {
   test('change term', testChangeTerm)
 })
 
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentChangeVocabulary.test.js b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentChangeVocabulary.test.js
index b8254682d376f286d5fba5dd654a8fe4ee86c0a2..be88fcd6ef2c9206c4c1b0bb786b62d156890bb4 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentChangeVocabulary.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentChangeVocabulary.test.js
@@ -8,7 +8,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('VocabularyFormComponentChangeVocabulary', () => {
+describe(VocabularyFormComponentTest.SUITE, () => {
   test('change vocabulary', testChangeVocabulary)
 })
 
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentFilter.test.js b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentFilter.test.js
index 8cf9e9b59df749bcee850a8b783f8697ece41ed6..271d434b0d582614baa4ce337e6f0724ef594d62 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentFilter.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentFilter.test.js
@@ -7,7 +7,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('VocabularyFormComponentFilter', () => {
+describe(VocabularyFormComponentTest.SUITE, () => {
   test('filter', testFilter)
 })
 
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentInternal.test.js b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentInternal.test.js
index c842de37699fdd8c2d691dd91d8bf040b77130a6..682b7c5f0e7f84d2af19824267a41982fe1955aa 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentInternal.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentInternal.test.js
@@ -10,7 +10,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('VocabularyFormComponentInternal', () => {
+describe(VocabularyFormComponentTest.SUITE, () => {
   test('internal', testInternal)
 })
 
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentLoad.test.js b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentLoad.test.js
index bda26882f98132930dee25fa580fe0e4f75fcfc7..e83b3957ac7ae360e8fd27791fc78d06885fc980 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentLoad.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentLoad.test.js
@@ -8,7 +8,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('VocabularyFormComponentLoad', () => {
+describe(VocabularyFormComponentTest.SUITE, () => {
   test('load new', testLoadNew)
   test('load existing', testLoadExisting)
 })
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentPage.test.js b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentPage.test.js
index 9eb61ed4ffac8d643d99c743bfb22adf1f7ef33d..6d02ecead0a3b4792bdd0af2039f66b785009132 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentPage.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentPage.test.js
@@ -7,7 +7,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('VocabularyFormComponentPage', () => {
+describe(VocabularyFormComponentTest.SUITE, () => {
   test('page', testPage)
 })
 
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentRemoveTerm.test.js b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentRemoveTerm.test.js
index e78d1bc0f527abad2bc239579c6ca46945a41f37..20bed8d71e655abd5fc83bd3601f7d94ffa4d0cb 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentRemoveTerm.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentRemoveTerm.test.js
@@ -8,7 +8,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('VocabularyFormComponentRemoveTerm', () => {
+describe(VocabularyFormComponentTest.SUITE, () => {
   test('remove term', testRemoveTerm)
 })
 
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentSelectTerm.test.js b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentSelectTerm.test.js
index 45bdf31bd807eb142b25c8c4707ef62542ec12dc..148d4b4c3cc826554c756884a53533b7dca1535c 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentSelectTerm.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentSelectTerm.test.js
@@ -8,7 +8,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('VocabularyFormComponentSelectTerm', () => {
+describe(VocabularyFormComponentTest.SUITE, () => {
   test('select term', testSelectTerm)
   test('follow selected term', testFollowSelectedTerm)
 })
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentSort.test.js b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentSort.test.js
index cfd128177d58581916de293819864d025f9b4c76..43c178d60f60a1c877f3cbe9451f4d15b525ee19 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentSort.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentSort.test.js
@@ -7,7 +7,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('VocabularyFormComponentSort', () => {
+describe(VocabularyFormComponentTest.SUITE, () => {
   test('sort', testSort)
 })
 
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentTest.js b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentTest.js
index 774cc479607d65a617472a177cea21ad1c8f8cf0..0f97784748b3130bd6eaad5586b3b027d0f3cb89 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentTest.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentTest.js
@@ -9,6 +9,8 @@ import objectTypes from '@src/js/common/consts/objectType.js'
 jest.mock('@src/js/components/types/form/VocabularyFormFacade')
 
 export default class VocabularyFormComponentTest extends ComponentTest {
+  static SUITE = 'VocabularyFormComponent'
+
   constructor() {
     super(
       object => <VocabularyForm object={object} controller={this.controller} />,
diff --git a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentValidate.test.js b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentValidate.test.js
index e7c263dec94aceae480716b2510ea4bd59978096..845255df284eb7314333f3161a991136b3a8a08e 100644
--- a/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentValidate.test.js
+++ b/openbis_ng_ui/srcTest/js/components/types/form/VocabularyFormComponentValidate.test.js
@@ -8,7 +8,7 @@ beforeEach(() => {
   common.beforeEach()
 })
 
-describe('VocabularyFormComponentValidate', () => {
+describe(VocabularyFormComponentTest.SUITE, () => {
   test('validate term', testValidateTerm)
   test('validate vocabulary', testValidateVocabulary)
 })
diff --git a/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserComponent.test.js b/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserComponent.test.js
deleted file mode 100644
index 107efc4836eba59deb265105a2b51bf6ef7f88cf..0000000000000000000000000000000000000000
--- a/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserComponent.test.js
+++ /dev/null
@@ -1,144 +0,0 @@
-import React from 'react'
-import ComponentTest from '@srcTest/js/components/common/ComponentTest.js'
-import BrowserWrapper from '@srcTest/js/components/common/browser/wrapper/BrowserWrapper.js'
-import UserBrowser from '@src/js/components/users/browser/UserBrowser.jsx'
-import openbis from '@srcTest/js/services/openbis.js'
-import fixture from '@srcTest/js/common/fixture.js'
-
-let common = null
-
-beforeEach(() => {
-  common = new ComponentTest(
-    () => <UserBrowser />,
-    wrapper => new BrowserWrapper(wrapper)
-  )
-  common.beforeEach()
-
-  openbis.mockSearchPersons([fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO])
-  openbis.mockSearchGroups([
-    fixture.TEST_USER_GROUP_DTO,
-    fixture.ANOTHER_USER_GROUP_DTO,
-    fixture.ALL_USERS_GROUP_DTO
-  ])
-})
-
-describe('browser', () => {
-  test('load', testLoad)
-  test('open/close', testOpenClose)
-  test('filter', testFilter)
-})
-
-async function testLoad() {
-  const browser = await common.mount()
-
-  browser.expectJSON({
-    filter: {
-      value: null
-    },
-    nodes: [
-      { level: 0, text: 'Users' },
-      { level: 0, text: 'Groups' }
-    ]
-  })
-}
-
-async function testOpenClose() {
-  const browser = await common.mount()
-
-  browser.getNodes()[0].getIcon().click()
-  await browser.update()
-
-  browser.expectJSON({
-    filter: {
-      value: null
-    },
-    nodes: [
-      { level: 0, text: 'Users' },
-      { level: 1, text: fixture.ANOTHER_USER_DTO.userId },
-      { level: 1, text: fixture.TEST_USER_DTO.userId },
-      { level: 0, text: 'Groups' }
-    ]
-  })
-
-  browser.getNodes()[3].getIcon().click()
-  await browser.update()
-
-  browser.expectJSON({
-    filter: {
-      value: null
-    },
-    nodes: [
-      { level: 0, text: 'Users' },
-      { level: 1, text: fixture.ANOTHER_USER_DTO.userId },
-      { level: 1, text: fixture.TEST_USER_DTO.userId },
-      { level: 0, text: 'Groups' },
-      { level: 1, text: fixture.ALL_USERS_GROUP_DTO.code },
-      { level: 1, text: fixture.ANOTHER_USER_GROUP_DTO.code },
-      { level: 1, text: fixture.TEST_USER_GROUP_DTO.code }
-    ]
-  })
-
-  browser.getNodes()[0].getIcon().click()
-  await browser.update()
-
-  browser.expectJSON({
-    filter: {
-      value: null
-    },
-    nodes: [
-      { level: 0, text: 'Users' },
-      { level: 0, text: 'Groups' },
-      { level: 1, text: fixture.ALL_USERS_GROUP_DTO.code },
-      { level: 1, text: fixture.ANOTHER_USER_GROUP_DTO.code },
-      { level: 1, text: fixture.TEST_USER_GROUP_DTO.code }
-    ]
-  })
-
-  browser.getNodes()[1].getIcon().click()
-  await browser.update()
-
-  browser.expectJSON({
-    filter: {
-      value: null
-    },
-    nodes: [
-      { level: 0, text: 'Users' },
-      { level: 0, text: 'Groups' }
-    ]
-  })
-}
-
-async function testFilter() {
-  const browser = await common.mount()
-
-  browser.getFilter().change(fixture.ANOTHER_USER_GROUP_DTO.code.toUpperCase())
-  await browser.update()
-
-  browser.expectJSON({
-    filter: {
-      value: fixture.ANOTHER_USER_GROUP_DTO.code.toUpperCase()
-    },
-    nodes: [
-      { level: 0, text: 'Groups' },
-      { level: 1, text: fixture.ANOTHER_USER_GROUP_DTO.code }
-    ]
-  })
-
-  browser.getFilter().getClearIcon().click()
-  await browser.update()
-
-  browser.expectJSON({
-    filter: {
-      value: null
-    },
-    nodes: [
-      { level: 0, text: 'Users' },
-      { level: 1, text: fixture.ANOTHER_USER_DTO.userId },
-      { level: 1, text: fixture.TEST_USER_DTO.userId },
-      { level: 0, text: 'Groups' },
-      { level: 1, text: fixture.ALL_USERS_GROUP_DTO.code },
-      { level: 1, text: fixture.ANOTHER_USER_GROUP_DTO.code },
-      { level: 1, text: fixture.TEST_USER_GROUP_DTO.code }
-    ]
-  })
-}
diff --git a/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserComponentFilter.test.js b/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserComponentFilter.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..04f1956e8d8005d8753fdeaf4a7c6a0bf17a8184
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserComponentFilter.test.js
@@ -0,0 +1,48 @@
+import UserBrowserComponentTest from '@srcTest/js/components/users/browser/UserBrowserComponentTest.js'
+import fixture from '@srcTest/js/common/fixture.js'
+
+let common = null
+
+beforeEach(() => {
+  common = new UserBrowserComponentTest()
+  common.beforeEach()
+})
+
+describe(UserBrowserComponentTest.SUITE, () => {
+  test('filter', testFilter)
+})
+
+async function testFilter() {
+  const browser = await common.mount()
+
+  browser.getFilter().change(fixture.ANOTHER_USER_GROUP_DTO.code.toUpperCase())
+  await browser.update()
+
+  browser.expectJSON({
+    filter: {
+      value: fixture.ANOTHER_USER_GROUP_DTO.code.toUpperCase()
+    },
+    nodes: [
+      { level: 0, text: 'Groups' },
+      { level: 1, text: fixture.ANOTHER_USER_GROUP_DTO.code }
+    ]
+  })
+
+  browser.getFilter().getClearIcon().click()
+  await browser.update()
+
+  browser.expectJSON({
+    filter: {
+      value: null
+    },
+    nodes: [
+      { level: 0, text: 'Users' },
+      { level: 1, text: fixture.ANOTHER_USER_DTO.userId },
+      { level: 1, text: fixture.TEST_USER_DTO.userId },
+      { level: 0, text: 'Groups' },
+      { level: 1, text: fixture.ALL_USERS_GROUP_DTO.code },
+      { level: 1, text: fixture.ANOTHER_USER_GROUP_DTO.code },
+      { level: 1, text: fixture.TEST_USER_GROUP_DTO.code }
+    ]
+  })
+}
diff --git a/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserComponentLoad.test.js b/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserComponentLoad.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..01313c11f6e2b221fb0c129a2a563e16f98ee784
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserComponentLoad.test.js
@@ -0,0 +1,26 @@
+import UserBrowserComponentTest from '@srcTest/js/components/users/browser/UserBrowserComponentTest.js'
+
+let common = null
+
+beforeEach(() => {
+  common = new UserBrowserComponentTest()
+  common.beforeEach()
+})
+
+describe(UserBrowserComponentTest.SUITE, () => {
+  test('load', testLoad)
+})
+
+async function testLoad() {
+  const browser = await common.mount()
+
+  browser.expectJSON({
+    filter: {
+      value: null
+    },
+    nodes: [
+      { level: 0, text: 'Users' },
+      { level: 0, text: 'Groups' }
+    ]
+  })
+}
diff --git a/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserComponentOpenClose.test.js b/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserComponentOpenClose.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..6b1b76af2bc0ecd3a4f7a3fd01b698706a6c921b
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserComponentOpenClose.test.js
@@ -0,0 +1,79 @@
+import UserBrowserComponentTest from '@srcTest/js/components/users/browser/UserBrowserComponentTest.js'
+import fixture from '@srcTest/js/common/fixture.js'
+
+let common = null
+
+beforeEach(() => {
+  common = new UserBrowserComponentTest()
+  common.beforeEach()
+})
+
+describe(UserBrowserComponentTest.SUITE, () => {
+  test('open/close', testOpenClose)
+})
+
+async function testOpenClose() {
+  const browser = await common.mount()
+
+  browser.getNodes()[0].getIcon().click()
+  await browser.update()
+
+  browser.expectJSON({
+    filter: {
+      value: null
+    },
+    nodes: [
+      { level: 0, text: 'Users' },
+      { level: 1, text: fixture.ANOTHER_USER_DTO.userId },
+      { level: 1, text: fixture.TEST_USER_DTO.userId },
+      { level: 0, text: 'Groups' }
+    ]
+  })
+
+  browser.getNodes()[3].getIcon().click()
+  await browser.update()
+
+  browser.expectJSON({
+    filter: {
+      value: null
+    },
+    nodes: [
+      { level: 0, text: 'Users' },
+      { level: 1, text: fixture.ANOTHER_USER_DTO.userId },
+      { level: 1, text: fixture.TEST_USER_DTO.userId },
+      { level: 0, text: 'Groups' },
+      { level: 1, text: fixture.ALL_USERS_GROUP_DTO.code },
+      { level: 1, text: fixture.ANOTHER_USER_GROUP_DTO.code },
+      { level: 1, text: fixture.TEST_USER_GROUP_DTO.code }
+    ]
+  })
+
+  browser.getNodes()[0].getIcon().click()
+  await browser.update()
+
+  browser.expectJSON({
+    filter: {
+      value: null
+    },
+    nodes: [
+      { level: 0, text: 'Users' },
+      { level: 0, text: 'Groups' },
+      { level: 1, text: fixture.ALL_USERS_GROUP_DTO.code },
+      { level: 1, text: fixture.ANOTHER_USER_GROUP_DTO.code },
+      { level: 1, text: fixture.TEST_USER_GROUP_DTO.code }
+    ]
+  })
+
+  browser.getNodes()[1].getIcon().click()
+  await browser.update()
+
+  browser.expectJSON({
+    filter: {
+      value: null
+    },
+    nodes: [
+      { level: 0, text: 'Users' },
+      { level: 0, text: 'Groups' }
+    ]
+  })
+}
diff --git a/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserComponentTest.js b/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserComponentTest.js
new file mode 100644
index 0000000000000000000000000000000000000000..8e023fc037ed7bde9fdea1422ccbeadba4f7fb04
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserComponentTest.js
@@ -0,0 +1,28 @@
+import React from 'react'
+import ComponentTest from '@srcTest/js/components/common/ComponentTest.js'
+import BrowserWrapper from '@srcTest/js/components/common/browser/wrapper/BrowserWrapper.js'
+import UserBrowser from '@src/js/components/users/browser/UserBrowser.jsx'
+import openbis from '@srcTest/js/services/openbis.js'
+import fixture from '@srcTest/js/common/fixture.js'
+
+export default class UserBrowserComponentTest extends ComponentTest {
+  static SUITE = 'UserBrowserComponent'
+
+  constructor() {
+    super(
+      () => <UserBrowser />,
+      wrapper => new BrowserWrapper(wrapper)
+    )
+  }
+
+  async beforeEach() {
+    super.beforeEach()
+
+    openbis.mockSearchPersons([fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO])
+    openbis.mockSearchGroups([
+      fixture.TEST_USER_GROUP_DTO,
+      fixture.ANOTHER_USER_GROUP_DTO,
+      fixture.ALL_USERS_GROUP_DTO
+    ])
+  }
+}
diff --git a/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserController.test.js b/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserController.test.js
deleted file mode 100644
index b34feb5bcc31ccacd6347cbd03c247050fbd96ac..0000000000000000000000000000000000000000
--- a/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserController.test.js
+++ /dev/null
@@ -1,305 +0,0 @@
-import UserBrowserController from '@src/js/components/users/browser/UserBrowserController.js'
-import ComponentContext from '@srcTest/js/components/common/ComponentContext.js'
-import openbis from '@srcTest/js/services/openbis.js'
-import pages from '@src/js/common/consts/pages.js'
-import objectType from '@src/js/common/consts/objectType.js'
-import actions from '@src/js/store/actions/actions.js'
-import fixture from '@srcTest/js/common/fixture.js'
-
-let context = null
-let controller = null
-
-beforeEach(() => {
-  jest.resetAllMocks()
-
-  context = new ComponentContext()
-  controller = new UserBrowserController()
-  controller.init(context)
-})
-
-describe('browser', () => {
-  test('load', testLoad)
-  test('filter', testFilter)
-  test('select node', testSelectNode)
-  test('select another node', testSelectAnotherNode)
-  test('select virtual node', testSelectVirtualNode)
-  test('expand and collapse node', testExpandAndCollapseNode)
-})
-
-async function testLoad() {
-  openbis.mockSearchPersons([fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO])
-  openbis.mockSearchGroups([
-    fixture.TEST_USER_GROUP_DTO,
-    fixture.ANOTHER_USER_GROUP_DTO,
-    fixture.ALL_USERS_GROUP_DTO
-  ])
-
-  await controller.load()
-
-  expect(controller.getNodes()).toMatchObject([
-    {
-      text: 'Users',
-      expanded: false,
-      selected: false
-    },
-    {
-      text: 'Groups',
-      expanded: false,
-      selected: false
-    }
-  ])
-
-  context.expectNoActions()
-}
-
-async function testFilter() {
-  openbis.mockSearchPersons([fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO])
-  openbis.mockSearchGroups([
-    fixture.TEST_USER_GROUP_DTO,
-    fixture.ANOTHER_USER_GROUP_DTO,
-    fixture.ALL_USERS_GROUP_DTO
-  ])
-
-  await controller.load()
-  controller.filterChange('ANOTHER')
-
-  expect(controller.getNodes()).toMatchObject([
-    {
-      text: 'Users',
-      expanded: true,
-      selected: false,
-      children: [
-        {
-          text: fixture.ANOTHER_USER_DTO.userId,
-          expanded: false,
-          selected: false
-        }
-      ]
-    },
-    {
-      text: 'Groups',
-      expanded: true,
-      selected: false,
-      children: [
-        {
-          text: fixture.ANOTHER_USER_GROUP_DTO.code,
-          expanded: false,
-          selected: false
-        }
-      ]
-    }
-  ])
-
-  context.expectNoActions()
-}
-
-async function testSelectNode() {
-  openbis.mockSearchPersons([fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO])
-  openbis.mockSearchGroups([
-    fixture.TEST_USER_GROUP_DTO,
-    fixture.ANOTHER_USER_GROUP_DTO,
-    fixture.ALL_USERS_GROUP_DTO
-  ])
-
-  await controller.load()
-
-  controller.nodeSelect('users/' + fixture.TEST_USER_DTO.userId)
-
-  expect(controller.getNodes()).toMatchObject([
-    {
-      text: 'Users',
-      expanded: false,
-      selected: false,
-      children: [
-        {
-          text: fixture.ANOTHER_USER_DTO.userId,
-          expanded: false,
-          selected: false
-        },
-        {
-          text: fixture.TEST_USER_DTO.userId,
-          expanded: false,
-          selected: true
-        }
-      ]
-    },
-    {
-      text: 'Groups',
-      expanded: false,
-      selected: false,
-      children: [
-        {
-          text: fixture.ALL_USERS_GROUP_DTO.code,
-          expanded: false,
-          selected: false
-        },
-        {
-          text: fixture.ANOTHER_USER_GROUP_DTO.code,
-          expanded: false,
-          selected: false
-        },
-        {
-          text: fixture.TEST_USER_GROUP_DTO.code,
-          expanded: false,
-          selected: false
-        }
-      ]
-    }
-  ])
-  expectOpenUserAction(fixture.TEST_USER_DTO.userId)
-
-  controller.nodeSelect('groups/' + fixture.ANOTHER_USER_GROUP_DTO.code)
-
-  expect(controller.getNodes()).toMatchObject([
-    {
-      text: 'Users',
-      expanded: false,
-      selected: false,
-      children: [
-        {
-          text: fixture.ANOTHER_USER_DTO.userId,
-          expanded: false,
-          selected: false
-        },
-        {
-          text: fixture.TEST_USER_DTO.userId,
-          expanded: false,
-          selected: false
-        }
-      ]
-    },
-    {
-      text: 'Groups',
-      expanded: false,
-      selected: false,
-      children: [
-        {
-          text: fixture.ALL_USERS_GROUP_DTO.code,
-          expanded: false,
-          selected: false
-        },
-        {
-          text: fixture.ANOTHER_USER_GROUP_DTO.code,
-          expanded: false,
-          selected: true
-        },
-        {
-          text: fixture.TEST_USER_GROUP_DTO.code,
-          expanded: false,
-          selected: false
-        }
-      ]
-    }
-  ])
-  expectOpenGroupAction(fixture.ANOTHER_USER_GROUP_DTO.code)
-}
-
-async function testSelectAnotherNode() {
-  openbis.mockSearchPersons([fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO])
-  openbis.mockSearchGroups([])
-
-  await controller.load()
-  controller.nodeSelect('users/' + fixture.TEST_USER_DTO.userId)
-  controller.nodeSelect('users/' + fixture.ANOTHER_USER_DTO.userId)
-
-  expect(controller.getNodes()).toMatchObject([
-    {
-      text: 'Users',
-      expanded: false,
-      selected: false,
-      children: [
-        {
-          text: fixture.ANOTHER_USER_DTO.userId,
-          expanded: false,
-          selected: true
-        },
-        {
-          text: fixture.TEST_USER_DTO.userId,
-          expanded: false,
-          selected: false
-        }
-      ]
-    },
-    {
-      text: 'Groups',
-      expanded: false,
-      selected: false,
-      children: []
-    }
-  ])
-
-  expectOpenUserAction(fixture.TEST_USER_DTO.userId)
-  expectOpenUserAction(fixture.ANOTHER_USER_DTO.userId)
-}
-
-async function testSelectVirtualNode() {
-  openbis.mockSearchPersons([fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO])
-  openbis.mockSearchGroups([])
-
-  await controller.load()
-  controller.nodeSelect('users')
-
-  expect(controller.getNodes()).toMatchObject([
-    {
-      text: 'Users',
-      expanded: false,
-      selected: true
-    },
-    {
-      text: 'Groups',
-      expanded: false,
-      selected: false
-    }
-  ])
-
-  context.expectNoActions()
-}
-
-async function testExpandAndCollapseNode() {
-  openbis.mockSearchPersons([])
-  openbis.mockSearchGroups([fixture.TEST_USER_GROUP_DTO])
-
-  await controller.load()
-  controller.nodeExpand('groups')
-
-  expect(controller.getNodes()).toMatchObject([
-    {
-      text: 'Users',
-      expanded: false,
-      selected: false
-    },
-    {
-      text: 'Groups',
-      expanded: true,
-      selected: false
-    }
-  ])
-
-  context.expectNoActions()
-  controller.nodeCollapse('groups')
-
-  expect(controller.getNodes()).toMatchObject([
-    {
-      text: 'Users',
-      expanded: false,
-      selected: false
-    },
-    {
-      text: 'Groups',
-      expanded: false,
-      selected: false
-    }
-  ])
-
-  context.expectNoActions()
-}
-
-function expectOpenUserAction(userId) {
-  context.expectAction(actions.objectOpen(pages.USERS, objectType.USER, userId))
-}
-
-function expectOpenGroupAction(groupId) {
-  context.expectAction(
-    actions.objectOpen(pages.USERS, objectType.USER_GROUP, groupId)
-  )
-}
diff --git a/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserControllerExpandCollapseNode.test.js b/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserControllerExpandCollapseNode.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..87068ca8bcac589cc9aed00b4747fea45b6b13e3
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserControllerExpandCollapseNode.test.js
@@ -0,0 +1,53 @@
+import UserBrowserControllerTest from '@srcTest/js/components/users/browser/UserBrowserControllerTest.js'
+import openbis from '@srcTest/js/services/openbis.js'
+import fixture from '@srcTest/js/common/fixture.js'
+
+let common = null
+
+beforeEach(() => {
+  common = new UserBrowserControllerTest()
+  common.beforeEach()
+})
+
+describe(UserBrowserControllerTest.SUITE, () => {
+  test('expand and collapse node', testExpandAndCollapseNode)
+})
+
+async function testExpandAndCollapseNode() {
+  openbis.mockSearchPersons([])
+  openbis.mockSearchGroups([fixture.TEST_USER_GROUP_DTO])
+
+  await common.controller.load()
+  common.controller.nodeExpand('groups')
+
+  expect(common.controller.getNodes()).toMatchObject([
+    {
+      text: 'Users',
+      expanded: false,
+      selected: false
+    },
+    {
+      text: 'Groups',
+      expanded: true,
+      selected: false
+    }
+  ])
+
+  common.context.expectNoActions()
+  common.controller.nodeCollapse('groups')
+
+  expect(common.controller.getNodes()).toMatchObject([
+    {
+      text: 'Users',
+      expanded: false,
+      selected: false
+    },
+    {
+      text: 'Groups',
+      expanded: false,
+      selected: false
+    }
+  ])
+
+  common.context.expectNoActions()
+}
diff --git a/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserControllerFilter.test.js b/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserControllerFilter.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..264a18f11649ed46bfffe9093ce12963d11f226b
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserControllerFilter.test.js
@@ -0,0 +1,55 @@
+import UserBrowserControllerTest from '@srcTest/js/components/users/browser/UserBrowserControllerTest.js'
+import openbis from '@srcTest/js/services/openbis.js'
+import fixture from '@srcTest/js/common/fixture.js'
+
+let common = null
+
+beforeEach(() => {
+  common = new UserBrowserControllerTest()
+  common.beforeEach()
+})
+
+describe(UserBrowserControllerTest.SUITE, () => {
+  test('filter', testFilter)
+})
+
+async function testFilter() {
+  openbis.mockSearchPersons([fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO])
+  openbis.mockSearchGroups([
+    fixture.TEST_USER_GROUP_DTO,
+    fixture.ANOTHER_USER_GROUP_DTO,
+    fixture.ALL_USERS_GROUP_DTO
+  ])
+
+  await common.controller.load()
+  common.controller.filterChange('ANOTHER')
+
+  expect(common.controller.getNodes()).toMatchObject([
+    {
+      text: 'Users',
+      expanded: true,
+      selected: false,
+      children: [
+        {
+          text: fixture.ANOTHER_USER_DTO.userId,
+          expanded: false,
+          selected: false
+        }
+      ]
+    },
+    {
+      text: 'Groups',
+      expanded: true,
+      selected: false,
+      children: [
+        {
+          text: fixture.ANOTHER_USER_GROUP_DTO.code,
+          expanded: false,
+          selected: false
+        }
+      ]
+    }
+  ])
+
+  common.context.expectNoActions()
+}
diff --git a/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserControllerLoad.test.js b/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserControllerLoad.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..c219f4255f0e626e04dd872f78a6de2e0c7cb5f1
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserControllerLoad.test.js
@@ -0,0 +1,40 @@
+import UserBrowserControllerTest from '@srcTest/js/components/users/browser/UserBrowserControllerTest.js'
+import openbis from '@srcTest/js/services/openbis.js'
+import fixture from '@srcTest/js/common/fixture.js'
+
+let common = null
+
+beforeEach(() => {
+  common = new UserBrowserControllerTest()
+  common.beforeEach()
+})
+
+describe(UserBrowserControllerTest.SUITE, () => {
+  test('load', testLoad)
+})
+
+async function testLoad() {
+  openbis.mockSearchPersons([fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO])
+  openbis.mockSearchGroups([
+    fixture.TEST_USER_GROUP_DTO,
+    fixture.ANOTHER_USER_GROUP_DTO,
+    fixture.ALL_USERS_GROUP_DTO
+  ])
+
+  await common.controller.load()
+
+  expect(common.controller.getNodes()).toMatchObject([
+    {
+      text: 'Users',
+      expanded: false,
+      selected: false
+    },
+    {
+      text: 'Groups',
+      expanded: false,
+      selected: false
+    }
+  ])
+
+  common.context.expectNoActions()
+}
diff --git a/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserControllerSelectNode.test.js b/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserControllerSelectNode.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..9d6e88713b39830ac1a7b47107021680afa6b33e
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserControllerSelectNode.test.js
@@ -0,0 +1,178 @@
+import UserBrowserControllerTest from '@srcTest/js/components/users/browser/UserBrowserControllerTest.js'
+import openbis from '@srcTest/js/services/openbis.js'
+import fixture from '@srcTest/js/common/fixture.js'
+
+let common = null
+
+beforeEach(() => {
+  common = new UserBrowserControllerTest()
+  common.beforeEach()
+})
+
+describe(UserBrowserControllerTest.SUITE, () => {
+  test('select node', testSelectNode)
+  test('select another node', testSelectAnotherNode)
+  test('select virtual node', testSelectVirtualNode)
+})
+
+async function testSelectNode() {
+  openbis.mockSearchPersons([fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO])
+  openbis.mockSearchGroups([
+    fixture.TEST_USER_GROUP_DTO,
+    fixture.ANOTHER_USER_GROUP_DTO,
+    fixture.ALL_USERS_GROUP_DTO
+  ])
+
+  await common.controller.load()
+
+  common.controller.nodeSelect('users/' + fixture.TEST_USER_DTO.userId)
+
+  expect(common.controller.getNodes()).toMatchObject([
+    {
+      text: 'Users',
+      expanded: false,
+      selected: false,
+      children: [
+        {
+          text: fixture.ANOTHER_USER_DTO.userId,
+          expanded: false,
+          selected: false
+        },
+        {
+          text: fixture.TEST_USER_DTO.userId,
+          expanded: false,
+          selected: true
+        }
+      ]
+    },
+    {
+      text: 'Groups',
+      expanded: false,
+      selected: false,
+      children: [
+        {
+          text: fixture.ALL_USERS_GROUP_DTO.code,
+          expanded: false,
+          selected: false
+        },
+        {
+          text: fixture.ANOTHER_USER_GROUP_DTO.code,
+          expanded: false,
+          selected: false
+        },
+        {
+          text: fixture.TEST_USER_GROUP_DTO.code,
+          expanded: false,
+          selected: false
+        }
+      ]
+    }
+  ])
+  common.expectOpenUserAction(fixture.TEST_USER_DTO.userId)
+
+  common.controller.nodeSelect('groups/' + fixture.ANOTHER_USER_GROUP_DTO.code)
+
+  expect(common.controller.getNodes()).toMatchObject([
+    {
+      text: 'Users',
+      expanded: false,
+      selected: false,
+      children: [
+        {
+          text: fixture.ANOTHER_USER_DTO.userId,
+          expanded: false,
+          selected: false
+        },
+        {
+          text: fixture.TEST_USER_DTO.userId,
+          expanded: false,
+          selected: false
+        }
+      ]
+    },
+    {
+      text: 'Groups',
+      expanded: false,
+      selected: false,
+      children: [
+        {
+          text: fixture.ALL_USERS_GROUP_DTO.code,
+          expanded: false,
+          selected: false
+        },
+        {
+          text: fixture.ANOTHER_USER_GROUP_DTO.code,
+          expanded: false,
+          selected: true
+        },
+        {
+          text: fixture.TEST_USER_GROUP_DTO.code,
+          expanded: false,
+          selected: false
+        }
+      ]
+    }
+  ])
+  common.expectOpenGroupAction(fixture.ANOTHER_USER_GROUP_DTO.code)
+}
+
+async function testSelectAnotherNode() {
+  openbis.mockSearchPersons([fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO])
+  openbis.mockSearchGroups([])
+
+  await common.controller.load()
+  common.controller.nodeSelect('users/' + fixture.TEST_USER_DTO.userId)
+  common.controller.nodeSelect('users/' + fixture.ANOTHER_USER_DTO.userId)
+
+  expect(common.controller.getNodes()).toMatchObject([
+    {
+      text: 'Users',
+      expanded: false,
+      selected: false,
+      children: [
+        {
+          text: fixture.ANOTHER_USER_DTO.userId,
+          expanded: false,
+          selected: true
+        },
+        {
+          text: fixture.TEST_USER_DTO.userId,
+          expanded: false,
+          selected: false
+        }
+      ]
+    },
+    {
+      text: 'Groups',
+      expanded: false,
+      selected: false,
+      children: []
+    }
+  ])
+
+  common.expectOpenUserAction(fixture.TEST_USER_DTO.userId)
+  common.expectOpenUserAction(fixture.ANOTHER_USER_DTO.userId)
+}
+
+async function testSelectVirtualNode() {
+  openbis.mockSearchPersons([fixture.TEST_USER_DTO, fixture.ANOTHER_USER_DTO])
+  openbis.mockSearchGroups([])
+
+  await common.controller.load()
+  common.controller.nodeSelect('users')
+
+  expect(common.controller.getNodes()).toMatchObject([
+    {
+      text: 'Users',
+      expanded: false,
+      selected: true
+    },
+    {
+      text: 'Groups',
+      expanded: false,
+      selected: false
+    }
+  ])
+
+  common.context.expectNoActions()
+}
diff --git a/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserControllerTest.js b/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserControllerTest.js
new file mode 100644
index 0000000000000000000000000000000000000000..9bde4e3bd654fabc49f5fa90e6a74423ffca3c1d
--- /dev/null
+++ b/openbis_ng_ui/srcTest/js/components/users/browser/UserBrowserControllerTest.js
@@ -0,0 +1,29 @@
+import UserBrowserController from '@src/js/components/users/browser/UserBrowserController.js'
+import ComponentContext from '@srcTest/js/components/common/ComponentContext.js'
+import pages from '@src/js/common/consts/pages.js'
+import objectType from '@src/js/common/consts/objectType.js'
+import actions from '@src/js/store/actions/actions.js'
+
+export default class UserBrowserControllerTest {
+  static SUITE = 'UserBrowserController'
+
+  beforeEach() {
+    jest.resetAllMocks()
+
+    this.context = new ComponentContext()
+    this.controller = new UserBrowserController()
+    this.controller.init(this.context)
+  }
+
+  expectOpenUserAction(userId) {
+    this.context.expectAction(
+      actions.objectOpen(pages.USERS, objectType.USER, userId)
+    )
+  }
+
+  expectOpenGroupAction(groupId) {
+    this.context.expectAction(
+      actions.objectOpen(pages.USERS, objectType.USER_GROUP, groupId)
+    )
+  }
+}