Skip to content

Commit 0e064c0

Browse files
author
Philipp Duwe
committed
feat 1398: Introduction of UI tests into the Configuration UI project
1 parent ae14c5e commit 0e064c0

File tree

13 files changed

+6060
-6286
lines changed

13 files changed

+6060
-6286
lines changed

.github/workflows/configuration_ui_test.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,8 @@ jobs:
3333

3434
- name: Run Prettier
3535
working-directory: ${{env.working-directory}}
36-
run: yarn format
36+
run: yarn format
37+
38+
- name: Run Jest
39+
working-directory: ${{env.working-directory}}
40+
run: yarn test

components/inspectit-ocelot-configurationserver-ui/.eslintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"env": {
33
"browser": true,
44
"es6": true,
5-
"node": true
5+
"node": true,
6+
"jest": true
67
},
78
"plugins": [
89
"import",
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
module.exports = {
2+
collectCoverageFrom: ['**/*.{js,jsx,ts,tsx}', '!**/*.d.ts', '!**/node_modules/**'],
3+
moduleNameMapper: {
4+
// Handle CSS imports (with CSS modules)
5+
// https://jestjs.io/docs/webpack#mocking-css-modules
6+
'^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',
7+
8+
// Handle CSS imports (without CSS modules)
9+
'^.+\\.(css|sass|scss)$': '<rootDir>/__mocks__/styleMock.js',
10+
11+
// Handle image imports
12+
// https://jestjs.io/docs/webpack#handling-static-assets
13+
'^.+\\.(png|jpg|jpeg|gif|webp|avif|ico|bmp|svg)$/i': `<rootDir>/__mocks__/fileMock.js`,
14+
15+
// Handle module aliases
16+
'^@/components/(.*)$': '<rootDir>/components/$1',
17+
},
18+
// Add more setup options before each test is run
19+
// setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
20+
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.next/'],
21+
testEnvironment: 'jsdom',
22+
transform: {
23+
// Use babel-jest to transpile tests with the next/babel preset
24+
// https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object
25+
'^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }],
26+
},
27+
transformIgnorePatterns: ['/node_modules/', '^.+\\.module\\.(css|sass|scss)$'],
28+
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
29+
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import '@testing-library/jest-dom/extend-expect';
2+
import { setConfig } from 'next/config';
3+
import config from './next.config';
4+
setConfig(config);
Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,41 @@
1-
const withCSS = require('@zeit/next-css')
1+
const withCSS = require('@zeit/next-css');
22
const isProduction = process.env.NODE_ENV === 'production';
33

44
module.exports = withCSS({
5-
distDir: '../.next',
5+
distDir: '../.next',
66

7-
// Each page will be exported as a directory
8-
exportTrailingSlash: true,
7+
// Each page will be exported as a directory
8+
exportTrailingSlash: true,
99

10-
assetPrefix: isProduction ? '/ui' : '',
10+
assetPrefix: isProduction ? '/ui' : '',
1111

12-
// Will only be available on the server side
13-
serverRuntimeConfig: {
14-
},
12+
// Will only be available on the server side
13+
serverRuntimeConfig: {},
1514

16-
// Will be available on both server and client
17-
publicRuntimeConfig: {
18-
// used in '/components/basics/Link.js', for more details go to the component itself
19-
linkPrefix: isProduction ? '/ui' : ''
20-
},
15+
// Will be available on both server and client
16+
publicRuntimeConfig: {
17+
// used in '/components/basics/Link.js', for more details go to the component itself
18+
linkPrefix: isProduction ? '/ui' : '',
19+
},
2120

22-
// Required for successfully importing CSS files (e.g. from PrimeReact)
23-
// See: https://github.com/zeit/next-plugins/issues/273#issuecomment-430597241
24-
webpack: function (config) {
25-
config.module.rules.push({
26-
test: /\.(eot|woff|woff2|ttf|svg|png|jpg|gif)$/,
27-
use: {
28-
loader: 'url-loader',
29-
options: {
30-
limit: 100000,
31-
name: '[name].[ext]'
32-
}
33-
}
34-
})
35-
return config
36-
},
21+
// Required for successfully importing CSS files (e.g. from PrimeReact)
22+
// See: https://github.com/zeit/next-plugins/issues/273#issuecomment-430597241
23+
webpack: function (config) {
24+
config.module.rules.push({
25+
test: /\.(eot|woff|woff2|ttf|svg|png|jpg|gif)$/,
26+
use: {
27+
loader: 'url-loader',
28+
options: {
29+
limit: 100000,
30+
name: '[name].[ext]',
31+
},
32+
},
33+
});
34+
return config;
35+
},
3736

38-
env: {
39-
VERSION: process.env.CIRCLE_TAG || "SNAPSHOT",
40-
BUILD_DATE: new Date().toUTCString()
41-
}
42-
})
37+
env: {
38+
VERSION: process.env.CIRCLE_TAG || 'SNAPSHOT',
39+
BUILD_DATE: new Date().toUTCString(),
40+
},
41+
});

components/inspectit-ocelot-configurationserver-ui/package.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,23 @@
2222
"format": "prettier --check \"./src/**/*.+(js|jsx|json|css|md)\"",
2323
"format:write": "prettier --write \"./src/**/*.+(js|jsx|json|css|md)\"",
2424
"storybook": "start-storybook -p 6006",
25-
"build-storybook": "build-storybook"
25+
"build-storybook": "build-storybook",
26+
"test": "jest",
27+
"test:watch": "jest --watch"
2628
},
2729
"dependencies": {
30+
"@reduxjs/toolkit": "^1.8.2",
31+
"@testing-library/jest-dom": "^5.16.4",
32+
"@testing-library/react": "^11.2.5",
33+
"@testing-library/user-event": "^14.2.0",
2834
"@zeit/next-css": "^1.0.1",
2935
"ace-builds": "^1.4.5",
3036
"axios": "^0.26.1",
37+
"babel-jest": "^28.1.0",
3138
"classnames": "^2.2.6",
3239
"dateformat": "^3.0.3",
40+
"jest": "^28.1.0",
41+
"jest-environment-jsdom": "^28.1.0",
3342
"js-yaml": "^3.13.1",
3443
"jwt-decode": "^2.2.0",
3544
"lodash": "^4.17.21",
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { render, screen, within } from '@testing-library/react';
2+
import LoginView from '../LoginView';
3+
import '@testing-library/jest-dom';
4+
import React from 'react';
5+
import { storeWrapper } from '../../../lib/reduxTestUtils';
6+
import { authentication } from '../../../redux/ducks';
7+
import userEvent from '@testing-library/user-event';
8+
9+
const setup = () => {
10+
const reducers = { authentication };
11+
return render(storeWrapper(<LoginView />, reducers));
12+
};
13+
14+
describe('LoginView', () => {
15+
//Arrange
16+
beforeEach(() => setup());
17+
18+
it('renders successfully', () => {
19+
//Arrange
20+
const logo = screen.getByRole('img');
21+
const heading1 = screen.getByText('inspectIT Ocelot');
22+
const heading2 = screen.getByText('Configuration Server');
23+
const username = screen.getByRole('textbox', { placeholder: 'Username' });
24+
const password = screen.getByRole('textbox', { placeholder: 'Password' });
25+
const loginButton = screen.getByRole('button', { name: 'Login' });
26+
const footer = screen.getByText(/inspectit ocelot configuration server/i);
27+
const docsLink = within(footer).getByRole('link', { name: 'Docs' });
28+
const githubLink = within(footer).getByRole('link', { name: 'Github' });
29+
30+
//Act - not required
31+
32+
//Assert
33+
expect(logo).toBeInTheDocument();
34+
expect(heading1).toBeInTheDocument();
35+
expect(heading2).toBeInTheDocument();
36+
expect(username).toBeInTheDocument();
37+
expect(password).toBeInTheDocument();
38+
expect(loginButton).toBeInTheDocument();
39+
expect(docsLink).toBeInTheDocument();
40+
expect(githubLink).toBeInTheDocument();
41+
});
42+
43+
it('disables the login button when username is missing', async () => {
44+
//Arrange
45+
const loginButton = screen.getByRole('button', { name: 'Login' });
46+
const password = screen.getByPlaceholderText('Password');
47+
48+
//Act
49+
await userEvent.type(password, 'password123');
50+
51+
//Assert
52+
expect(loginButton).toBeDisabled();
53+
});
54+
55+
it('disables the login button when password is missing', async () => {
56+
//Arrange
57+
const loginButton = screen.getByRole('button', { name: 'Login' });
58+
const username = screen.getByPlaceholderText('Username');
59+
60+
//Act
61+
await userEvent.type(username, 'userName');
62+
63+
//Assert
64+
expect(loginButton).toBeDisabled();
65+
});
66+
67+
it('enables the login button when username and password are present', async () => {
68+
//Arrange
69+
const loginButton = screen.getByRole('button', { name: 'Login' });
70+
const username = screen.getByPlaceholderText('Username');
71+
const password = screen.getByPlaceholderText('Password');
72+
73+
//Act
74+
await userEvent.type(username, 'userName');
75+
await userEvent.type(password, 'password123');
76+
77+
//Assert
78+
expect(loginButton).toBeEnabled();
79+
});
80+
});

components/inspectit-ocelot-configurationserver-ui/src/components/views/alerting/rules/editor/VariableView.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,6 @@ const ValueEditor = ({ type, value, options, readOnly, onDataChanged }) => {
204204
* Simple value display component.
205205
*/
206206
const SimpleDataView = ({ value, isDefault, error }) => {
207-
const style = {};
208207
return (
209208
<>
210209
<style jsx>{`

components/inspectit-ocelot-configurationserver-ui/src/components/views/configuration/FileTree.js

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -116,36 +116,36 @@ class FileTree extends React.Component {
116116
return (
117117
<div className="this" onContextMenu={readOnly ? undefined : this.showContextMenu} onKeyDown={readOnly ? undefined : this.onKeyDown}>
118118
<style jsx>{`
119-
.this {
120-
overflow: auto;
121-
flex-grow: 1;
122-
display: flex;
123-
flex-direction: column;
124-
}
125-
.this :global(.cm-tree-icon) {
126-
width: 1.3rem;
127-
height: 1.3rem;
128-
}
129-
.this :global(.cm-tree-label) {
130-
color: #aaa;
131-
}
132-
.this :global(.ocelot-tree-head-orange) {
133-
background: url("${linkPrefix}/static/images/inspectit-ocelot-head_orange.svg") center no-repeat;
134-
background-size: 1rem 1rem;
135-
}
136-
.this :global(.ocelot-tree-head-white) {
137-
background: url("${linkPrefix}/static/images/inspectit-ocelot-head_white.svg") center no-repeat;
138-
background-size: 1rem 1rem;
139-
}
140-
.tree-container {
141-
overflow: auto;
142-
}
143-
.version-banner {
144-
background-color: #ffcc80;
145-
height: 2.45rem;
146-
border-bottom: 1px solid #dddddd;
147-
}
148-
`}</style>
119+
.this {
120+
overflow: auto;
121+
flex-grow: 1;
122+
display: flex;
123+
flex-direction: column;
124+
}
125+
.this :global(.cm-tree-icon) {
126+
width: 1.3rem;
127+
height: 1.3rem;
128+
}
129+
.this :global(.cm-tree-label) {
130+
color: #aaa;
131+
}
132+
.this :global(.ocelot-tree-head-orange) {
133+
background: url('${linkPrefix}/static/images/inspectit-ocelot-head_orange.svg') center no-repeat;
134+
background-size: 1rem 1rem;
135+
}
136+
.this :global(.ocelot-tree-head-white) {
137+
background: url('${linkPrefix}/static/images/inspectit-ocelot-head_white.svg') center no-repeat;
138+
background-size: 1rem 1rem;
139+
}
140+
.tree-container {
141+
overflow: auto;
142+
}
143+
.version-banner {
144+
background-color: #ffcc80;
145+
height: 2.45rem;
146+
border-bottom: 1px solid #dddddd;
147+
}
148+
`}</style>
149149
{selectedVersion && <div className="version-banner" />}
150150
<div className="tree-container">
151151
<ContextMenu model={this.state.contextMenuModel} ref={this.contextMenuRef} />
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Provider } from 'react-redux';
2+
import React from 'react';
3+
import { configureStore } from '@reduxjs/toolkit';
4+
5+
/**
6+
* wrapper function for testing purposes that include a redux store
7+
*/
8+
export function storeWrapper(jsx, reducers) {
9+
const store = mockStore(reducers);
10+
return <Provider store={store}>{jsx}</Provider>;
11+
}
12+
13+
/**
14+
* store configuration function that supplies a store with the provided reducers
15+
*/
16+
export function mockStore(reducers) {
17+
return configureStore({
18+
reducer: reducers,
19+
middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false }),
20+
});
21+
}

components/inspectit-ocelot-configurationserver-ui/src/redux/middlewares/logger.js

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,25 @@ const STARTED = ['background: darkorange', 'color: white'].join(';');
77

88
const FAILURE = ['background: red', 'color: white'].join(';');
99

10-
const createLogger = (active = true) => (store) => (next) => (action) => {
11-
if (!active || typeof action === 'function') {
12-
return next(action);
13-
}
14-
15-
const prevState = store.getState();
16-
const result = next(action);
17-
const nextState = store.getState();
18-
logGroupCollapsed(`%c ${action.type} `, determineStyle(action));
19-
logInfo('%cprev state', 'color: darkorange', prevState);
20-
logInfo('%caction payload', 'color: blue', action.payload);
21-
logInfo('%cnext state', 'color: darkgreen', nextState);
22-
logGroupEnd();
23-
return result;
24-
};
10+
const createLogger =
11+
(active = true) =>
12+
(store) =>
13+
(next) =>
14+
(action) => {
15+
if (!active || typeof action === 'function') {
16+
return next(action);
17+
}
18+
19+
const prevState = store.getState();
20+
const result = next(action);
21+
const nextState = store.getState();
22+
logGroupCollapsed(`%c ${action.type} `, determineStyle(action));
23+
logInfo('%cprev state', 'color: darkorange', prevState);
24+
logInfo('%caction payload', 'color: blue', action.payload);
25+
logInfo('%cnext state', 'color: darkgreen', nextState);
26+
logGroupEnd();
27+
return result;
28+
};
2529

2630
export default createLogger;
2731

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
/* eslint default-param-last: 0 */
2-
export default (initialState) => (reducerMap) => (state = initialState, action) => {
3-
const reducer = reducerMap[action.type];
4-
return reducer ? reducer(state, action) : state;
5-
};
2+
export default (initialState) =>
3+
(reducerMap) =>
4+
(state = initialState, action) => {
5+
const reducer = reducerMap[action.type];
6+
return reducer ? reducer(state, action) : state;
7+
};

0 commit comments

Comments
 (0)