Просмотр исходного кода

Merge pull request #671 from smiclea/unit-tests

Implement unit testing framework and add unit tests
Nashwan Azhari 4 лет назад
Родитель
Сommit
c624aa58a3
100 измененных файлов с 1013 добавлено и 525 удалено
  1. 0 1
      .eslintignore
  2. 2 0
      .eslintrc
  3. 1 1
      .storybook/preview.js
  4. 2 3
      babel.config.js
  5. 202 0
      jest.config.ts
  6. 13 4
      package.json
  7. 0 16
      private/jest/componentsMock.js
  8. 0 1
      private/jest/fileMock.js
  9. 0 4
      private/jest/setupTests.js
  10. 0 3
      private/jest/shim.js
  11. 13 2
      src/components/App.tsx
  12. 99 0
      src/components/modules/DashboardModule/DashboardActivity/DashboardActivity.spec.tsx
  13. 4 4
      src/components/modules/DashboardModule/DashboardActivity/DashboardActivity.tsx
  14. 85 0
      src/components/modules/DashboardModule/DashboardBarChart/DashboardBarChart.spec.tsx
  15. 2 4
      src/components/modules/DashboardModule/DashboardBarChart/DashboardBarChart.tsx
  16. 1 1
      src/components/modules/DashboardModule/DashboardExecutions/DashboardExecutions.tsx
  17. 1 2
      src/components/modules/DetailsModule/DetailsContentHeader/DetailsContentHeader.tsx
  18. 1 2
      src/components/modules/EndpointModule/ChooseProvider/ChooseProvider.tsx
  19. 11 11
      src/components/modules/EndpointModule/EndpointDetailsContent/EndpointDetailsContent.tsx
  20. 1 4
      src/components/modules/EndpointModule/EndpointDuplicateOptions/EndpointDuplicateOptions.tsx
  21. 4 5
      src/components/modules/EndpointModule/EndpointListItem/EndpointListItem.tsx
  22. 1 4
      src/components/modules/EndpointModule/EndpointLogos/EndpointLogos.tsx
  23. 1 1
      src/components/modules/EndpointModule/EndpointModal/EndpointModal.tsx
  24. 7 7
      src/components/modules/EndpointModule/EndpointValidation/EndpointValidation.tsx
  25. 0 3
      src/components/modules/LoginModule/LoginForm/LoginForm.tsx
  26. 1 2
      src/components/modules/LoginModule/LoginFormField/LoginFormField.tsx
  27. 1 2
      src/components/modules/LoginModule/LoginOptions/LoginOptions.tsx
  28. 1 1
      src/components/modules/NavigationModule/DetailsNavigation/test.tsx
  29. 0 2
      src/components/modules/NavigationModule/Navigation/Navigation.tsx
  30. 0 1
      src/components/modules/NavigationModule/NavigationMini/NavigationMini.tsx
  31. 3 9
      src/components/modules/ProjectModule/ProjectDetailsContent/ProjectDetailsContent.tsx
  32. 0 2
      src/components/modules/ProjectModule/ProjectDetailsContent/test.tsx
  33. 5 7
      src/components/modules/ProjectModule/ProjectListItem/ProjectListItem.tsx
  34. 0 6
      src/components/modules/ProjectModule/ProjectMemberModal/ProjectMemberModal.tsx
  35. 0 3
      src/components/modules/ProjectModule/ProjectModal/ProjectModal.tsx
  36. 3 4
      src/components/modules/TransferModule/Executions/Executions.tsx
  37. 7 9
      src/components/modules/TransferModule/MainDetails/MainDetails.tsx
  38. 0 1
      src/components/modules/TransferModule/MigrationDetailsContent/MigrationDetailsContent.tsx
  39. 0 3
      src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx
  40. 2 3
      src/components/modules/TransferModule/ReplicaExecutionOptions/ReplicaExecutionOptions.tsx
  41. 2 2
      src/components/modules/TransferModule/ReplicaMigrationOptions/ReplicaMigrationOptions.tsx
  42. 1 2
      src/components/modules/TransferModule/Schedule/Schedule.tsx
  43. 1 10
      src/components/modules/TransferModule/ScheduleItem/ScheduleItem.tsx
  44. 1 1
      src/components/modules/TransferModule/TaskItem/TaskItem.tsx
  45. 0 1
      src/components/modules/TransferModule/Tasks/Tasks.tsx
  46. 1 4
      src/components/modules/TransferModule/Timeline/Timeline.tsx
  47. 2 2
      src/components/modules/TransferModule/TransferDetailsTable/TransferDetailsTable.tsx
  48. 1 2
      src/components/modules/TransferModule/TransferItemModal/TransferItemModal.tsx
  49. 4 4
      src/components/modules/TransferModule/TransferListItem/TransferListItem.tsx
  50. 0 0
      src/components/modules/TransferModule/TransferListItem/images/arrow.svg
  51. 0 0
      src/components/modules/TransferModule/TransferListItem/images/schedule.svg
  52. 6 0
      src/components/modules/TransferModule/TransferListItem/package.json
  53. 3 3
      src/components/modules/TransferModule/TransferListItem/story.tsx
  54. 0 0
      src/components/modules/TransferModule/TransferListItem/test.tsx
  55. 6 9
      src/components/modules/UserModule/UserDetailsContent/UserDetailsContent.tsx
  56. 6 7
      src/components/modules/UserModule/UserListItem/UserListItem.tsx
  57. 0 3
      src/components/modules/UserModule/UserModal/UserModal.tsx
  58. 1 1
      src/components/modules/WizardModule/WizardBreadcrumbs/WizardBreadcrumbs.tsx
  59. 0 3
      src/components/modules/WizardModule/WizardEndpointList/WizardEndpointList.tsx
  60. 4 7
      src/components/modules/WizardModule/WizardInstances/WizardInstances.tsx
  61. 2 2
      src/components/modules/WizardModule/WizardNetworks/WizardNetworks.tsx
  62. 0 1
      src/components/modules/WizardModule/WizardOptions/WizardOptions.tsx
  63. 0 1
      src/components/modules/WizardModule/WizardStorage/WizardStorage.tsx
  64. 7 10
      src/components/modules/WizardModule/WizardSummary/WizardSummary.tsx
  65. 0 1
      src/components/modules/WizardModule/WizardType/WizardType.tsx
  66. 1 1
      src/components/smart/AssessmentsPage/AssessmentsPage.tsx
  67. 1 1
      src/components/smart/DashboardPage/DashboardPage.tsx
  68. 1 1
      src/components/smart/EndpointsPage/EndpointsPage.tsx
  69. 1 1
      src/components/smart/LogsPage/LogsPage.tsx
  70. 3 3
      src/components/smart/MigrationsPage/MigrationsPage.tsx
  71. 1 1
      src/components/smart/MinionPoolsPage/MinionPoolsPage.tsx
  72. 0 0
      src/components/smart/PageHeader/PageHeader.tsx
  73. 0 0
      src/components/smart/PageHeader/package.json
  74. 0 0
      src/components/smart/PageHeader/story.tsx
  75. 1 1
      src/components/smart/ProjectsPage/ProjectsPage.tsx
  76. 3 3
      src/components/smart/ReplicasPage/ReplicasPage.tsx
  77. 1 1
      src/components/smart/UsersPage/UsersPage.tsx
  78. 71 0
      src/components/ui/AlertModal/AlertModal.spec.tsx
  79. 7 7
      src/components/ui/AlertModal/AlertModal.tsx
  80. 0 56
      src/components/ui/AlertModal/test.tsx
  81. 46 0
      src/components/ui/Arrow/Arrow.spec.tsx
  82. 36 0
      src/components/ui/AutocompleteInput/AutocompleteInput.spec.tsx
  83. 0 2
      src/components/ui/AutocompleteInput/AutocompleteInput.tsx
  84. 37 0
      src/components/ui/Button/Button.spec.tsx
  85. 0 43
      src/components/ui/Button/test.tsx
  86. 39 0
      src/components/ui/Checkbox/Checkbox.spec.tsx
  87. 0 2
      src/components/ui/Checkbox/Checkbox.tsx
  88. 0 44
      src/components/ui/Checkbox/test.tsx
  89. 14 15
      src/components/ui/CopyButton/CopyButton.spec.tsx
  90. 38 0
      src/components/ui/CopyMultilineValue/CopyMultilineValue.spec.tsx
  91. 0 2
      src/components/ui/CopyMultilineValue/CopyMultilineValue.tsx
  92. 0 38
      src/components/ui/CopyMultilineValue/test.tsx
  93. 34 0
      src/components/ui/CopyValue/CopyValue.spec.tsx
  94. 0 3
      src/components/ui/CopyValue/CopyValue.tsx
  95. 0 39
      src/components/ui/CopyValue/test.tsx
  96. 61 0
      src/components/ui/DatetimePicker/DatetimePicker.spec.tsx
  97. 0 1
      src/components/ui/DatetimePicker/DatetimePicker.tsx
  98. 0 42
      src/components/ui/DatetimePicker/test.tsx
  99. 94 0
      src/components/ui/Dropdowns/ActionDropdown/ActionDropdown.spec.tsx
  100. 1 4
      src/components/ui/Dropdowns/ActionDropdown/ActionDropdown.tsx

+ 0 - 1
.eslintignore

@@ -1 +0,0 @@
-*.js

+ 2 - 0
.eslintrc

@@ -32,6 +32,8 @@
     "*.log",
     "*.jpg",
     "*.woff",
+    "*.js",
+    "*.snap",
     "src/**/test.tsx",
     "src/**/package.json"
   ],

+ 1 - 1
.storybook/preview.js

@@ -3,7 +3,7 @@ import { addDecorator } from '@storybook/react'
 import styled, { createGlobalStyle } from 'styled-components'
 
 import { ThemePalette, ThemeProps } from '@src/components/Theme'
-import Fonts from '../src/components/atoms/Fonts'
+import Fonts from '@src/components/ui/Fonts'
 import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
 
 const Wrapper = styled.div`

+ 2 - 3
babel.config.js

@@ -3,7 +3,7 @@ module.exports = api => {
 
   const common = {
     presets: [
-      ['@babel/env', { targets: { node: true } }],
+      ['@babel/env', { targets: { node: 'current' } }],
       '@babel/typescript',
       '@babel/react',
     ],
@@ -20,8 +20,7 @@ module.exports = api => {
       '@babel/plugin-proposal-optional-chaining',
     ],
   }
-
-  if (process.env.NODE_MODE === 'development') {
+  if (process.env.NODE_MODE === 'development' || process.env.NODE_ENV === 'test') {
     common.plugins.push(['babel-plugin-styled-components', { displayName: true, minify: false }])
   } else {
     common.plugins.push(['babel-plugin-styled-components', { displayName: false, minify: true }])

+ 202 - 0
jest.config.ts

@@ -0,0 +1,202 @@
+/*
+ * For a detailed explanation regarding each configuration property and type check, visit:
+ * https://jestjs.io/docs/configuration
+ */
+
+export default {
+  // All imported modules in your tests should be mocked automatically
+  // automock: false,
+
+  // Stop running tests after `n` failures
+  // bail: 0,
+
+  // The directory where Jest should store its cached dependency information
+  // cacheDirectory: "/private/var/folders/gx/mbf75kfj2l3c7lpwklx2ltnw0000gn/T/jest_dx",
+
+  // Automatically clear mock calls and instances between every test
+  clearMocks: true,
+
+  // Indicates whether the coverage information should be collected while executing the test
+  // collectCoverage: false,
+
+  // An array of glob patterns indicating a set of files for which coverage information should be collected
+  // collectCoverageFrom: undefined,
+
+  // The directory where Jest should output its coverage files
+  // coverageDirectory: undefined,
+
+  // An array of regexp pattern strings used to skip coverage collection
+  // coveragePathIgnorePatterns: [
+  //   "/node_modules/"
+  // ],
+
+  // Indicates which provider should be used to instrument code for coverage
+  coverageProvider: 'v8',
+
+  // A list of reporter names that Jest uses when writing coverage reports
+  // coverageReporters: [
+  //   "json",
+  //   "text",
+  //   "lcov",
+  //   "clover"
+  // ],
+
+  // An object that configures minimum threshold enforcement for coverage results
+  // coverageThreshold: undefined,
+
+  // A path to a custom dependency extractor
+  // dependencyExtractor: undefined,
+
+  // Make calling deprecated APIs throw helpful error messages
+  // errorOnDeprecated: false,
+
+  // Force coverage collection from ignored files using an array of glob patterns
+  // forceCoverageMatch: [],
+
+  // A path to a module which exports an async function that is triggered once before all test suites
+  // globalSetup: undefined,
+
+  // A path to a module which exports an async function that is triggered once after all test suites
+  // globalTeardown: undefined,
+
+  // A set of global variables that need to be available in all test environments
+  // globals: {},
+
+  // The maximum amount of workers used to run your tests. Can be specified as % or a number.
+  // E.g.maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number.maxWorkers: 2 will use a maximum of 2 workers.
+  // maxWorkers: "50%",
+
+  // An array of directory names to be searched recursively up from the requiring module's location
+  // moduleDirectories: [
+  //   "node_modules"
+  // ],
+
+  // An array of file extensions your modules use
+  // moduleFileExtensions: [
+  //   "js",
+  //   "jsx",
+  //   "ts",
+  //   "tsx",
+  //   "json",
+  //   "node"
+  // ],
+
+  // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
+  moduleNameMapper: {
+    '@src/(.*)': '<rootDir>/src/$1',
+    '@tests/(.*)': '<rootDir>/tests/$1',
+  },
+
+  // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
+  // modulePathIgnorePatterns: [],
+
+  // Activates notifications for test results
+  // notify: false,
+
+  // An enum that specifies notification mode. Requires { notify: true }
+  // notifyMode: "failure-change",
+
+  // A preset that is used as a base for Jest's configuration
+  // preset: undefined,
+
+  // Run tests from one or more projects
+  // projects: undefined,
+
+  // Use this configuration option to add custom reporters to Jest
+  // reporters: undefined,
+
+  // Automatically reset mock state between every test
+  // resetMocks: false,
+
+  // Reset the module registry before running each individual test
+  // resetModules: false,
+
+  // A path to a custom resolver
+  // resolver: undefined,
+
+  // Automatically restore mock state between every test
+  // restoreMocks: false,
+
+  // The root directory that Jest should scan for tests and modules within
+  // rootDir: undefined,
+
+  // A list of paths to directories that Jest should use to search for files in
+  // roots: [
+  //   "<rootDir>"
+  // ],
+
+  // Allows you to use a custom runner instead of Jest's default test runner
+  // runner: "jest-runner",
+
+  // The paths to modules that run some code to configure or set up the testing environment before each test
+  setupFiles: [
+    '<rootDir>/tests/setup.js',
+  ],
+
+  // A list of paths to modules that run some code to configure or set up the testing framework before each test
+  // setupFilesAfterEnv: [],
+
+  // The number of seconds after which a test is considered as slow and reported as such in the results.
+  // slowTestThreshold: 5,
+
+  // A list of paths to snapshot serializer modules Jest should use for snapshot testing
+  // snapshotSerializers: [],
+
+  // The test environment that will be used for testing
+  testEnvironment: 'jsdom',
+
+  // Options that will be passed to the testEnvironment
+  // testEnvironmentOptions: {},
+
+  // Adds a location field to test results
+  // testLocationInResults: false,
+
+  // The glob patterns Jest uses to detect test files
+  testMatch: [
+    '**/*.spec.tsx',
+  ],
+
+  // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
+  // testPathIgnorePatterns: [
+  //   "/node_modules/"
+  // ],
+
+  // The regexp pattern or array of patterns that Jest uses to detect test files
+  // testRegex: [],
+
+  // This option allows the use of a custom results processor
+  // testResultsProcessor: undefined,
+
+  // This option allows use of a custom test runner
+  // testRunner: "jest-circus/runner",
+
+  // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
+  // testURL: "http://localhost",
+
+  // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
+  // timers: "real",
+
+  // A map from regular expressions to paths to transformers
+  transform: {
+    '\\.[jt]sx?$': 'babel-jest',
+    '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/tests/fileTransform.js',
+  },
+
+  // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
+  // transformIgnorePatterns: [
+  //   "/node_modules/",
+  //   "\\.pnp\\.[^\\/]+$"
+  // ],
+
+  // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
+  // unmockedModulePathPatterns: undefined,
+
+  // Indicates whether each individual test should be reported during the run
+  // verbose: undefined,
+
+  // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
+  // watchPathIgnorePatterns: [],
+
+  // Whether to use watchman for file crawling
+  // watchman: true,
+}

+ 13 - 4
package.json

@@ -13,25 +13,32 @@
     "server-debug": "node --inspect server",
     "tsc": "npx tsc --skipLibCheck",
     "eslint": "npx eslint \"src/**\" \"server/**\"",
-    "test-release": "node ./server/testRelease",
+    "test": "jest",
+    "test-release": "node ./tests/testRelease",
+    "test-coverage": "node ./tests/testCoverage",
     "storybook": "start-storybook"
   },
   "devDependencies": {
     "@storybook/react": "^6.4.13",
+    "@testing-library/dom": "^8.11.1",
+    "@testing-library/react": "^12.1.2",
+    "@testing-library/user-event": "^13.5.0",
     "@types/connect": "^3.4.33",
     "@types/express": "^4.17.6",
     "@types/file-saver": "^2.0.1",
+    "@types/jest": "^27.0.2",
     "@types/js-cookie": "^2.2.6",
     "@types/moment-timezone": "^0.5.13",
     "@types/react-collapse": "^5.0.0",
     "@types/react-dom": "^16.9.8",
     "@types/react-modal": "^3.10.5",
     "@types/react-notification-system": "^0.2.39",
-    "@types/react-router-dom": "^5.1.5",
+    "@types/react-router-dom": "^5.1.2",
     "@types/react-tooltip": "^4.2.4",
     "@types/styled-components": "^5.1.0",
     "@typescript-eslint/eslint-plugin": "^5.6.0",
     "@typescript-eslint/parser": "^5.6.0",
+    "cross-spawn": "^7.0.3",
     "cypress": "^3.2.0",
     "eslint": "^8.2.0",
     "eslint-config-airbnb": "19.0.0",
@@ -41,6 +48,7 @@
     "eslint-plugin-jsx-a11y": "^6.5.1",
     "eslint-plugin-react": "^7.27.0",
     "eslint-plugin-react-hooks": "^4.3.0",
+    "jest": "^27.3.1",
     "nodemon": "^2.0.4"
   },
   "dependencies": {
@@ -79,7 +87,7 @@
     "path": "^0.12.7",
     "react": "^16.13.1",
     "react-collapse": "^5.0.1",
-    "react-datetime": "^2.10.3",
+    "react-datetime": "^3.1.1",
     "react-dom": "^16.13.1",
     "react-hot-loader": "^4.12.17",
     "react-modal": "^3.11.2",
@@ -93,6 +101,7 @@
     "rimraf": "^2.6.2",
     "styled-components": "^4.4.1",
     "tai-password-strength": "^1.1.2",
+    "ts-node": "10.4.0",
     "typescript": "^4.5.3",
     "url-loader": "^4.1.0",
     "webpack": "^4.41.2",
@@ -120,6 +129,6 @@
     "ua-parser-js": "^0.7.24",
     "underscore": "^1.12.1",
     "y18n": "^3.2.2",
-    "yargs-parser": "^13.1.2"
+    "yargs-parser": "^20.2.9"
   }
 }

+ 0 - 16
private/jest/componentsMock.js

@@ -1,16 +0,0 @@
-// https://github.com/diegohaz/arc/wiki/Testing-components
-import React from 'react'
-import PropTypes from 'prop-types'
-
-module.exports = new Proxy({}, {
-  get: (target, property) => {
-    const Mock = props => React.createElement('span', null, props.children)
-
-    Mock.displayName = property
-    Mock.propTypes = {
-      children: PropTypes.any,
-    }
-
-    return Mock
-  },
-})

+ 0 - 1
private/jest/fileMock.js

@@ -1 +0,0 @@
-export default 'file'

+ 0 - 4
private/jest/setupTests.js

@@ -1,4 +0,0 @@
-import { configure } from 'enzyme'
-import Adapter from 'enzyme-adapter-react-16'
-
-configure({ adapter: new Adapter() })

+ 0 - 3
private/jest/shim.js

@@ -1,3 +0,0 @@
-global.requestAnimationFrame = /* istanbul ignore next */ (callback) => {
-  setTimeout(callback, 0)
-}

+ 13 - 2
src/components/App.tsx

@@ -120,6 +120,7 @@ class App extends React.Component<{}, State> {
     }) => (
       <Route
         path={options.path}
+        // @ts-ignore
         exact={options.exact}
         render={() => (
           <MessagePage
@@ -142,6 +143,7 @@ class App extends React.Component<{}, State> {
           showAuthAnimation: true,
         })
       }
+      // @ts-ignore
       return <Route path={path} component={component} exact={exact} />
     }
 
@@ -173,6 +175,7 @@ class App extends React.Component<{}, State> {
         })
       }
       if (userStore.loggedUser?.isAdmin) {
+        // @ts-ignore
         return <Route path={actualPath} exact={exact} component={component} />
       }
       return null
@@ -184,9 +187,14 @@ class App extends React.Component<{}, State> {
         <Router>
           <Switch>
             {configLoader.isFirstLaunch ? (
+            // @ts-ignore
               <Route path="/" component={SetupPage} exact />
+            // @ts-ignore
             ) : renderRoute('/', DashboardPage, true)}
-            <Route path="/login" component={LoginPage} />
+            {
+              // @ts-ignore
+              <Route path="/login" component={LoginPage} />
+            }
             {renderRoute('/dashboard', DashboardPage)}
             {renderRoute('/replicas', ReplicasPage, true)}
             {renderRoute('/replicas/:id', ReplicaDetailsPage, true)}
@@ -208,7 +216,10 @@ class App extends React.Component<{}, State> {
             {renderOptionalRoute('projects', ProjectDetailsPage, '/projects/:id')}
             {renderOptionalRoute('logging', LogsPage)}
             {renderRoute('/streamlog', LogStreamPage)}
-            <Route component={MessagePage} />
+            {
+              // @ts-ignore
+              <Route component={MessagePage} />
+            }
           </Switch>
         </Router>
         <NotificationsModule />

+ 99 - 0
src/components/modules/DashboardModule/DashboardActivity/DashboardActivity.spec.tsx

@@ -0,0 +1,99 @@
+/*
+Copyright (C) 2021  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import React from 'react'
+import { render } from '@testing-library/react'
+import TestUtils from '@tests/TestUtils'
+import { NotificationItemData } from '@src/@types/NotificationItem'
+import progressImage from '@src/components/ui/StatusComponents/StatusIcon/images/progress'
+import { ThemePalette } from '@src/components/Theme'
+import DashboardActivity from '.'
+
+const encodedProgressImage = encodeURIComponent(progressImage(ThemePalette.grayscale[3], ThemePalette.primary))
+
+jest.mock('react-router-dom', () => ({ Link: 'a' }))
+
+const ITEMS: NotificationItemData[] = [
+  {
+    id: '1',
+    type: 'replica',
+    status: 'ERROR',
+    name: 'Replica 1',
+    description: 'Replica 1 description',
+  },
+  {
+    id: '2',
+    type: 'migration',
+    status: 'RUNNING',
+    name: 'Migration 1',
+    description: 'Migration 1 description',
+  },
+  {
+    id: '3',
+    type: 'migration',
+    status: 'COMPLETED',
+    name: 'Migration 2',
+    description: 'Migration 2 description',
+  },
+]
+
+describe('DashboardActivity', () => {
+  it('renders no recent activity', () => {
+    render(<DashboardActivity notificationItems={[]} />)
+    expect(TestUtils.select('DashboardActivity__Message')!.textContent).toContain('There is no recent activity')
+  })
+
+  it('fires new click', () => {
+    const onNewClick = jest.fn()
+    render(<DashboardActivity notificationItems={[]} onNewClick={onNewClick} />)
+    TestUtils.select('Button__StyledButton')!.click()
+    expect(onNewClick).toHaveBeenCalled()
+  })
+
+  it('renders loading', () => {
+    const { rerender } = render(<DashboardActivity notificationItems={[]} loading />)
+    expect(TestUtils.select('DashboardActivity__LoadingWrapper')).toBeTruthy()
+
+    rerender(<DashboardActivity notificationItems={[]} />)
+    expect(TestUtils.select('DashboardActivity__LoadingWrapper')).toBeFalsy()
+  })
+
+  it('renders all items', () => {
+    render(<DashboardActivity notificationItems={ITEMS} />)
+
+    const listItemsEl = TestUtils.selectAll('DashboardActivity__ListItem')
+    expect(listItemsEl.length).toBe(ITEMS.length)
+  })
+
+  it.each`
+    idx  | href                     | expectedStatusIcon
+    ${0} | ${'/replicas/1'}         | ${'error-hollow.svg'}
+    ${1} | ${'/migrations/2/tasks'} | ${encodedProgressImage}
+    ${2} | ${'/migrations/3'}       | ${'success-hollow.svg'}
+  `('renders item with href $href', ({
+    idx, href, expectedStatusIcon,
+  }) => {
+    render(<DashboardActivity notificationItems={ITEMS} />)
+
+    const itemElement = TestUtils.selectAll('DashboardActivity__ListItem')[idx]
+    expect(itemElement.getAttribute('to')).toBe(href)
+
+    const background = window.getComputedStyle(TestUtils.select('StatusIcon__Wrapper', itemElement)!).background
+    expect(background).toContain(expectedStatusIcon)
+
+    expect(TestUtils.select('NotificationDropdown__ItemReplicaBadge', itemElement)!.textContent).toContain(ITEMS[idx].type === 'replica' ? 'RE' : 'MI')
+    expect(TestUtils.select('NotificationDropdown__ItemTitle', itemElement)!.textContent).toContain(ITEMS[idx].name)
+    expect(TestUtils.select('NotificationDropdown__ItemDescription', itemElement)!.textContent).toContain(ITEMS[idx].description)
+  })
+})

+ 4 - 4
src/components/modules/DashboardModule/DashboardActivity/DashboardActivity.tsx

@@ -87,10 +87,10 @@ const Message = styled.div<any>`
 
 type Props = {
   notificationItems: NotificationItemData[],
-  style: any,
-  loading: boolean,
-  large: boolean,
-  onNewClick: () => void,
+  style?: React.CSSProperties | null,
+  loading?: boolean,
+  large?: boolean,
+  onNewClick?: () => void,
 }
 @observer
 class DashboardActivity extends React.Component<Props> {

+ 85 - 0
src/components/modules/DashboardModule/DashboardBarChart/DashboardBarChart.spec.tsx

@@ -0,0 +1,85 @@
+/*
+Copyright (C) 2021  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import React from 'react'
+import { render } from '@testing-library/react'
+import TestUtils from '@tests/TestUtils'
+import { ThemePalette } from '@src/components/Theme'
+import userEvent from '@testing-library/user-event'
+import DashboardBarChart from '.'
+
+const DATA: DashboardBarChart['props']['data'] = [
+  {
+    label: 'label 1',
+    values: [10, 15],
+    data: 'data 1',
+  },
+  {
+    label: 'label 2',
+    values: [20, 25],
+    data: 'data 2',
+  },
+]
+
+describe('DashboardBarChart', () => {
+  it('renders all data correctly', () => {
+    render(<DashboardBarChart data={DATA} yNumTicks={3} />)
+
+    // Y ticks
+
+    const yTickEl = TestUtils.selectAll('DashboardBarChart__YTick')
+    expect(yTickEl.length).toBe(3)
+    expect(yTickEl[0].textContent).toBe('0')
+    expect(yTickEl[1].textContent).toBe('20')
+    expect(yTickEl[2].textContent).toBe('40')
+
+    // Bars
+
+    const barsEl = TestUtils.selectAll('DashboardBarChart__Bar-')
+    expect(barsEl.length).toBe(DATA.length)
+    expect(barsEl[0].textContent).toBe('label 1')
+    expect(barsEl[1].textContent).toBe('label 2')
+  })
+
+  it.each`
+    barIndex | stackedBarIndex | expectedHeight                    | expectedColor
+    ${0}     | ${0}            | ${(DATA[0].values[1] / 45) * 100} | ${ThemePalette.alert}
+    ${0}     | ${1}            | ${(DATA[0].values[0] / 45) * 100} | ${ThemePalette.primary}
+    ${1}     | ${0}            | ${(DATA[1].values[1] / 45) * 100} | ${ThemePalette.alert}
+    ${1}     | ${1}            | ${(DATA[1].values[0] / 45) * 100} | ${ThemePalette.primary}
+  `('renders bar index $barIndex, stacked bar index $stackedBarIndex with height $expectedHeight and color $expectedColor', ({
+    barIndex, stackedBarIndex, expectedHeight, expectedColor,
+  }) => {
+    render(<DashboardBarChart data={DATA} yNumTicks={3} colors={[ThemePalette.alert, ThemePalette.primary]} />)
+
+    const stackedBarEl = TestUtils.selectAll('DashboardBarChart__StackedBar-', TestUtils.selectAll('DashboardBarChart__Bar-')[barIndex])[stackedBarIndex]
+    const style = window.getComputedStyle(stackedBarEl)
+
+    expect(parseFloat(style.height)).toBeCloseTo(expectedHeight)
+    expect(TestUtils.rgbToHex(style.background)).toBe(expectedColor)
+  })
+
+  it.each`
+  barIndex | stackedBarIndex | expectedData
+  ${0}     | ${0}            | ${DATA[0]}
+  ${0}     | ${1}            | ${DATA[0]}
+  ${1}     | ${0}            | ${DATA[1]}
+  ${1}     | ${1}            | ${DATA[1]}
+`('fires mouse position with correct data on bar mouse enter, bar index $barIndex, stacked bar index $stackedBarIndex', ({ barIndex, stackedBarIndex, expectedData }) => {
+    const onBarMouseEnter = jest.fn()
+    render(<DashboardBarChart data={DATA} yNumTicks={3} onBarMouseEnter={onBarMouseEnter} />)
+    userEvent.hover(TestUtils.selectAll('DashboardBarChart__StackedBar-', TestUtils.selectAll('DashboardBarChart__Bar-')[barIndex])[stackedBarIndex])
+    expect(onBarMouseEnter).toHaveBeenCalledWith({ x: 48, y: 65 }, expectedData)
+  })
+})

+ 2 - 4
src/components/modules/DashboardModule/DashboardBarChart/DashboardBarChart.tsx

@@ -16,7 +16,7 @@ import * as React from 'react'
 import { observer } from 'mobx-react'
 import styled from 'styled-components'
 
-import { ThemeProps } from '@src/components/Theme'
+import { ThemePalette, ThemeProps } from '@src/components/Theme'
 import BarChartNiceScale from './BarChartNiceScale'
 
 const Wrapper = styled.div<any>`
@@ -94,9 +94,7 @@ type DataItem = {
 }
 type Props = {
   style?: any,
-  // eslint-disable-next-line react/no-unused-prop-types
   data: DataItem[],
-  // eslint-disable-next-line react/no-unused-prop-types
   yNumTicks: number,
   colors?: string[],
   onBarMouseEnter?: (position: { x: number, y: number }, item: DataItem) => void,
@@ -192,7 +190,7 @@ class DashboardBarChart extends React.Component<Props> {
                   <StackedBar
                     // eslint-disable-next-line react/no-array-index-key
                     key={`${item.label}-${i}`}
-                    background={this.props.colors ? this.props.colors[i % this.props.colors.length] : '#0044CA'}
+                    background={this.props.colors ? this.props.colors[i % this.props.colors.length] : ThemePalette.primary}
                     height={height}
                     onMouseEnter={(evt: MouseEvent) => {
                       const onMouseEnter = this.props.onBarMouseEnter

+ 1 - 1
src/components/modules/DashboardModule/DashboardExecutions/DashboardExecutions.tsx

@@ -149,7 +149,7 @@ type State = {
   tooltipPosition: { x: number, y: number },
   tooltipData: TooltipData | null,
 }
-const COLORS = ['#F91661', '#0044CB']
+const COLORS = [ThemePalette.alert, ThemePalette.primary]
 
 @observer
 class DashboardExecutions extends React.Component<Props, State> {

+ 1 - 2
src/components/modules/DetailsModule/DetailsContentHeader/DetailsContentHeader.tsx

@@ -126,7 +126,6 @@ class DetailsContentHeader extends React.Component<Props> {
         actions={this.props.dropdownActions}
         largeItems={this.props.largeDropdownActionItems}
         style={{ marginLeft: '32px' }}
-        data-test-id="dcHeader-actionButton"
       />
     )
   }
@@ -144,7 +143,7 @@ class DetailsContentHeader extends React.Component<Props> {
   render() {
     return (
       <Wrapper>
-        <BackButton to={this.props.backLink} data-test-id="dcHeader-backButton" />
+        <BackButton to={this.props.backLink} />
         <TypeImage image={this.props.typeImage} />
         <Title>
           <Status>

+ 1 - 2
src/components/modules/EndpointModule/ChooseProvider/ChooseProvider.tsx

@@ -365,7 +365,6 @@ class ChooseProvider extends React.Component<Props, State> {
               height={128}
               key={k}
               endpoint={k}
-              data-test-id={`cProvider-endpointLogo-${k}`}
               onClick={() => { this.props.onProviderClick(k) }}
             />
           ))}
@@ -384,7 +383,7 @@ class ChooseProvider extends React.Component<Props, State> {
           multiple
           onChange={e => { this.handleFileUpload(e.target.files) }}
         />
-        <Button secondary onClick={this.props.onCancelClick} data-test-id="cProvider-cancelButton">Cancel</Button>
+        <Button secondary onClick={this.props.onCancelClick}>Cancel</Button>
       </Providers>
     )
   }

+ 11 - 11
src/components/modules/EndpointModule/EndpointDetailsContent/EndpointDetailsContent.tsx

@@ -130,7 +130,7 @@ class EndpointDetailsContent extends React.Component<Props> {
 
     return (
       <LoadingWrapper>
-        <StatusImage loading data-test-id="edContent-connLoading" />
+        <StatusImage loading />
       </LoadingWrapper>
     )
   }
@@ -169,11 +169,11 @@ class EndpointDetailsContent extends React.Component<Props> {
       const schemaField = this.props.connectionInfoSchema.find(f => f.name === key)
 
       if (configLoader.config.passwordFields.find(fn => fn === key) || key.indexOf('password') > -1) {
-        valueElement = <PasswordValue value={value} data-test-id="edContent-connPassword" />
+        valueElement = <PasswordValue value={value} />
       } else if (schemaField?.useFile) {
         valueElement = this.renderDownloadValue(value, key)
       } else {
-        valueElement = this.renderValue(value, `connValue-${key}`)
+        valueElement = this.renderValue(value)
       }
 
       return (
@@ -189,17 +189,17 @@ class EndpointDetailsContent extends React.Component<Props> {
     return (
       <Buttons>
         <MainButtons>
-          <Button onClick={this.props.onValidateClick} data-test-id="edContent-validateButton">Validate Endpoint</Button>
+          <Button onClick={this.props.onValidateClick}>Validate Endpoint</Button>
         </MainButtons>
         <DeleteButton>
-          <Button hollow alert onClick={this.props.onDeleteClick} data-test-id="edContent-deleteButton">Delete Endpoint</Button>
+          <Button hollow alert onClick={this.props.onDeleteClick}>Delete Endpoint</Button>
         </DeleteButton>
       </Buttons>
     )
   }
 
-  renderValue(value: string, dataTestId?: string) {
-    return <CopyValue data-test-id={dataTestId ? `edContent-${dataTestId}` : undefined} value={value} maxWidth="90%" />
+  renderValue(value: string) {
+    return <CopyValue value={value} maxWidth="90%" />
   }
 
   renderRegions() {
@@ -246,11 +246,11 @@ class EndpointDetailsContent extends React.Component<Props> {
           </Field>
           <Field>
             <Label>Name</Label>
-            {this.renderValue(name || '', 'name')}
+            {this.renderValue(name || '')}
           </Field>
           <Field>
             <Label>Type</Label>
-            {this.renderValue(this.props.item ? configLoader.config.providerNames[this.props.item.type] : '', 'type')}
+            {this.renderValue(this.props.item ? configLoader.config.providerNames[this.props.item.type] : '')}
           </Field>
           <Field>
             <Label>Coriolis Regions</Label>
@@ -258,11 +258,11 @@ class EndpointDetailsContent extends React.Component<Props> {
           </Field>
           <Field>
             <Label>Description</Label>
-            {description ? <CopyMultilineValue data-test-id="edContent-description" value={description} /> : <Value>-</Value>}
+            {description ? <CopyMultilineValue value={description} /> : <Value>-</Value>}
           </Field>
           <Field>
             <Label>Created</Label>
-            {this.renderValue(DateUtils.getLocalTime(created_at).format('DD/MM/YYYY HH:mm'), 'created')}
+            {this.renderValue(DateUtils.getLocalTime(created_at).format('DD/MM/YYYY HH:mm'))}
           </Field>
           <Field>
             <Label>Used in replicas/migrations ({usage.length})</Label>

+ 1 - 4
src/components/modules/EndpointModule/EndpointDuplicateOptions/EndpointDuplicateOptions.tsx

@@ -84,7 +84,6 @@ type Props = {
 type State = {
   selectedProjectId: string,
 }
-const testName = 'edOptions'
 @observer
 class EndpointDuplicateOptions extends React.Component<Props, State> {
   UNSAFE_componentWillMount() {
@@ -103,7 +102,7 @@ class EndpointDuplicateOptions extends React.Component<Props, State> {
 
   renderDuplicating() {
     return (
-      <Loading data-test-id={`${testName}-loading`}>
+      <Loading>
         <StatusImage loading />
         <Message>
           <Title>Duplicating Endpoint</Title>
@@ -119,7 +118,6 @@ class EndpointDuplicateOptions extends React.Component<Props, State> {
         <Image />
         <Form>
           <FieldInputStyled
-            data-test-id={`${testName}-field-project`}
             name="duplicate_to_project"
             label="Duplicate To Project"
             type="string"
@@ -133,7 +131,6 @@ class EndpointDuplicateOptions extends React.Component<Props, State> {
         <Buttons>
           <Button secondary onClick={this.props.onCancelClick}>Cancel</Button>
           <Button
-            data-test-id={`${testName}-duplicateButton`}
             onClick={() => { this.props.onDuplicateClick(this.state.selectedProjectId) }}
           >Duplicate
           </Button>

+ 4 - 5
src/components/modules/EndpointModule/EndpointListItem/EndpointListItem.tsx

@@ -103,15 +103,14 @@ class EndpointListItem extends React.Component<Props> {
     return (
       <Wrapper>
         <CheckboxStyled
-          data-test-id={`endpointListItem-checkbox-${this.props.item.name}`}
           checked={this.props.selected}
           onChange={this.props.onSelectedChange}
         />
-        <Content onClick={this.props.onClick} data-test-id={`endpointListItem-content-${this.props.item.name}`}>
+        <Content onClick={this.props.onClick}>
           <Image image={endpointImage} />
           <Title>
-            <TitleLabel data-test-id="endpointListItem-name">{this.props.item.name}</TitleLabel>
-            <Subtitle data-test-id="endpointListItem-description">{this.props.item.description || 'N/A'}</Subtitle>
+            <TitleLabel>{this.props.item.name}</TitleLabel>
+            <Subtitle>{this.props.item.description || 'N/A'}</Subtitle>
           </Title>
           <EndpointLogos height={42} endpoint={this.props.item.type} />
           <Created>
@@ -122,7 +121,7 @@ class EndpointListItem extends React.Component<Props> {
           </Created>
           <Usage>
             <ItemLabel>Usage</ItemLabel>
-            <ItemValue data-test-id="endpointListItem-usageCount">
+            <ItemValue>
               {this.props.getUsage(this.props.item).migrationsCount} migrations,&nbsp;
               {this.props.getUsage(this.props.item).replicasCount} replicas
             </ItemValue>

+ 1 - 4
src/components/modules/EndpointModule/EndpointLogos/EndpointLogos.tsx

@@ -42,7 +42,6 @@ type Props = {
   white?: boolean,
   baseUrl?: string,
   onClick?: () => void
-  'data-test-id'?: string,
   style?: React.CSSProperties
 }
 @observer
@@ -54,7 +53,6 @@ class EndpointLogos extends React.Component<Props> {
   renderGenericLogo(size: { w: number, h: number }) {
     return (
       <Generic
-        data-test-id="endpointLogos-genericLogo"
         size={size}
         name={this.props.endpoint || ''}
         disabled={this.props.disabled}
@@ -80,12 +78,11 @@ class EndpointLogos extends React.Component<Props> {
 
     return (
       // eslint-disable-next-line react/jsx-props-no-spreading
-      <Wrapper {...this.props} data-test-id={this.props['data-test-id'] || 'endpointLogos'}>
+      <Wrapper {...this.props}>
         <Logo
           width={size.w}
           height={size.h}
           url={imageUrl}
-          data-test-id="endpointLogos-logo"
         >
           {imageUrl ? null : this.renderGenericLogo(size)}
         </Logo>

+ 1 - 1
src/components/modules/EndpointModule/EndpointModal/EndpointModal.tsx

@@ -388,7 +388,7 @@ class EndpointModal extends React.Component<Props, State> {
     }
 
     return (
-      <Status data-test-id="endpointStatus">
+      <Status>
         <StatusHeader>
           <StatusIcon status={status} />
           <StatusMessage>{message}{showErrorButton}</StatusMessage>

+ 7 - 7
src/components/modules/EndpointModule/EndpointValidation/EndpointValidation.tsx

@@ -96,9 +96,9 @@ class EndpointValidation extends React.Component<Props> {
 
     return (
       <Loading>
-        <StatusImage loading data-test-id="eValidation-status" />
+        <StatusImage loading />
         <Message>
-          <Title data-test-id="eValidation-title">Validating Endpoint</Title>
+          <Title>Validating Endpoint</Title>
           <Subtitle>Please wait ...</Subtitle>
         </Message>
       </Loading>
@@ -112,9 +112,9 @@ class EndpointValidation extends React.Component<Props> {
 
     return (
       <Validation>
-        <StatusImage status="COMPLETED" data-test-id="eValidation-status" />
+        <StatusImage status="COMPLETED" />
         <Message>
-          <Title data-test-id="eValidation-title">Endpoint is Valid</Title>
+          <Title>Endpoint is Valid</Title>
           <Subtitle>All tests passed succesfully.</Subtitle>
         </Message>
       </Validation>
@@ -130,10 +130,10 @@ class EndpointValidation extends React.Component<Props> {
 
     return (
       <Validation>
-        <StatusImage status="ERROR" data-test-id="eValidation-status" />
+        <StatusImage status="ERROR" />
         <Message>
-          <Title data-test-id="eValidation-title">Validation Failed</Title>
-          <Error onClick={() => { this.handleCopyClick(message) }} data-test-id="eValidation-errorMessage">
+          <Title>Validation Failed</Title>
+          <Error onClick={() => { this.handleCopyClick(message) }}>
             {message}<CopyButton />
           </Error>
         </Message>

+ 0 - 3
src/components/modules/LoginModule/LoginForm/LoginForm.tsx

@@ -152,7 +152,6 @@ class LoginForm extends React.Component<Props, State> {
       <LoginError>
         <LoginErrorIcon />
         <LoginErrorText
-          data-test-id="loginForm-errorText"
           dangerouslySetInnerHTML={{ __html: errorMessage }}
         />
       </LoginError>
@@ -191,7 +190,6 @@ class LoginForm extends React.Component<Props, State> {
             value={this.state.username}
             name="username"
             onChange={e => { this.handleUsernameChange(e.target.value) }}
-            data-test-id="loginForm-usernameField"
           />
           <LoginFormField
             label="Password"
@@ -199,7 +197,6 @@ class LoginForm extends React.Component<Props, State> {
             onChange={e => { this.handlePasswordChange(e.target.value) }}
             name="password"
             type="password"
-            data-test-id="loginForm-passwordField"
           />
         </FormFields>
         {button}

+ 1 - 2
src/components/modules/LoginModule/LoginFormField/LoginFormField.tsx

@@ -42,9 +42,8 @@ type Props = {
 }
 const LoginFormField = (props: Props) => (
   <Wrapper>
-    <FormFieldLabel data-test-id="loginFormField-label">{props.label}</FormFieldLabel>
+    <FormFieldLabel>{props.label}</FormFieldLabel>
     <StyledTextInput
-      data-test-id="loginFormField-input"
       // eslint-disable-next-line react/jsx-props-no-spreading
       {...props}
       onChange={props.onChange}

+ 1 - 2
src/components/modules/LoginModule/LoginOptions/LoginOptions.tsx

@@ -105,11 +105,10 @@ const LoginOptions = (props: Props) => {
     <Wrapper>
       {buttons.map(button => (
         <Button
-          data-test-id={`loginOptions-button-${button.id}`}
           key={button.id}
           id={button.id}
         >
-          <Logo data-test-id={`loginOptions-logo-${button.id}`} id={button.id} />Sign in with {button.name}
+          <Logo id={button.id} />Sign in with {button.name}
         </Button>
       ))}
     </Wrapper>

+ 1 - 1
src/components/modules/NavigationModule/DetailsNavigation/test.tsx

@@ -26,7 +26,7 @@ const items = [
 
 describe('DetailsNavigation Component', () => {
   // it('renders 3 items', () => {
-  //   let wrapper = wrap({ items, 'data-test-id': 'dn-wrapper' })
+  //   let wrapper = wrap({ items})
   //   console.log(wrapper.find('dn-wrapper').debug())
   //   // items.forEach(item => {
   //   //   expect(wrapper.find(item.value).shallow.dive().dive()).toBe(item.label)

+ 0 - 2
src/components/modules/NavigationModule/Navigation/Navigation.tsx

@@ -356,7 +356,6 @@ class Navigation extends React.Component<Props> {
               key={item.value}
               selected={this.props.currentPage === item.value}
               to={`/${item.value}`}
-              data-test-id={`navigation-item-${item.value}`}
             >{item.label}
             </MenuItem>
           ))
@@ -416,7 +415,6 @@ class Navigation extends React.Component<Props> {
                 key={item.value}
                 selected={this.props.currentPage === item.value}
                 to={`/${item.value}`}
-                data-test-id={`${TEST_ID}-smallMenuItem-${item.value}`}
               >
                 <SmallMenuBackground />
                 {bullet ? <SmallMenuItemBullet bullet={bullet} /> : null}

+ 0 - 1
src/components/modules/NavigationModule/NavigationMini/NavigationMini.tsx

@@ -78,7 +78,6 @@ class NavigationMini extends React.Component<{}, State> {
           open={this.state.open}
           onClick={() => { this.handleMenuToggleClick() }}
           dangerouslySetInnerHTML={{ __html: menuImage() }}
-          data-test-id={`${TEST_ID}-toggleButton`}
         />
         {this.state.open ? <Stub /> : null}
         <NavigationStyled

+ 3 - 9
src/components/modules/ProjectModule/ProjectDetailsContent/ProjectDetailsContent.tsx

@@ -105,7 +105,6 @@ type Props = {
 type State = {
   showRemoveUserAlert: boolean,
 }
-const testName = 'pdContent'
 @observer
 class ProjectDetailsContent extends React.Component<Props, State> {
   state = {
@@ -185,7 +184,7 @@ class ProjectDetailsContent extends React.Component<Props, State> {
       <Info>
         <Field>
           <Label>Name</Label>
-          {this.renderValue(project.name, 'name')}
+          {this.renderValue(project.name)}
         </Field>
         <Field>
           <Label>Description</Label>
@@ -197,7 +196,7 @@ class ProjectDetailsContent extends React.Component<Props, State> {
         </Field>
         <Field>
           <Label>ID</Label>
-          {this.renderValue(project.id, 'id')}
+          {this.renderValue(project.id)}
         </Field>
         <Field>
           <Label>Enabled</Label>
@@ -238,13 +237,11 @@ class ProjectDetailsContent extends React.Component<Props, State> {
       const userRoles = getUserRoles(user)
       const columns = [
         <UserName
-          data-test-id={`pdContent-users-${user.name}`}
           disabled={!user.enabled}
           to={`/users/${user.id}`}
         >{user.name}
         </UserName>,
         <DropdownLink
-          data-test-id={`${testName}-roles-${user.name}`}
           width="214px"
           getLabel={() => (userRoles.length > 0 ? userRoles.map(r => r.label).join(', ') : 'No roles')}
           selectedItems={userRoles.map(r => r.value)}
@@ -264,7 +261,6 @@ class ProjectDetailsContent extends React.Component<Props, State> {
         />,
         <UserColumn disabled={!user.enabled}>{user.enabled ? 'Enabled' : 'Disabled'}</UserColumn>,
         <DropdownLink
-          data-test-id={`${testName}-actions-${user.name}`}
           noCheckmark
           width="82px"
           items={userActions}
@@ -282,7 +278,6 @@ class ProjectDetailsContent extends React.Component<Props, State> {
 
     return (
       <TableStyled
-        data-test-id={`${testName}-members`}
         header={['Member', 'Roles', 'Status', '']}
         items={rows}
         noItemsLabel="No members available!"
@@ -291,10 +286,9 @@ class ProjectDetailsContent extends React.Component<Props, State> {
     )
   }
 
-  renderValue(value: string, dataTestId: string) {
+  renderValue(value: string) {
     return value !== '-' ? (
       <CopyValue
-        data-test-id={`${testName}-${dataTestId}`}
         value={value}
         maxWidth="90%"
       />

+ 0 - 2
src/components/modules/ProjectModule/ProjectDetailsContent/test.tsx

@@ -71,10 +71,8 @@ describe('ProjectDetailsContent Component', () => {
     expect(wrapper.find('name').prop('value')).toBe('Project 1')
     expect(wrapper.find('id').prop('value')).toBe('project-1')
     let rows = wrapper.find('members').prop('items')
-    expect(rows[0][0].props['data-test-id']).toBe('pdContent-users-User 1')
     expect(rows[0][1].props.selectedItems.length).toBe(1)
     expect(rows[0][1].props.selectedItems[0]).toBe('role-1')
-    expect(rows[1][0].props['data-test-id']).toBe('pdContent-users-User 2')
     expect(rows[1][1].props.selectedItems.length).toBe(1)
     expect(rows[1][1].props.selectedItems[0]).toBe('role-2')
   })

+ 5 - 7
src/components/modules/ProjectModule/ProjectListItem/ProjectListItem.tsx

@@ -96,7 +96,6 @@ type Props = {
   isCurrentProject: (projectId: string) => boolean,
   onSwitchProjectClick: (projectId: string) => void | Promise<void>,
 }
-const testName = 'plItem'
 @observer
 class ProjectListItem extends React.Component<Props> {
   render() {
@@ -104,22 +103,22 @@ class ProjectListItem extends React.Component<Props> {
 
     return (
       <Wrapper>
-        <Content onClick={this.props.onClick} data-test-id={`${testName}-content`}>
+        <Content onClick={this.props.onClick}>
           <Image />
           <Title>
-            <TitleLabel data-test-id={`${testName}-name`}>{this.props.item.name}</TitleLabel>
-            <Subtitle data-test-id={`${testName}-description`}>{this.props.item.description}</Subtitle>
+            <TitleLabel>{this.props.item.name}</TitleLabel>
+            <Subtitle>{this.props.item.description}</Subtitle>
           </Title>
           <Body>
             <Data percentage={33}>
               <ItemLabel>Members</ItemLabel>
-              <ItemValue data-test-id={`${testName}-members`}>
+              <ItemValue>
                 {this.props.getMembers(this.props.item.id)}
               </ItemValue>
             </Data>
             <Data percentage={33}>
               <ItemLabel>Enabled</ItemLabel>
-              <ItemValue data-test-id={`${testName}-enabled`}>
+              <ItemValue>
                 {this.props.item.enabled ? 'Yes' : 'No'}
               </ItemValue>
             </Data>
@@ -134,7 +133,6 @@ class ProjectListItem extends React.Component<Props> {
                   if (e) e.stopPropagation(); this.props.onSwitchProjectClick(this.props.item.id)
                 }}
                 disabled={isCurrentProject}
-                data-test-id={`${testName}-currentButton`}
               >{isCurrentProject ? 'Current' : 'Switch'}
               </Button>
             </Data>

+ 0 - 6
src/components/modules/ProjectModule/ProjectMemberModal/ProjectMemberModal.tsx

@@ -99,7 +99,6 @@ type State = {
   selectedRolesExisting: string[],
   selectedRolesNew: string[],
 }
-const testName = 'pmModal'
 @observer
 class ProjectMemberModal extends React.Component<Props, State> {
   state: State = {
@@ -204,7 +203,6 @@ class ProjectMemberModal extends React.Component<Props, State> {
     }]
     return (
       <ToggleButtonBarStyled
-        data-test-id={`${testName}-formToggle`}
         items={items}
         selectedValue={this.state.isNew ? 'new' : 'existing'}
         onChange={item => { this.setState({ isNew: item.value === 'new' }) }}
@@ -226,7 +224,6 @@ class ProjectMemberModal extends React.Component<Props, State> {
 
     return (
       <FieldInput
-        data-test-id={`${testName}-roles`}
         key="roles"
         name="role(s)"
         label="Role(s)"
@@ -254,7 +251,6 @@ class ProjectMemberModal extends React.Component<Props, State> {
   renderField(field: FieldType, value: any, onChange: (value: any) => void) {
     return (
       <FieldStyled
-        data-test-id={`${testName}-field-${field.name}`}
         key={field.name}
         name={field.name}
         type={field.type || 'string'}
@@ -336,7 +332,6 @@ class ProjectMemberModal extends React.Component<Props, State> {
             Username
           </FormLabel>
           <AutocompleteDropdown
-            data-test-id={`${testName}-users`}
             items={users}
             disabled={this.props.loading}
             selectedItem={this.state.selectedUser ? this.state.selectedUser.id : ''}
@@ -381,7 +376,6 @@ class ProjectMemberModal extends React.Component<Props, State> {
               large
               disabled={this.props.loading}
               onClick={() => { this.handleAddClick() }}
-              data-test-id={`${testName}-addButton`}
             >Add Member
             </Button>
           </Buttons>

+ 0 - 3
src/components/modules/ProjectModule/ProjectModal/ProjectModal.tsx

@@ -66,7 +66,6 @@ type State = {
   highlightFieldNames: string[],
   description?: string,
 }
-const testName = 'projectModal'
 @observer
 class ProjectModal extends React.Component<Props, State> {
   UNSAFE_componentWillMount() {
@@ -118,7 +117,6 @@ class ProjectModal extends React.Component<Props, State> {
     return (
       <FieldInput
         layout="modal"
-        data-test-id={`${testName}-field-${field.name}`}
         key={field.name}
         name={field.name}
         type={field.type || 'string'}
@@ -179,7 +177,6 @@ class ProjectModal extends React.Component<Props, State> {
             >Cancel
             </Button>
             <Button
-              data-test-id={`${testName}-updateButton`}
               large
               disabled={this.props.loading}
               onClick={() => { this.handleUpdateClick() }}

+ 3 - 4
src/components/modules/TransferModule/Executions/Executions.tsx

@@ -259,7 +259,6 @@ class Executions extends React.Component<Props, State> {
         onPreviousClick={() => { this.handlePreviousExecutionClick() }}
         onNextClick={() => { this.handleNextExecutionClick() }}
         onItemClick={item => { this.handleTimelineItemClick(item) }}
-        data-test-id="executions-timeline"
       />
     )
   }
@@ -312,7 +311,7 @@ class Executions extends React.Component<Props, State> {
 
     return (
       <ExecutionInfo>
-        <ExecutionInfoNumber data-test-id="executions-number">Execution #{this.state.selectedExecution.number}</ExecutionInfoNumber>
+        <ExecutionInfoNumber>Execution #{this.state.selectedExecution.number}</ExecutionInfoNumber>
         <StatusPill style={{ marginRight: '16px' }} small status={this.state.selectedExecution.status} />
         <ExecutionInfoDate>
           {DateUtils.getLocalTime(this.state.selectedExecution.created_at).format('DD MMMM YYYY HH:mm')}
@@ -350,9 +349,9 @@ class Executions extends React.Component<Props, State> {
     return (
       <NoExecutions>
         <ExecutionImage />
-        <NoExecutionTitle data-test-id="executions-noExTitle">It looks like there are no executions in this replica.</NoExecutionTitle>
+        <NoExecutionTitle>It looks like there are no executions in this replica.</NoExecutionTitle>
         <NoExecutionText>This replica has not been executed yet.</NoExecutionText>
-        <Button onClick={this.props.onExecuteClick} data-test-id="executions-executeButton">Execute Now</Button>
+        <Button onClick={this.props.onExecuteClick}>Execute Now</Button>
       </NoExecutions>
     )
   }

+ 7 - 9
src/components/modules/TransferModule/MainDetails/MainDetails.tsx

@@ -169,13 +169,13 @@ class MainDetails extends React.Component<Props, State> {
     return this.props.item ? this.renderValue(DateUtils.getLocalTime(this.props.item.updated_at).format('YYYY-MM-DD HH:mm:ss')) : '-'
   }
 
-  renderValue(value: string, dateTestId?: string) {
-    return <CopyValue value={value} maxWidth="90%" data-test-id={dateTestId ? `mainDetails-${dateTestId}` : undefined} />
+  renderValue(value: string) {
+    return <CopyValue value={value} maxWidth="90%" />
   }
 
   renderEndpointLink(type: string): React.ReactNode {
     const endpointIsMissing = (
-      <Value flex data-test-id={`mainDetails-missing-${type}`}>
+      <Value flex>
         <StatusIcon style={{ marginRight: '8px' }} status="ERROR" />Endpoint is missing
       </Value>
     )
@@ -289,7 +289,6 @@ class MainDetails extends React.Component<Props, State> {
           <Row>
             <EndpointLogos
               endpoint={(sourceEndpoint ? sourceEndpoint.type : '') as any}
-              data-test-id="mainDetails-sourceLogo"
             />
           </Row>
           {getPropertyNames('source').length > 0 ? (
@@ -306,20 +305,20 @@ class MainDetails extends React.Component<Props, State> {
           <Row>
             <Field>
               <Label>Id</Label>
-              {this.renderValue(this.props.item ? this.props.item.id || '-' : '-', 'id')}
+              {this.renderValue(this.props.item ? this.props.item.id || '-' : '-')}
             </Field>
           </Row>
           <Row>
             <Field>
               <Label>Created</Label>
-              {this.props.item && this.props.item.created_at ? this.renderValue(DateUtils.getLocalTime(this.props.item.created_at).format('YYYY-MM-DD HH:mm:ss'), 'created') : <Value>-</Value>}
+              {this.props.item && this.props.item.created_at ? this.renderValue(DateUtils.getLocalTime(this.props.item.created_at).format('YYYY-MM-DD HH:mm:ss')) : <Value>-</Value>}
             </Field>
           </Row>
           {lastUpdated ? (
             <Row>
               <Field>
                 <Label>Last Updated</Label>
-                <Value data-test-id="mainDetails-updated">{lastUpdated}</Value>
+                <Value>{lastUpdated}</Value>
               </Field>
             </Row>
           ) : null}
@@ -357,7 +356,6 @@ class MainDetails extends React.Component<Props, State> {
           <Row>
             <EndpointLogos
               endpoint={(destinationEndpoint ? destinationEndpoint.type : '') as any}
-              data-test-id="mainDetails-targetLogo"
             />
           </Row>
           {getPropertyNames('destination').length > 0 ? (
@@ -403,7 +401,7 @@ class MainDetails extends React.Component<Props, State> {
 
     return (
       <Loading>
-        <StatusImage loading data-test-id="mainDetails-loading" />
+        <StatusImage loading />
       </Loading>
     )
   }

+ 0 - 1
src/components/modules/TransferModule/MigrationDetailsContent/MigrationDetailsContent.tsx

@@ -106,7 +106,6 @@ class MigrationDetailsContent extends React.Component<Props> {
         endpoints={this.props.endpoints}
         bottomControls={this.renderBottomControls()}
         loading={this.props.detailsLoading}
-        data-test-id="mdContent-mainDetails"
       />
     )
   }

+ 0 - 3
src/components/modules/TransferModule/ReplicaDetailsContent/ReplicaDetailsContent.tsx

@@ -147,7 +147,6 @@ class ReplicaDetailsContent extends React.Component<Props, State> {
             primary
             disabled={this.isEndpointMissing()}
             onClick={this.props.onCreateMigrationClick}
-            data-test-id="rdContent-createButton"
           >Create Migration
           </Button>
         </ButtonColumn>
@@ -156,7 +155,6 @@ class ReplicaDetailsContent extends React.Component<Props, State> {
             alert
             hollow
             onClick={this.props.onDeleteReplicaClick}
-            data-test-id="rdContent-deleteButton"
           >Delete Replica
           </Button>
         </ButtonColumn>
@@ -184,7 +182,6 @@ class ReplicaDetailsContent extends React.Component<Props, State> {
         endpoints={this.props.endpoints}
         networks={this.props.networks}
         bottomControls={this.renderBottomControls()}
-        data-test-id="rdContent-mainDetails"
       />
     )
   }

+ 2 - 3
src/components/modules/TransferModule/ReplicaExecutionOptions/ReplicaExecutionOptions.tsx

@@ -117,13 +117,12 @@ class ReplicaExecutionOptions extends React.Component<Props, State> {
               value={this.getFieldValue(field)}
               label={LabelDictionary.get(field.name)}
               onChange={value => this.handleValueChange(field, value)}
-              data-test-id={`reOptions-option-${field.name}`}
             />
           ))}
         </Form>
         <Buttons>
-          <Button secondary onClick={this.props.onCancelClick} data-test-id="reOptions-cancelButton">Cancel</Button>
-          <Button onClick={() => { this.props.onExecuteClick(this.state.fields) }} data-test-id="reOptions-execButton">{this.props.executionLabel}</Button>
+          <Button secondary onClick={this.props.onCancelClick}>Cancel</Button>
+          <Button onClick={() => { this.props.onExecuteClick(this.state.fields) }}>{this.props.executionLabel}</Button>
         </Buttons>
       </Wrapper>
     )

+ 2 - 2
src/components/modules/TransferModule/ReplicaMigrationOptions/ReplicaMigrationOptions.tsx

@@ -294,8 +294,8 @@ class ReplicaMigrationOptions extends React.Component<Props, State> {
         <Image />
         {this.renderBody()}
         <Buttons>
-          <Button secondary onClick={this.props.onCancelClick} data-test-id="rmOptions-cancelButton">Cancel</Button>
-          <Button onClick={() => { this.migrate() }} data-test-id="rmOptions-execButton">Migrate</Button>
+          <Button secondary onClick={this.props.onCancelClick}>Cancel</Button>
+          <Button onClick={() => { this.migrate() }}>Migrate</Button>
         </Buttons>
       </Wrapper>
     )

+ 1 - 2
src/components/modules/TransferModule/Schedule/Schedule.tsx

@@ -206,7 +206,7 @@ class Schedule extends React.Component<Props, State> {
 
     return (
       <LoadingWrapper>
-        <StatusImage loading data-test-id="schedule-loadingStatus" />
+        <StatusImage loading />
         <LoadingText>Loading schedules...</LoadingText>
       </LoadingWrapper>
     )
@@ -316,7 +316,6 @@ class Schedule extends React.Component<Props, State> {
         <Timezone>
           <TimezoneLabel>Show all times in</TimezoneLabel>
           <DropdownLink
-            data-test-id="schedule-timezoneDropdown"
             items={timezoneItems}
             selectedItem={selectedItem}
             onChange={item => { this.props.onTimezoneChange(item.value === 'utc' ? 'utc' : 'local') }}

+ 1 - 10
src/components/modules/TransferModule/ScheduleItem/ScheduleItem.tsx

@@ -230,7 +230,6 @@ class ScheduleItem extends React.Component<Props> {
         useBold={this.shouldUseBold('month')}
         selectedItem={this.getFieldValue(items, 'month')}
         onChange={item => { this.handleMonthChange(item) }}
-        data-test-id="scheduleItem-monthDropdown"
       />
     )
   }
@@ -255,7 +254,6 @@ class ScheduleItem extends React.Component<Props> {
         useBold={this.shouldUseBold('dom')}
         selectedItem={this.getFieldValue(items, 'dom')}
         onChange={item => { this.props.onChange({ schedule: { dom: item.value } }) }}
-        data-test-id="scheduleItem-dayOfMonthDropdown"
       />
     )
   }
@@ -280,7 +278,6 @@ class ScheduleItem extends React.Component<Props> {
         useBold={this.shouldUseBold('dow')}
         selectedItem={this.getFieldValue(items, 'dow', true)}
         onChange={item => { this.props.onChange({ schedule: { dow: item.value } }) }}
-        data-test-id="scheduleItem-dayOfWeekDropdown"
       />
     )
   }
@@ -303,7 +300,6 @@ class ScheduleItem extends React.Component<Props> {
         useBold={this.shouldUseBold('hour')}
         selectedItem={this.getFieldValue(items, 'hour', true, 1)}
         onChange={item => { this.handleHourChange(item.value) }}
-        data-test-id="scheduleItem-hourDropdown"
       />
     )
   }
@@ -326,7 +322,6 @@ class ScheduleItem extends React.Component<Props> {
         useBold={this.shouldUseBold('minute')}
         selectedItem={this.getFieldValue(items, 'minute', true, 1)}
         onChange={item => { this.props.onChange({ schedule: { minute: item.value } }) }}
-        data-test-id="scheduleItem-minuteDropdown"
       />
     )
   }
@@ -356,7 +351,7 @@ class ScheduleItem extends React.Component<Props> {
   render() {
     const enabled = typeof this.props.item.enabled !== 'undefined' && this.props.item.enabled !== null ? this.props.item.enabled : false
     return (
-      <Wrapper data-test-id="scheduleItem">
+      <Wrapper>
         <Data width={this.props.colWidths[0]}>
           {this.props.enabling ? (
             <EnablingIcon>
@@ -369,7 +364,6 @@ class ScheduleItem extends React.Component<Props> {
               disabled={this.props.deleting}
               checked={enabled}
               onChange={itemEnabled => { this.props.onChange({ enabled: itemEnabled }, true) }}
-              data-test-id="scheduleItem-enabled"
             />
           )}
         </Data>
@@ -402,7 +396,6 @@ class ScheduleItem extends React.Component<Props> {
               letterSpacing: '1px',
               padding: '0 0 1px 3px',
             }}
-            data-test-id="scheduleItem-optionsButton"
           >•••
           </Button>
         </Data>
@@ -412,7 +405,6 @@ class ScheduleItem extends React.Component<Props> {
           </DeletingIcon>
         ) : (
           <DeleteButton
-            data-test-id="scheduleItem-deleteButton"
             onClick={this.props.onDeleteClick}
             hidden={this.props.item.enabled}
           />
@@ -423,7 +415,6 @@ class ScheduleItem extends React.Component<Props> {
           </SavingIcon>
         ) : (
           <SaveButton
-            data-test-id="scheduleItem-saveButton"
             onClick={this.props.onSaveSchedule}
             hidden={this.props.item.enabled
           || !this.props.unsavedSchedules.find(us => us.id === this.props.item.id)}

+ 1 - 1
src/components/modules/TransferModule/TaskItem/TaskItem.tsx

@@ -271,7 +271,7 @@ class TaskItem extends React.Component<Props> {
               <ProgressUpdateDate width={this.props.columnWidths[0]}>
                 <span>{DateUtils.getLocalTime(update.created_at).format('YYYY-MM-DD HH:mm:ss')}</span>
               </ProgressUpdateDate>
-              <ProgressUpdateValue data-test-id={`taskItem-progressUpdateMessage-${i}`}>
+              <ProgressUpdateValue>
                 {update.message}
                 {progressPercentage && (
                   <ProgressBar

+ 0 - 1
src/components/modules/TransferModule/Tasks/Tasks.tsx

@@ -158,7 +158,6 @@ class Tasks extends React.Component<Props, State> {
             columnWidths={ColumnWidths}
             open={Boolean(this.state.openedItems.find(i => i.id === item.id))}
             onDependsOnClick={id => { this.handleDependsOnClick(id) }}
-            data-test-id={`tasks-item-${item.id}`}
           />
         ))}
       </Body>

+ 1 - 4
src/components/modules/TransferModule/Timeline/Timeline.tsx

@@ -166,10 +166,9 @@ class Timeline extends React.Component<Props> {
               key={item.id}
               ref={(ref: HTMLElement | null | undefined) => { this.itemRef = ref }}
               onClick={() => { if (this.props.onItemClick) this.props.onItemClick(item) }}
-              data-test-id={`timeline-item-${item.id}`}
             >
               <StatusIcon status={item.status} useBackground />
-              <ItemLabel selected={this.props.selectedItem && this.props.selectedItem.id === item.id} data-test-id={`timeline-label-${item.id}`}>
+              <ItemLabel selected={this.props.selectedItem && this.props.selectedItem.id === item.id}>
                 {DateUtils.getLocalTime(item.created_at).format('DD MMM YYYY')}
               </ItemLabel>
             </Item>
@@ -187,7 +186,6 @@ class Timeline extends React.Component<Props> {
           forceShow={!this.props.items || !this.props.items.length}
           primary={Boolean(this.props.items && this.props.items.length)}
           onClick={this.props.onPreviousClick}
-          data-test-id="timeline-previous"
         />
         {this.renderMainLine()}
         {this.renderItems()}
@@ -195,7 +193,6 @@ class Timeline extends React.Component<Props> {
           orientation="right"
           forceShow={!this.props.items || !this.props.items.length}
           onClick={this.props.onNextClick}
-          data-test-id="timeline-next"
         />
       </Wrapper>
     )

+ 2 - 2
src/components/modules/TransferModule/TransferDetailsTable/TransferDetailsTable.tsx

@@ -208,11 +208,11 @@ class TransferDetailsTable extends React.Component<Props, State> {
         <RowHeader>
           <RowHeaderColumn>
             <HeaderIcon icon={icon} />
-            <HeaderName source data-test-id={`${TEST_ID}-source-${icon}`}>{sourceName}</HeaderName>
+            <HeaderName source>{sourceName}</HeaderName>
             {destinationName ? <ArrowIcon /> : null}
           </RowHeaderColumn>
           <RowHeaderColumn>
-            <HeaderName data-test-id={`${TEST_ID}-destination-${icon}`}>{destinationName}</HeaderName>
+            <HeaderName>{destinationName}</HeaderName>
           </RowHeaderColumn>
         </RowHeader>
         <Collapse isOpened={isOpened}>

+ 1 - 2
src/components/modules/TransferModule/TransferItemModal/TransferItemModal.tsx

@@ -34,7 +34,6 @@ import WizardStorage from '@src/components/modules/WizardModule/WizardStorage'
 import type {
   UpdateData, TransferItemDetails, MigrationItemDetails,
 } from '@src/@types/MainItem'
-import type { NavigationItem } from '@src/components/ui/Panel'
 import {
   Endpoint, EndpointUtils, StorageBackend, StorageMap,
 } from '@src/@types/Endpoint'
@@ -787,7 +786,7 @@ class TransferItemModal extends React.Component<Props, State> {
   }
 
   render() {
-    const navigationItems: NavigationItem[] = [
+    const navigationItems: Panel['props']['navigationItems'] = [
       {
         value: 'source_options',
         label: 'Source Options',

+ 4 - 4
src/components/ui/Lists/MainListItem/MainListItem.tsx → src/components/modules/TransferModule/TransferListItem/TransferListItem.tsx

@@ -117,7 +117,7 @@ type Props = {
   onSelectedChange: (value: boolean) => void,
 }
 @observer
-class MainListItem extends React.Component<Props> {
+class TransferListItem extends React.Component<Props> {
   getStatus() {
     return this.props.item.last_execution_status
   }
@@ -171,9 +171,9 @@ class MainListItem extends React.Component<Props> {
     const destinationType = this.props.endpointType(this.props.item.destination_endpoint_id)
     const endpointImages = (
       <EndpointsImages>
-        <EndpointLogos data-test-id="mainListItem-sourceLogo" height={32} endpoint={sourceType as any} />
+        <EndpointLogos height={32} endpoint={sourceType as any} />
         <EndpointImageArrow />
-        <EndpointLogos data-test-id="mainListItem-destLogo" height={32} endpoint={destinationType as any} />
+        <EndpointLogos height={32} endpoint={destinationType as any} />
       </EndpointsImages>
     )
     const status = this.getStatus()
@@ -212,4 +212,4 @@ class MainListItem extends React.Component<Props> {
   }
 }
 
-export default MainListItem
+export default TransferListItem

+ 0 - 0
src/components/ui/Lists/MainListItem/images/arrow.svg → src/components/modules/TransferModule/TransferListItem/images/arrow.svg


+ 0 - 0
src/components/ui/Lists/MainListItem/images/schedule.svg → src/components/modules/TransferModule/TransferListItem/images/schedule.svg


+ 6 - 0
src/components/modules/TransferModule/TransferListItem/package.json

@@ -0,0 +1,6 @@
+{
+  "name": "TransferListItem",
+  "version": "0.0.0",
+  "private": true,
+  "main": "./TransferListItem.tsx"
+}

+ 3 - 3
src/components/ui/Lists/MainListItem/story.tsx → src/components/modules/TransferModule/TransferListItem/story.tsx

@@ -14,7 +14,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import React from 'react'
 import { storiesOf } from '@storybook/react'
-import MainListItem from '.'
+import TransferListItem from './TransferListItem'
 
 const item: any = {
   origin_endpoint_id: 'openstack',
@@ -32,7 +32,7 @@ const endpointType = (id: any) => id
 
 storiesOf('MainListItem', module)
   .add('completed', () => (
-    <MainListItem
+    <TransferListItem
       item={item}
       endpointType={endpointType}
       selected={false}
@@ -44,7 +44,7 @@ storiesOf('MainListItem', module)
     />
   ))
   .add('running', () => (
-    <MainListItem
+    <TransferListItem
       item={item2}
       endpointType={endpointType}
       selected={false}

+ 0 - 0
src/components/ui/Lists/MainListItem/test.tsx → src/components/modules/TransferModule/TransferListItem/test.tsx


+ 6 - 9
src/components/modules/UserModule/UserDetailsContent/UserDetailsContent.tsx

@@ -112,7 +112,6 @@ class UserDetailsContent extends React.Component<Props> {
           <Button
             hollow
             onClick={this.props.onUpdatePasswordClick}
-            data-test-id={`${TEST_ID}-updateButton`}
           >Change password
           </Button>
         </ButtonsColumn>
@@ -122,7 +121,6 @@ class UserDetailsContent extends React.Component<Props> {
             hollow
             onClick={() => { this.props.onDeleteClick() }}
             disabled={this.props.isLoggedUser}
-            data-test-id={`${TEST_ID}-deleteUserButton`}
           >Delete user
           </Button>
         </ButtonsColumn>
@@ -163,7 +161,7 @@ class UserDetailsContent extends React.Component<Props> {
       <Info>
         <Field>
           <Label>Name</Label>
-          {this.renderValue(user.name, 'name')}
+          {this.renderValue(user.name)}
         </Field>
         <Field>
           <Label>Description</Label>
@@ -171,15 +169,15 @@ class UserDetailsContent extends React.Component<Props> {
         </Field>
         <Field>
           <Label>ID</Label>
-          {this.renderValue(user.id, 'id')}
+          {this.renderValue(user.id)}
         </Field>
         <Field>
           <Label>Email</Label>
-          {this.renderValue(user.email || '-', 'email')}
+          {this.renderValue(user.email || '-')}
         </Field>
         <Field>
           <Label>Primary Project</Label>
-          {this.renderValue(primaryProjectName || '-', 'primaryProject')}
+          {this.renderValue(primaryProjectName || '-')}
         </Field>
         <Field>
           <Label>Project Membership</Label>
@@ -187,16 +185,15 @@ class UserDetailsContent extends React.Component<Props> {
         </Field>
         <Field>
           <Label>Enabled</Label>
-          <Value data-test-id={`${TEST_ID}-enabled`}>{user.enabled ? 'Yes' : 'No'}</Value>
+          <Value>{user.enabled ? 'Yes' : 'No'}</Value>
         </Field>
       </Info>
     )
   }
 
-  renderValue(value: string, dataTestId?: string) {
+  renderValue(value: string) {
     return value !== '-' ? (
       <CopyValue
-        data-test-id={`${TEST_ID}-${dataTestId || ''}`}
         value={value}
         maxWidth="90%"
       />

+ 6 - 7
src/components/modules/UserModule/UserListItem/UserListItem.tsx

@@ -93,34 +93,33 @@ type Props = {
   onClick: () => void,
   getProjectName: (projectId: string | null | undefined) => string,
 }
-const testName = 'ulItem'
 @observer
 class UserListItem extends React.Component<Props> {
   render() {
     return (
       <Wrapper>
-        <Content data-test-id={`${testName}-content`} onClick={this.props.onClick}>
+        <Content onClick={this.props.onClick}>
           <Image />
           <Title>
-            <TitleLabel data-test-id={`${testName}-name`}>{this.props.item.name}</TitleLabel>
-            <Subtitle data-test-id={`${testName}-description`}>{this.props.item.description}</Subtitle>
+            <TitleLabel>{this.props.item.name}</TitleLabel>
+            <Subtitle>{this.props.item.description}</Subtitle>
           </Title>
           <Body>
             <Data percentage={45}>
               <ItemLabel>Email</ItemLabel>
-              <ItemValue data-test-id={`${testName}-email`}>
+              <ItemValue>
                 {this.props.item.email || '-'}
               </ItemValue>
             </Data>
             <Data percentage={35}>
               <ItemLabel>Primary Project</ItemLabel>
-              <ItemValue data-test-id={`${testName}-project`}>
+              <ItemValue>
                 {this.props.getProjectName(this.props.item.project_id)}
               </ItemValue>
             </Data>
             <Data percentage={20}>
               <ItemLabel>Enabled</ItemLabel>
-              <ItemValue data-test-id={`${testName}-enabled`}>
+              <ItemValue>
                 {this.props.item.enabled ? 'Yes' : 'No'}
               </ItemValue>
             </Data>

+ 0 - 3
src/components/modules/UserModule/UserModal/UserModal.tsx

@@ -81,7 +81,6 @@ type State = {
   confirmPassword: string,
   description?: string,
 }
-const testName = 'userModal'
 @observer
 class UserModal extends React.Component<Props, State> {
   UNSAFE_componentWillMount() {
@@ -176,7 +175,6 @@ class UserModal extends React.Component<Props, State> {
     return (
       <FieldInput
         layout="modal"
-        data-test-id={`${testName}-field-${field.name}`}
         key={field.name}
         name={field.name}
         label={LabelDictionary.get(field.name)}
@@ -275,7 +273,6 @@ class UserModal extends React.Component<Props, State> {
             >Cancel
             </Button>
             <Button
-              data-test-id={`${testName}-updateButton`}
               large
               disabled={this.props.loading}
               onClick={() => { this.handleUpdateClick() }}

+ 1 - 1
src/components/modules/WizardModule/WizardBreadcrumbs/WizardBreadcrumbs.tsx

@@ -50,7 +50,7 @@ class WizardBreadcrumbs extends React.Component<Props> {
       <Wrapper>
         {this.props.pages.map(page => (
           <Breadcrumb key={page.id}>
-            <Name selected={this.props.selected.id === page.id} data-test-id={`wBreadCrumbs-name-${page.id}`}>{page.breadcrumb}</Name>
+            <Name selected={this.props.selected.id === page.id}>{page.breadcrumb}</Name>
             <ArrowStyled primary={this.props.selected.id === page.id} useDefaultCursor />
           </Breadcrumb>
         ))}

+ 0 - 3
src/components/modules/WizardModule/WizardEndpointList/WizardEndpointList.tsx

@@ -97,7 +97,6 @@ class WizardEndpointList extends React.Component<Props> {
 
       actionInput = (
         <Dropdown
-          data-test-id={`wEndpointList-dropdown-${provider}`}
           primary={Boolean(selectedItem)}
           items={items}
           valueField="id"
@@ -121,7 +120,6 @@ class WizardEndpointList extends React.Component<Props> {
           hollow
           hoverPrimary
           onClick={() => { this.props.onAddEndpoint(provider) }}
-          data-test-id={`wEndpointList-addButton-${provider}`}
         >Add
         </Button>
       )
@@ -133,7 +131,6 @@ class WizardEndpointList extends React.Component<Props> {
           height={128}
           endpoint={provider}
           disabled={items.length === 0}
-          data-test-id={`wEndpointList-logo-${provider}`}
         />
         {actionInput}
       </Item>

+ 4 - 7
src/components/modules/WizardModule/WizardInstances/WizardInstances.tsx

@@ -264,7 +264,7 @@ class WizardInstances extends React.Component<Props, State> {
     return (
       <SearchNotFound>
         <StatusImage status="ERROR" />
-        <SearchNotFoundText data-test-id="wInstances-notFoundText">Your search returned no results</SearchNotFoundText>
+        <SearchNotFoundText>Your search returned no results</SearchNotFoundText>
         {subtitle}
         <Button hollow onClick={() => { this.props.onReloadClick() }}>Retry</Button>
       </SearchNotFound>
@@ -290,7 +290,7 @@ class WizardInstances extends React.Component<Props, State> {
 
     return (
       <LoadingWrapper>
-        <StatusImage loading data-test-id="wInstances-loadingStatus" />
+        <StatusImage loading />
         <LoadingText>Loading instances...</LoadingText>
       </LoadingWrapper>
     )
@@ -320,7 +320,6 @@ class WizardInstances extends React.Component<Props, State> {
                 if (!this.isCheckboxMouseDown) this.props.onInstanceClick(instance)
               }}
               selected={selected}
-              data-test-id={`wInstances-item-${instance.id}`}
             >
               <CheckboxStyled
                 checked={selected}
@@ -328,7 +327,7 @@ class WizardInstances extends React.Component<Props, State> {
                 onMouseDown={() => { this.isCheckboxMouseDown = true }}
                 onMouseUp={() => { this.isCheckboxMouseDown = false }}
               />
-              <InstanceContent data-test-id="wInstances-instanceItem">
+              <InstanceContent>
                 <Image />
                 <Label>
                   <LabelTitle>{instance.name}</LabelTitle>
@@ -362,7 +361,6 @@ class WizardInstances extends React.Component<Props, State> {
             value={this.state.searchText}
             loading={this.props.searching}
             placeholder="Search VMs"
-            data-test-id="wInstances-searchInput"
           />
           {this.props.hasSourceOptions ? (
             <InfoIcon
@@ -374,11 +372,10 @@ class WizardInstances extends React.Component<Props, State> {
           ) : null}
         </SearchInputInfo>
         <FilterInfo>
-          <SelectionInfo data-test-id="wInstances-selInfo">{count} instance{plural} selected</SelectionInfo>
+          <SelectionInfo>{count} instance{plural} selected</SelectionInfo>
           <FilterSeparator>|</FilterSeparator>
           <ReloadButton
             onClick={() => { this.props.onReloadClick() }}
-            data-test-id="wInstances-reloadButton"
           />
         </FilterInfo>
       </FiltersWrapper>

+ 2 - 2
src/components/modules/WizardModule/WizardNetworks/WizardNetworks.tsx

@@ -149,7 +149,7 @@ class WizardNetworks extends React.Component<Props> {
     return (
       <NoNicsMessage>
         <BigNetworkImage />
-        <NoNicsTitle data-test-id="wNetworks-noNics">No networks were found</NoNicsTitle>
+        <NoNicsTitle>No networks were found</NoNicsTitle>
         <NoNicsSubtitle>
           We could not find any Networks attached to the selected Instances.
           Coriolis will skip this step.
@@ -286,7 +286,7 @@ class WizardNetworks extends React.Component<Props> {
 
           const selectedNetwork = this.props.selectedNetworks?.find(n => n.sourceNic.network_name === nic.network_name)
           return (
-            <Nic key={nic.id} data-test-id="networkItem">
+            <Nic key={nic.id}>
               <NetworkImage />
               <NetworkTitle width={this.props.titleWidth || 320}>
                 <NetworkName>{nic.network_name}</NetworkName>

+ 0 - 1
src/components/modules/WizardModule/WizardOptions/WizardOptions.tsx

@@ -358,7 +358,6 @@ class WizardOptions extends React.Component<Props> {
         enum={field.enum}
         addNullValue
         required={field.required}
-        data-test-id={`wOptions-field-${field.name}`}
         width={this.props.fieldWidth || ThemeProps.inputSizes.wizard.width}
         nullableBoolean={field.nullableBoolean}
         disabled={field.disabled}

+ 0 - 1
src/components/modules/WizardModule/WizardStorage/WizardStorage.tsx

@@ -241,7 +241,6 @@ class WizardStorage extends React.Component<Props> {
           labelField="name"
           valueField="id"
           onChange={(item: StorageBackend) => { this.props.onChange({ source: disk, target: item, type }) }}
-          data-test-id={`${TEST_ID}-${type}-destination`}
         />
       )
   }

+ 7 - 10
src/components/modules/WizardModule/WizardSummary/WizardSummary.tsx

@@ -252,7 +252,7 @@ class WizardSummary extends React.Component<Props> {
         <SectionTitle>Schedule</SectionTitle>
         <Table>
           {schedules.map(schedule => (
-            <Row key={schedule.id} schedule data-test-id={`wSummary-scheduleItem-${schedule.id || 0}`}>
+            <Row key={schedule.id} schedule>
               {this.renderScheduleLabel(schedule)}
             </Row>
           ))}
@@ -474,10 +474,10 @@ class WizardSummary extends React.Component<Props> {
 
             return (
               <Option key={optionName}>
-                <OptionLabel data-test-id={`wSummary-optionLabel-${optionName}`} title={optionLabel}>
+                <OptionLabel title={optionLabel}>
                   {optionLabel}
                 </OptionLabel>
-                <OptionValue data-test-id={`wSummary-optionValue-${optionName}`} title={optionValue}>
+                <OptionValue title={optionValue}>
                   {optionValue}
                 </OptionValue>
               </Option>
@@ -554,10 +554,10 @@ class WizardSummary extends React.Component<Props> {
         <Table>
           {data.networks.map(mapping => (
             <Row key={mapping.sourceNic.network_name} direction="row">
-              <SourceNetwork data-test-id="wSummary-networkSource">{mapping.sourceNic.network_name}</SourceNetwork>
+              <SourceNetwork>{mapping.sourceNic.network_name}</SourceNetwork>
               <NetworkArrow />
               <TargetNetwork>
-                <TargetNetworkName data-test-id="wSummary-networkTarget">{mapping.targetNetwork!.name}</TargetNetworkName>
+                <TargetNetworkName>{mapping.targetNetwork!.name}</TargetNetworkName>
                 {mapping.targetSecurityGroups?.length ? (
                   <TargetNetworkName>Security Groups: {mapping.targetSecurityGroups.map(s => (typeof s === 'string' ? s : s.name)).join(', ')}</TargetNetworkName>
                 ) : null}
@@ -639,9 +639,8 @@ class WizardSummary extends React.Component<Props> {
                 secondary
                 small
                 label={configLoader.config.providerNames[data.source!.type]}
-                data-test-id="wSummary-sourcePill"
               />
-              <OverviewRowLabel data-test-id="wSummary-source">{data.source ? data.source.name : ''}</OverviewRowLabel>
+              <OverviewRowLabel>{data.source ? data.source.name : ''}</OverviewRowLabel>
             </OverviewRowData>
           </OverviewRow>
           <OverviewRow>
@@ -651,9 +650,8 @@ class WizardSummary extends React.Component<Props> {
                 secondary
                 small
                 label={configLoader.config.providerNames[data.target!.type]}
-                data-test-id="wSummary-targetPill"
               />
-              <OverviewRowLabel data-test-id="wSummary-target">{data.target && data.target.name}</OverviewRowLabel>
+              <OverviewRowLabel>{data.target && data.target.name}</OverviewRowLabel>
             </OverviewRowData>
           </OverviewRow>
           <OverviewRow>
@@ -663,7 +661,6 @@ class WizardSummary extends React.Component<Props> {
                 alert={type === 'Replica'}
                 small
                 label={this.props.wizardType.toUpperCase()}
-                data-test-id="wSummary-typePill"
               />
               <OverviewRowLabel>Coriolis {type}</OverviewRowLabel>
             </OverviewRowData>

+ 0 - 1
src/components/modules/WizardModule/WizardType/WizardType.tsx

@@ -79,7 +79,6 @@ class WizardType extends React.Component<Props> {
               big
               onChange={this.props.onChange}
               checked={this.props.selected === 'replica'}
-              data-test-id="wType-switch"
             />
           </Column>
           <Column width="50%">

+ 1 - 1
src/components/smart/AssessmentsPage/AssessmentsPage.tsx

@@ -18,7 +18,7 @@ import { observer } from 'mobx-react'
 
 import FilterList from '@src/components/ui/Lists/FilterList'
 import MainTemplate from '@src/components/modules/TemplateModule/MainTemplate'
-import PageHeader from '@src/components/ui/PageHeader'
+import PageHeader from '@src/components/smart/PageHeader'
 import Navigation from '@src/components/modules/NavigationModule/Navigation'
 import DropdownFilterGroup from '@src/components/ui/Dropdowns/DropdownFilterGroup'
 import AssessmentListItem from '@src/components/modules/AssessmentModule/AssessmentListItem'

+ 1 - 1
src/components/smart/DashboardPage/DashboardPage.tsx

@@ -26,7 +26,7 @@ import notificationStore from '@src/stores/NotificationStore'
 
 import MainTemplate from '@src/components/modules/TemplateModule/MainTemplate'
 import Navigation from '@src/components/modules/NavigationModule/Navigation'
-import PageHeader from '@src/components/ui/PageHeader'
+import PageHeader from '@src/components/smart/PageHeader'
 import DashboardContent from '@src/components/modules/DashboardModule/DashboardContent'
 
 import Utils from '@src/utils/ObjectUtils'

+ 1 - 1
src/components/smart/EndpointsPage/EndpointsPage.tsx

@@ -19,7 +19,7 @@ import { observer } from 'mobx-react'
 import MainTemplate from '@src/components/modules/TemplateModule/MainTemplate'
 import Navigation from '@src/components/modules/NavigationModule/Navigation'
 import FilterList from '@src/components/ui/Lists/FilterList'
-import PageHeader from '@src/components/ui/PageHeader'
+import PageHeader from '@src/components/smart/PageHeader'
 import EndpointListItem from '@src/components/modules/EndpointModule/EndpointListItem'
 import AlertModal from '@src/components/ui/AlertModal'
 import Modal from '@src/components/ui/Modal'

+ 1 - 1
src/components/smart/LogsPage/LogsPage.tsx

@@ -18,7 +18,7 @@ import { observer } from 'mobx-react'
 
 import MainTemplate from '@src/components/modules/TemplateModule/MainTemplate'
 import Navigation from '@src/components/modules/NavigationModule/Navigation'
-import PageHeader from '@src/components/ui/PageHeader'
+import PageHeader from '@src/components/smart/PageHeader'
 import TabNavigation from '@src/components/ui/TabNavigation'
 
 import logStore from '@src/stores/LogStore'

+ 3 - 3
src/components/smart/MigrationsPage/MigrationsPage.tsx

@@ -19,9 +19,8 @@ import { observer } from 'mobx-react'
 import MainTemplate from '@src/components/modules/TemplateModule/MainTemplate'
 import Navigation from '@src/components/modules/NavigationModule/Navigation'
 import FilterList from '@src/components/ui/Lists/FilterList'
-import PageHeader from '@src/components/ui/PageHeader'
+import PageHeader from '@src/components/smart/PageHeader'
 import AlertModal from '@src/components/ui/AlertModal'
-import MainListItem from '@src/components/ui/Lists/MainListItem'
 
 import projectStore from '@src/stores/ProjectStore'
 import migrationStore from '@src/stores/MigrationStore'
@@ -33,6 +32,7 @@ import { ThemePalette } from '@src/components/Theme'
 import replicaMigrationFields from '@src/components/modules/TransferModule/ReplicaMigrationOptions/replicaMigrationFields'
 import { MigrationItem } from '@src/@types/MainItem'
 import userStore from '@src/stores/UserStore'
+import TransferListItem from '@src/components/modules/TransferModule/TransferListItem'
 import migrationLargeImage from './images/migration-large.svg'
 import migrationItemImage from './images/migration.svg'
 
@@ -254,7 +254,7 @@ class MigrationsPage extends React.Component<{ history: any }, State> {
               }}
               dropdownActions={BulkActions}
               renderItemComponent={options => (
-                <MainListItem
+                <TransferListItem
                   {...options}
                   image={migrationItemImage}
                   endpointType={id => {

+ 1 - 1
src/components/smart/MinionPoolsPage/MinionPoolsPage.tsx

@@ -22,7 +22,7 @@ import Modal from '@src/components/ui/Modal'
 import MainTemplate from '@src/components/modules/TemplateModule/MainTemplate'
 import Navigation from '@src/components/modules/NavigationModule/Navigation'
 import FilterList from '@src/components/ui/Lists/FilterList'
-import PageHeader from '@src/components/ui/PageHeader'
+import PageHeader from '@src/components/smart/PageHeader'
 
 import type { Action as DropdownAction } from '@src/components/ui/Dropdowns/ActionDropdown'
 

+ 0 - 0
src/components/ui/PageHeader/PageHeader.tsx → src/components/smart/PageHeader/PageHeader.tsx


+ 0 - 0
src/components/ui/PageHeader/package.json → src/components/smart/PageHeader/package.json


+ 0 - 0
src/components/ui/PageHeader/story.tsx → src/components/smart/PageHeader/story.tsx


+ 1 - 1
src/components/smart/ProjectsPage/ProjectsPage.tsx

@@ -19,7 +19,7 @@ import { observer } from 'mobx-react'
 import MainTemplate from '@src/components/modules/TemplateModule/MainTemplate'
 import Navigation from '@src/components/modules/NavigationModule/Navigation'
 import FilterList from '@src/components/ui/Lists/FilterList'
-import PageHeader from '@src/components/ui/PageHeader'
+import PageHeader from '@src/components/smart/PageHeader'
 import ProjectListItem from '@src/components/modules/ProjectModule/ProjectListItem'
 
 import type { Project, RoleAssignment } from '@src/@types/Project'

+ 3 - 3
src/components/smart/ReplicasPage/ReplicasPage.tsx

@@ -19,9 +19,8 @@ import { observer } from 'mobx-react'
 import MainTemplate from '@src/components/modules/TemplateModule/MainTemplate'
 import Navigation from '@src/components/modules/NavigationModule/Navigation'
 import FilterList from '@src/components/ui/Lists/FilterList'
-import PageHeader from '@src/components/ui/PageHeader'
+import PageHeader from '@src/components/smart/PageHeader'
 import AlertModal from '@src/components/ui/AlertModal'
-import MainListItem from '@src/components/ui/Lists/MainListItem'
 import Modal from '@src/components/ui/Modal'
 import ReplicaExecutionOptions from '@src/components/modules/TransferModule/ReplicaExecutionOptions'
 import ReplicaMigrationOptions from '@src/components/modules/TransferModule/ReplicaMigrationOptions'
@@ -43,6 +42,7 @@ import { ThemePalette } from '@src/components/Theme'
 import configLoader from '@src/utils/Config'
 import { ReplicaItem } from '@src/@types/MainItem'
 import userStore from '@src/stores/UserStore'
+import TransferListItem from '@src/components/modules/TransferModule/TransferListItem'
 import replicaLargeImage from './images/replica-large.svg'
 import replicaItemImage from './images/replica.svg'
 
@@ -352,7 +352,7 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
               onSelectedItemsChange={selectedReplicas => { this.setState({ selectedReplicas }) }}
               onPaginatedItemsChange={paginatedReplicas => { this.handlePaginatedItemsChange(paginatedReplicas) }}
               renderItemComponent={options => (
-                <MainListItem
+                <TransferListItem
                   {...options}
                   image={replicaItemImage}
                   showScheduleIcon={this.isReplicaScheduled(options.item.id)}

+ 1 - 1
src/components/smart/UsersPage/UsersPage.tsx

@@ -19,7 +19,6 @@ import { observer } from 'mobx-react'
 import MainTemplate from '@src/components/modules/TemplateModule/MainTemplate'
 import Navigation from '@src/components/modules/NavigationModule/Navigation'
 import FilterList from '@src/components/ui/Lists/FilterList'
-import PageHeader from '@src/components/ui/PageHeader'
 import UserListItem from '@src/components/modules/UserModule/UserListItem'
 
 import type { User } from '@src/@types/User'
@@ -27,6 +26,7 @@ import type { User } from '@src/@types/User'
 import projectStore from '@src/stores/ProjectStore'
 import userStore from '@src/stores/UserStore'
 import configLoader from '@src/utils/Config'
+import PageHeader from '@src/components/smart/PageHeader'
 
 const Wrapper = styled.div<any>``
 

+ 71 - 0
src/components/ui/AlertModal/AlertModal.spec.tsx

@@ -0,0 +1,71 @@
+/*
+Copyright (C) 2017  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import React from 'react'
+import { render } from '@testing-library/react'
+import StatusImage from '@src/components/ui/StatusComponents/StatusImage'
+import TestUtils from '@tests/TestUtils'
+import AlertModal from './AlertModal'
+
+jest.mock('../StatusComponents/StatusImage/StatusImage', () => jest.fn(() => null))
+
+describe('AlertModal', () => {
+  it('renders confirmation as default with message and extra message', () => {
+    const message = 'message'
+    const extraMessage = 'extra message'
+    const { queryByText } = render((
+      <AlertModal
+        isOpen
+        message={message}
+        extraMessage={extraMessage}
+      />
+    ))
+    expect(TestUtils.select('AlertModal__Message')?.innerHTML).toBe(message)
+    expect(TestUtils.select('AlertModal__ExtraMessage')?.textContent).toBe(extraMessage)
+
+    expect(queryByText('No')).toBeTruthy()
+    expect(queryByText('Yes')).toBeTruthy()
+    expect(queryByText('Dismiss')).toBeNull()
+    expect(StatusImage).toHaveBeenCalledWith({ status: 'confirmation' }, {})
+  })
+
+  it('has correct buttons for errors', () => {
+    const { queryByText } = render((
+      <AlertModal
+        isOpen
+        message="message"
+        extraMessage="extra message"
+        type="error"
+      />
+    ))
+    expect(queryByText('Dismiss')).toBeTruthy()
+    expect(queryByText('No')).toBeNull()
+    expect(queryByText('Yes')).toBeNull()
+  })
+
+  it('renders loading', () => {
+    const { queryByText } = render((
+      <AlertModal
+        isOpen
+        message="message"
+        extraMessage="extra message"
+        type="loading"
+      />
+    ))
+    expect(queryByText('Dismiss')).toBeNull()
+    expect(queryByText('No')).toBeNull()
+    expect(queryByText('Yes')).toBeNull()
+    expect(StatusImage).toHaveBeenCalledWith({ status: 'RUNNING' }, {})
+  })
+})

+ 7 - 7
src/components/ui/AlertModal/AlertModal.tsx

@@ -84,7 +84,7 @@ class AlertModal extends React.Component<Props> {
 
     return (
       <Buttons centered>
-        <Button secondary onClick={this.props.onRequestClose} data-test-id="aModal-dismissButton">Dismiss</Button>
+        <Button secondary onClick={this.props.onRequestClose}>Dismiss</Button>
       </Buttons>
     )
   }
@@ -96,8 +96,8 @@ class AlertModal extends React.Component<Props> {
 
     return (
       <Buttons>
-        <Button secondary onClick={this.props.onRequestClose} data-test-id="aModal-noButton">No</Button>
-        <Button onClick={this.props.onConfirmation} data-test-id="aModal-yesButton">Yes</Button>
+        <Button secondary onClick={this.props.onRequestClose}>No</Button>
+        <Button onClick={this.props.onConfirmation}>Yes</Button>
       </Buttons>
     )
   }
@@ -108,10 +108,10 @@ class AlertModal extends React.Component<Props> {
     return (
       // eslint-disable-next-line react/jsx-props-no-spreading
       <Modal {...this.props} isOpen={this.props.isOpen || false}>
-        <Wrapper data-test-id="alertModal">
-          <StatusImage status={status} data-test-id="aModal-status" />
-          {this.props.message ? <Message data-test-id="aModal-message">{this.props.message}</Message> : null}
-          {this.props.extraMessage ? <ExtraMessage data-test-id="aModal-extraMessage">{this.props.extraMessage}</ExtraMessage> : null}
+        <Wrapper>
+          <StatusImage status={status} />
+          {this.props.message ? <Message>{this.props.message}</Message> : null}
+          {this.props.extraMessage ? <ExtraMessage>{this.props.extraMessage}</ExtraMessage> : null}
           {this.renderConfirmationButtons()}
           {this.renderDismissButton()}
         </Wrapper>

+ 0 - 56
src/components/ui/AlertModal/test.tsx

@@ -1,56 +0,0 @@
-/*
-Copyright (C) 2017  Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-import React from 'react'
-import { shallow } from 'enzyme'
-import TW from '../../../utils/TestWrapper'
-import AlertModal from '.'
-
-const wrap = props => new TW(shallow(<AlertModal {...props} />), 'aModal')
-
-describe('AlertModal Component', () => {
-  it('renders confirmation as default with message and extra message', () => {
-    let wrapper = wrap({ message: 'alert-message', extraMessage: 'alert-extra' })
-    expect(wrapper.findText('message')).toBe('alert-message')
-    expect(wrapper.findText('extraMessage')).toBe('alert-extra')
-    expect(wrapper.find('status').prop('status')).toBe('confirmation')
-    expect(wrapper.find('noButton').length).toBe(1)
-    expect(wrapper.find('yesButton').length).toBe(1)
-    expect(wrapper.find('dismissButton').length).toBe(0)
-  })
-
-  it('has correct buttons for confirmation', () => {
-    let wrapper = wrap({ message: 'alert-message', extraMessage: 'alert-extra' })
-    expect(wrapper.find('noButton').prop('secondary')).toBe(true)
-    expect(wrapper.find('yesButton').prop('secondary')).toBe(undefined)
-    expect(wrapper.find('noButton').shallow.dive().dive().text()).toBe('No')
-    expect(wrapper.find('yesButton').shallow.dive().dive().text()).toBe('Yes')
-  })
-
-  it('has correct button for error', () => {
-    let wrapper = wrap({ message: 'alert-message', extraMessage: 'alert-extra', type: 'error' })
-    expect(wrapper.find('dismissButton').length).toBe(1)
-  })
-
-  it('renders loading', () => {
-    let wrapper = wrap({ message: 'alert-message', extraMessage: 'alert-extra', type: 'loading' })
-    expect(wrapper.find('status').prop('status')).toBe('RUNNING')
-    expect(wrapper.find('noButton').length).toBe(0)
-    expect(wrapper.find('yesButton').length).toBe(0)
-    expect(wrapper.find('dismissButton').length).toBe(0)
-  })
-})
-
-
-

+ 46 - 0
src/components/ui/Arrow/Arrow.spec.tsx

@@ -0,0 +1,46 @@
+/*
+Copyright (C) 2021  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import React from 'react'
+import { render } from '@testing-library/react'
+import TestUtils from '@tests/TestUtils'
+import { ThemePalette } from '@src/components/Theme'
+import Arrow from './Arrow'
+
+describe('Arrow', () => {
+  it.each`
+    orientation
+    ${'up'}
+    ${'down'}
+    ${'left'}
+    ${'right'}
+  `('renders the $orientation orientation', ({ orientation }) => {
+    render(<Arrow orientation={orientation} />)
+    expect(TestUtils.select('Arrow__Wrapper')?.getAttribute('orientation')).toBe(orientation)
+  })
+
+  it('renderes with primary colors', () => {
+    const { rerender } = render(<Arrow primary />)
+    expect(document.querySelector(`g[stroke="${ThemePalette.primary}"]`)).toBeTruthy()
+    rerender(<Arrow />)
+    expect(document.querySelector(`g[stroke="${ThemePalette.grayscale[4]}"]`)).toBeTruthy()
+  })
+
+  it('renderes with primary colors', () => {
+    const { rerender } = render(<Arrow primary />)
+    expect(document.querySelector(`g[stroke="${ThemePalette.primary}"]`)).toBeTruthy()
+    rerender(<Arrow />)
+    expect(document.querySelector(`g[stroke="${ThemePalette.grayscale[4]}"]`)).toBeTruthy()
+  })
+})

+ 36 - 0
src/components/ui/AutocompleteInput/AutocompleteInput.spec.tsx

@@ -0,0 +1,36 @@
+/*
+Copyright (C) 2021  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import React from 'react'
+import { render } from '@testing-library/react'
+import TestUtils from '@tests/TestUtils'
+import AutocompleteInput from './AutocompleteInput'
+
+describe('AutocompleteInput', () => {
+  it('renders correct data', () => {
+    render(<AutocompleteInput value="searching" onChange={() => { }} />)
+    expect(TestUtils.selectInput('TextInput__Input')!.value).toBe('searching')
+  })
+
+  it('calls focus and blur', () => {
+    const onFocus = jest.fn()
+    const onBlur = jest.fn()
+    render(<AutocompleteInput value="" onChange={() => { }} onFocus={onFocus} onBlur={onBlur} />)
+    const inputElement = TestUtils.select('TextInput__Input')
+    inputElement?.focus()
+    expect(onFocus).toHaveBeenCalled()
+    inputElement?.blur()
+    expect(onBlur).toHaveBeenCalled()
+  })
+})

+ 0 - 2
src/components/ui/AutocompleteInput/AutocompleteInput.tsx

@@ -111,7 +111,6 @@ class AutocompleteInput extends React.Component<Props, State> {
         }}
       >
         <TextInput
-          data-test-id="acInput-text"
           disabled={disabled}
           value={this.props.value}
           onChange={e => { this.props.onChange(e.target.value) }}
@@ -134,7 +133,6 @@ class AutocompleteInput extends React.Component<Props, State> {
           onInputKeyDown={this.props.onInputKeyDown}
         />
         <Arrow
-          data-test-id="acInput-arrow"
           disabled={disabled}
           dangerouslySetInnerHTML={{ __html: arrowImage }}
           onClick={() => { if (this.textInputRef) this.textInputRef.focus() }}

+ 37 - 0
src/components/ui/Button/Button.spec.tsx

@@ -0,0 +1,37 @@
+/*
+Copyright (C) 2021  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import React from 'react'
+import { render } from '@testing-library/react'
+import TestUtils from '@tests/TestUtils'
+import { ThemePalette } from '@src/components/Theme'
+import Button from './Button'
+
+describe('Button', () => {
+  it('should render with different style props', () => {
+    const { rerender } = render(<Button disabled />)
+    expect(document.querySelector('button')?.hasAttribute('disabled')).toBeTruthy()
+    expect(TestUtils.rgbToHex(window.getComputedStyle(document.querySelector('button')!).backgroundColor)).toBe(ThemePalette.primary)
+
+    rerender(<Button secondary />)
+    expect(TestUtils.rgbToHex(window.getComputedStyle(document.querySelector('button')!).backgroundColor)).toBe(ThemePalette.secondaryLight)
+  })
+
+  it('fires click', () => {
+    const onClick = jest.fn()
+    render(<Button onClick={onClick} />)
+    document.querySelector('button')?.click()
+    expect(onClick).toHaveBeenCalled()
+  })
+})

+ 0 - 43
src/components/ui/Button/test.tsx

@@ -1,43 +0,0 @@
-/*
-Copyright (C) 2017  Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-import React from 'react'
-import { shallow } from 'enzyme'
-import sinon from 'sinon'
-import Button from '.'
-
-const wrap = props => shallow(<Button {...props} />)
-
-describe('Button Component', () => {
-  it('renders with different combination of props', () => {
-    let wrapper = wrap({ disabled: true })
-    expect(wrapper.prop('disabled')).toBe(true)
-    wrapper = wrap({ primary: true })
-    expect(wrapper.prop('disabled')).toBe(undefined)
-    expect(wrapper.prop('primary')).toBe(true)
-    wrapper = wrap({ disabled: true, primary: true })
-    expect(wrapper.prop('disabled')).toBe(true)
-    expect(wrapper.prop('primary')).toBe(true)
-  })
-
-  it('dispatches click event', () => {
-    const onButtonClick = sinon.spy()
-    const wrapper = wrap({ onClick: onButtonClick })
-    wrapper.simulate('click')
-    expect(onButtonClick.calledOnce).toBe(true)
-  })
-})
-
-
-

+ 39 - 0
src/components/ui/Checkbox/Checkbox.spec.tsx

@@ -0,0 +1,39 @@
+/*
+Copyright (C) 2021  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import React from 'react'
+import { render } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+
+import TestUtils from '@tests/TestUtils'
+import Checkbox from './Checkbox'
+
+describe('Checkbox', () => {
+  it('dispatches change on space key', () => {
+    const onChange = jest.fn()
+    const { rerender } = render(<Checkbox onChange={onChange} />)
+    userEvent.type(TestUtils.select('Checkbox__Wrapper')!, ' ')
+    expect(onChange).toHaveBeenCalledWith(true)
+    rerender(<Checkbox onChange={onChange} checked />)
+    userEvent.type(TestUtils.select('Checkbox__Wrapper')!, ' ')
+    expect(onChange).toHaveBeenCalledWith(false)
+  })
+
+  it('doesn\'t dispatch change if disabled', () => {
+    const onChange = jest.fn()
+    render(<Checkbox onChange={onChange} disabled />)
+    userEvent.type(TestUtils.select('Checkbox__Wrapper')!, ' ')
+    expect(onChange).not.toHaveBeenCalled()
+  })
+})

+ 0 - 2
src/components/ui/Checkbox/Checkbox.tsx

@@ -56,7 +56,6 @@ type Props = {
   checked?: boolean,
   disabled?: boolean,
   onChange?: (checked: boolean) => void,
-  'data-test-id'?: string,
   onMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void,
   onMouseUp?: (e: React.MouseEvent<HTMLDivElement>) => void,
 }
@@ -81,7 +80,6 @@ class Checkbox extends React.Component<Props> {
   render() {
     return (
       <Wrapper
-        data-test-id={this.props['data-test-id'] || 'checkbox'}
         className={this.props.className}
         onClick={() => { this.handleClick() }}
         checked={this.props.checked}

+ 0 - 44
src/components/ui/Checkbox/test.tsx

@@ -1,44 +0,0 @@
-/*
-Copyright (C) 2017  Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-import React from 'react'
-import { shallow } from 'enzyme'
-import sinon from 'sinon'
-import Checkbox from '../Checkbox'
-
-const wrap = props => shallow(<Checkbox {...props} />)
-
-describe('Checkbox Component', () => {
-  it('passes `checked` to the component', () => {
-    let wrapper = wrap({ checked: true, onChange: () => {} })
-    expect(wrapper.prop('checked')).toBe(true)
-  })
-
-  it('calls `onChange` with correct value, on click', () => {
-    let onChange = sinon.spy()
-    let wrapper = wrap({ checked: false, onChange })
-    wrapper.simulate('click')
-    expect(onChange.args[0][0]).toBe(true)
-  })
-
-  it('doesn\'t call `onChange` if disabled', () => {
-    let onChange = sinon.spy()
-    let wrapper = wrap({ checked: false, onChange, disabled: true })
-    wrapper.simulate('click')
-    expect(onChange.notCalled).toBe(true)
-  })
-})
-
-
-

+ 14 - 15
src/components/ui/StatusComponents/StatusPill/test.tsx → src/components/ui/CopyButton/CopyButton.spec.tsx

@@ -1,5 +1,5 @@
 /*
-Copyright (C) 2017  Cloudbase Solutions SRL
+Copyright (C) 2021  Cloudbase Solutions SRL
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as
 published by the Free Software Foundation, either version 3 of the
@@ -13,22 +13,21 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 import React from 'react'
-import { shallow } from 'enzyme'
-import StatusPill from '.'
+import { render } from '@testing-library/react'
+import TestUtils from '@tests/TestUtils'
+import CopyButton from './CopyButton'
 
-const wrap = props => shallow(<StatusPill {...props} />)
-
-describe('StatusPill Component', () => {
-  it('renders label if given', () => {
-    let wrapper = wrap({ label: 'the_value', status: 'COMPLETED' })
-    expect(wrapper.dive().text()).toBe('the_value')
+describe('CopyButton', () => {
+  it('renders with no opacity', () => {
+    render(<CopyButton />)
+    expect(window.getComputedStyle(TestUtils.select('CopyButton__Wrapper')!).opacity).toBe('0')
   })
 
-  it('renders status as label if no label is given', () => {
-    let wrapper = wrap({ status: 'COMPLETED' })
-    expect(wrapper.dive().text()).toBe('COMPLETED')
+  it('dispatches click', () => {
+    const onClick = jest.fn()
+    render(<CopyButton onClick={onClick} />)
+    const button = TestUtils.select('CopyButton__Wrapper') as HTMLElement
+    button.click()
+    expect(onClick).toHaveBeenCalled()
   })
 })
-
-
-

+ 38 - 0
src/components/ui/CopyMultilineValue/CopyMultilineValue.spec.tsx

@@ -0,0 +1,38 @@
+/*
+Copyright (C) 2021  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import React from 'react'
+import { render } from '@testing-library/react'
+import TestUtils from '@tests/TestUtils'
+import DomUtils from '@src/utils/DomUtils'
+import CopyMultineValue from './CopyMultilineValue'
+
+jest.mock('../../../utils/DomUtils')
+
+describe('CopyMultilineValue', () => {
+  it('copies value to clipboard', () => {
+    const onCopy = jest.fn()
+    render(<CopyMultineValue value="test value" onCopy={onCopy} />)
+    TestUtils.select('CopyMultilineValue__Wrapper')!.click()
+    expect(DomUtils.copyTextToClipboard).toHaveBeenCalledWith('test value')
+    expect(onCopy).toHaveBeenCalledWith('test value')
+  })
+
+  it('transforms dangerous HTML', () => {
+    const onCopy = jest.fn()
+    render(<CopyMultineValue useDangerousHtml onCopy={onCopy} value="this<br />is <b>OK</b>" />)
+    TestUtils.select('CopyMultilineValue__Wrapper')!.click()
+    expect(onCopy).toHaveBeenCalledWith('this\nis OK')
+  })
+})

+ 0 - 2
src/components/ui/CopyMultilineValue/CopyMultilineValue.tsx

@@ -34,7 +34,6 @@ const Wrapper = styled.div<any>`
 `
 
 type Props = {
-  'data-test-id'?: string,
   value: string | null | undefined,
   onCopy?: (value: string) => void,
   useDangerousHtml?: boolean,
@@ -67,7 +66,6 @@ class CopyMultineValue extends React.Component<Props> {
     return (
       <Wrapper
         onClick={() => { this.handleCopy() }}
-        data-test-id={(this.props && this.props['data-test-id']) || 'copyMultilineValue'}
       >
         {text}
         <CopyButtonStyled />

+ 0 - 38
src/components/ui/CopyMultilineValue/test.tsx

@@ -1,38 +0,0 @@
-/*
-Copyright (C) 2018  Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-import React from 'react'
-import { shallow } from 'enzyme'
-import sinon from 'sinon'
-import CopyMultilineValue from '../CopyMultilineValue'
-
-const wrap = props => shallow(<CopyMultilineValue value="" {...props} />)
-
-describe('CopyMultilineValue Component', () => {
-  it('renders `value`', () => {
-    const wrapper = wrap({ value: 'the_value' })
-    expect(wrapper.dive().text()).toBe('the_value<Styled(CopyButton) />')
-  })
-
-  it('copies `value` to clipboard', () => {
-    const onCopy = sinon.spy()
-    const wrapper = wrap({ value: 'the_value', onCopy })
-    wrapper.simulate('click')
-    expect(onCopy.calledOnce).toBe(true)
-    expect(onCopy.args[0][0]).toBe('the_value')
-  })
-})
-
-
-

+ 34 - 0
src/components/ui/CopyValue/CopyValue.spec.tsx

@@ -0,0 +1,34 @@
+/*
+Copyright (C) 2021  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import React from 'react'
+import { render } from '@testing-library/react'
+import TestUtils from '@tests/TestUtils'
+import DomUtils from '@src/utils/DomUtils'
+import CopyValue from './CopyValue'
+
+jest.mock('../../../utils/DomUtils')
+
+describe('CopyValue', () => {
+  it('copies value to clipboard', () => {
+    render(<CopyValue value="value" />)
+    TestUtils.select('CopyValue__Wrapper')!.click()
+    expect(DomUtils.copyTextToClipboard).toHaveBeenCalledWith('value')
+  })
+
+  it('capitalizes the value', () => {
+    render(<CopyValue capitalize value="value" />)
+    expect(window.getComputedStyle(TestUtils.select('CopyValue__Wrapper')!).textTransform).toBe('capitalize')
+  })
+})

+ 0 - 3
src/components/ui/CopyValue/CopyValue.tsx

@@ -46,7 +46,6 @@ type Props = {
   width?: string,
   maxWidth?: string,
   capitalize?: boolean,
-  'data-test-id'?: string,
   onCopy?: (value: string) => void,
   style?: React.CSSProperties
 }
@@ -71,12 +70,10 @@ class CopyValue extends React.Component<Props> {
         onClick={(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => { this.handleCopyIdClick(e) }}
         onMouseDown={(e: { stopPropagation: () => void }) => { e.stopPropagation() }}
         onMouseUp={(e: { stopPropagation: () => void }) => { e.stopPropagation() }}
-        data-test-id={this.props['data-test-id'] || 'copyValue'}
         capitalize={this.props.capitalize}
         style={this.props.style}
       >
         <Value
-          data-test-id="copyValue-value"
           width={this.props.width}
           maxWidth={this.props.maxWidth}
         >{this.props.label || this.props.value}

+ 0 - 39
src/components/ui/CopyValue/test.tsx

@@ -1,39 +0,0 @@
-/*
-Copyright (C) 2018  Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-import React from 'react'
-import { shallow } from 'enzyme'
-import sinon from 'sinon'
-import TestWrapper from '../../../utils/TestWrapper'
-import CopyValue from '../CopyValue'
-
-const wrap = props => new TestWrapper(shallow(<CopyValue value="the_value" {...props} />), 'copyValue')
-
-describe('CopyValue Component', () => {
-  it('renders `value`', () => {
-    const wrapper = wrap()
-    expect(wrapper.findText('value')).toBe('the_value')
-  })
-
-  it('copies `value` to clipboard', () => {
-    const onCopy = sinon.spy()
-    const wrapper = wrap({ onCopy })
-    wrapper.simulate('click')
-    expect(onCopy.calledOnce).toBe(true)
-    expect(onCopy.args[0][0]).toBe('the_value')
-  })
-})
-
-
-

+ 61 - 0
src/components/ui/DatetimePicker/DatetimePicker.spec.tsx

@@ -0,0 +1,61 @@
+/*
+Copyright (C) 2021  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import React from 'react'
+import moment from 'moment'
+import { render } from '@testing-library/react'
+import TestUtils from '@tests/TestUtils'
+import DatetimePicker from './DatetimePicker'
+
+const DATE = new Date('2021-11-12T12:32:44.426Z')
+
+describe('DatetimePicker', () => {
+  it('renders date value in UTC timezone in dropdown label', () => {
+    render(
+      <DatetimePicker
+        onChange={() => { }}
+        timezone="utc"
+        value={DATE}
+      />,
+    )
+
+    const expected = moment(DATE)
+      .add(new Date().getTimezoneOffset(), 'minutes')
+      .format('DD/MM/YYYY hh:mm A')
+
+    expect(TestUtils.select('DropdownButton__Label')?.innerHTML).toEqual(expected)
+  })
+
+  it('changes the date', () => {
+    render(
+      <DatetimePicker
+        onChange={() => { }}
+        timezone="utc"
+        value={DATE}
+      />,
+    )
+    expect(TestUtils.select('DatetimePicker__Portal')).toBeNull()
+    TestUtils.select('DropdownButton__Wrapper')?.click()
+    expect(TestUtils.select('DatetimePicker__Portal')).not.toBeNull()
+    const firstDay = document.querySelector<HTMLElement>('td.rdtDay[data-value="1"]')
+    firstDay?.click()
+
+    const expected = moment(DATE)
+      .set('date', 1)
+      .add(new Date().getTimezoneOffset(), 'minutes')
+      .format('DD/MM/YYYY hh:mm A')
+
+    expect(TestUtils.select('DropdownButton__Label')?.innerHTML).toEqual(expected)
+  })
+})

+ 0 - 1
src/components/ui/DatetimePicker/DatetimePicker.tsx

@@ -224,7 +224,6 @@ class DatetimePicker extends React.Component<Props, State> {
         <Wrapper>
           <DropdownButtonStyled
             customRef={e => { this.buttonRef = e }}
-            data-test-id="datetimePicker-dropdownButton"
             width={207}
             value={(timezoneDate && moment(timezoneDate).format('DD/MM/YYYY hh:mm A')) || '-'}
             centered

+ 0 - 42
src/components/ui/DatetimePicker/test.tsx

@@ -1,42 +0,0 @@
-/*
-Copyright (C) 2017  Cloudbase Solutions SRL
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Affero General Public License as
-published by the Free Software Foundation, either version 3 of the
-License, or (at your option) any later version.
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU Affero General Public License for more details.
-You should have received a copy of the GNU Affero General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-*/
-
-import React from 'react'
-import { shallow } from 'enzyme'
-import moment from 'moment'
-import sinon from 'sinon'
-import TestWrapper from '../../../utils/TestWrapper'
-import DatetimePicker from '.'
-
-const wrap = props => new TestWrapper(shallow(<DatetimePicker timezone="local" {...props} />), 'datetimePicker')
-
-describe('DateTimePicker Component', () => {
-  it('renders date value in dropdown label', () => {
-    let onChange = sinon.spy()
-    let wrapper = wrap({ value: new Date(2017, 3, 21, 14, 22), onChange })
-    let label = '21/04/2017 02:22 PM'
-    expect(wrapper.find('dropdownButton').prop('value')).toBe(label)
-  })
-
-  it('renders date value in UTC timezone in dropdown label', () => {
-    let onChange = sinon.spy()
-    const date = new Date(2017, 3, 21, 14, 22)
-    let wrapper = wrap({ value: date, onChange, timezone: 'utc' })
-    const label = moment(date).add(new Date().getTimezoneOffset(), 'minutes').format('DD/MM/YYYY hh:mm A')
-    expect(wrapper.find('dropdownButton').prop('value')).toBe(label)
-  })
-})
-
-
-

+ 94 - 0
src/components/ui/Dropdowns/ActionDropdown/ActionDropdown.spec.tsx

@@ -0,0 +1,94 @@
+/*
+Copyright (C) 2021  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+import React from 'react'
+import { render } from '@testing-library/react'
+import TestUtils from '@tests/TestUtils'
+import ActionDropdown, { Action } from './ActionDropdown'
+
+const ACTIONS: Action[] = [
+  {
+    label: 'Action 1',
+    title: 'Action 1 Description',
+    action: jest.fn(),
+  },
+  {
+    label: 'Action 2',
+    disabled: true,
+    action: jest.fn(),
+
+  },
+  {
+    label: 'Action 3',
+    loading: true,
+    action: jest.fn(),
+  },
+  {
+    label: 'Action 4',
+    hidden: true,
+    action: jest.fn(),
+  },
+]
+
+describe('ActionDropdown', () => {
+  it('renders button label', () => {
+    const { rerender } = render(<ActionDropdown actions={ACTIONS} />)
+    expect(TestUtils.select('DropdownButton__Label')?.textContent).toBe('Actions')
+    rerender(<ActionDropdown actions={ACTIONS} label="Actions Label" />)
+    expect(TestUtils.select('DropdownButton__Label')?.textContent).toBe('Actions Label')
+  })
+
+  it('renders only visible actions', () => {
+    render(<ActionDropdown actions={ACTIONS} />)
+    TestUtils.select('DropdownButton__Wrapper')!.click()
+    expect(TestUtils.selectAll('ActionDropdown__ListItem').length).toBe(3)
+    TestUtils.selectAll('ActionDropdown__ListItem').forEach((item, index) => {
+      expect(item.textContent).toBe(ACTIONS[index].label)
+    })
+  })
+
+  it('renders actions with props', () => {
+    render(<ActionDropdown actions={ACTIONS} />)
+    TestUtils.select('DropdownButton__Wrapper')!.click()
+    TestUtils.selectAll('ActionDropdown__ListItem').forEach((item, index) => {
+      if (ACTIONS[index].disabled) {
+        expect(item.hasAttribute('disabled')).toBe(true)
+      } else {
+        expect(item.hasAttribute('disabled')).toBe(false)
+      }
+      if (ACTIONS[index].title) {
+        expect(item.getAttribute('title')).toBe(ACTIONS[index].title)
+      }
+      if (ACTIONS[index].loading) {
+        expect(TestUtils.select('StatusIcon__Wrapper', item)).toBeTruthy()
+      } else {
+        expect(TestUtils.select('StatusIcon__Wrapper', item)).toBeFalsy()
+      }
+    })
+  })
+
+  it('fires click events correctly', () => {
+    render(<ActionDropdown actions={ACTIONS} />)
+    TestUtils.select('DropdownButton__Wrapper')!.click()
+    TestUtils.selectAll('ActionDropdown__ListItem').forEach((item, index) => {
+      item.click()
+      if (ACTIONS[index].disabled || ACTIONS[index].loading) {
+        expect(ACTIONS[index].action).not.toHaveBeenCalled()
+      } else {
+        TestUtils.select('DropdownButton__Wrapper')!.click()
+        expect(ACTIONS[index].action).toHaveBeenCalled()
+      }
+    })
+  })
+})

+ 1 - 4
src/components/ui/Dropdowns/ActionDropdown/ActionDropdown.tsx

@@ -66,7 +66,6 @@ export type Props = {
   label: string,
   actions: Action[],
   style?: any,
-  'data-test-id'?: string,
   largeItems?: boolean
 }
 
@@ -172,7 +171,6 @@ class ActionDropdown extends React.Component<Props, State> {
             onClick={() => { this.handleItemClick(action) }}
             color={action.color}
             disabled={action.disabled}
-            data-test-id={`${TEST_ID}-listItem-${action.label}`}
             title={action.title}
             large={this.props.largeItems}
           >
@@ -206,14 +204,13 @@ class ActionDropdown extends React.Component<Props, State> {
 
   render() {
     return (
-      <Wrapper style={this.props.style} data-test-id={this.props['data-test-id']}>
+      <Wrapper style={this.props.style}>
         <DropdownButton
           secondary
           centered
           value={this.props.label}
           customRef={ref => { this.buttonRef = ref }}
           onClick={() => { this.handleButtonClick() }}
-          data-test-id={`${TEST_ID}-dropdownButton`}
         />
         {this.renderList()}
       </Wrapper>

Некоторые файлы не были показаны из-за большого количества измененных файлов