Skip to content

Commit

Permalink
Merge pull request #2613 from andrewbaldwin44/feature/test-app
Browse files Browse the repository at this point in the history
[Modern UI] Refactor and Add Tests for Entrypoints
  • Loading branch information
cyberw committed Feb 26, 2024
2 parents 0c286a3 + 69ea0a0 commit 355d2e8
Show file tree
Hide file tree
Showing 20 changed files with 444 additions and 187 deletions.
2 changes: 1 addition & 1 deletion locust/webui/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"group": "internal"
},
{
"pattern": "{api,components,constants,hooks,redux,styles,test,types,utils}/**",
"pattern": "{api,components,constants,hooks,pages,redux,styles,test,types,utils}/**",
"group": "internal"
}
],
Expand Down

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion locust/webui/dist/auth.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<meta name="theme-color" content="#000000" />

<title>Locust</title>
<script type="module" crossorigin src="/assets/index-ad6f2665.js"></script>
<script type="module" crossorigin src="/assets/index-207c29ed.js"></script>
</head>
<body>
<div id="root"></div>
Expand Down
2 changes: 1 addition & 1 deletion locust/webui/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<meta name="theme-color" content="#000000" />

<title>Locust</title>
<script type="module" crossorigin src="/assets/index-ad6f2665.js"></script>
<script type="module" crossorigin src="/assets/index-207c29ed.js"></script>
</head>
<body>
<div id="root"></div>
Expand Down
35 changes: 35 additions & 0 deletions locust/webui/src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { describe, expect, it, vi } from 'vitest';

import App from 'App';
import * as authConstants from 'constants/auth';
import * as swarmConstants from 'constants/swarm';
import { swarmReportMock } from 'test/mocks/swarmState.mock';
import { renderWithProvider } from 'test/testUtils';

describe('App', () => {
it('renders Dashboard by default', () => {
const { getByText } = renderWithProvider(<App />);

expect(getByText('Start new load test')).toBeTruthy();
});

it('renders Auth component when authArgs is present', () => {
vi.mocked(authConstants).authArgs = { usernamePasswordCallback: '/auth' };

const { getByRole, getByLabelText } = renderWithProvider(<App />);

expect(getByRole('button', { name: 'Login' })).toBeTruthy();
expect(getByLabelText('Username')).toBeTruthy();
expect(getByLabelText('Password')).toBeTruthy();

(vi.mocked(authConstants) as any).authArgs = undefined;
});

it('renders the HTML Report when isReport is true', () => {
vi.mocked(swarmConstants).htmlReportProps = swarmReportMock;

const { getByText } = renderWithProvider(<App />);

expect(getByText('Locust Test Report')).toBeTruthy();
});
});
69 changes: 28 additions & 41 deletions locust/webui/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,32 @@
import { useMemo } from 'react';
import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider } from '@mui/material/styles';
import { connect } from 'react-redux';
import { combineReducers, configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';

import Layout from 'components/Layout/Layout';
import useLogViewer from 'components/LogViewer/useLogViewer';
import SwarmForm from 'components/SwarmForm/SwarmForm';
import Tabs from 'components/Tabs/Tabs';
import { SWARM_STATE } from 'constants/swarm';
import { THEME_MODE } from 'constants/theme';
import useSwarmUi from 'hooks/useSwarmUi';
import { IRootState } from 'redux/store';
import createTheme from 'styles/theme';
import { SwarmState } from 'types/ui.types';
import { authArgs } from 'constants/auth';
import { htmlReportProps } from 'constants/swarm';
import Auth from 'pages/Auth';
import Dashboard from 'pages/Dashboard';
import HtmlReport from 'pages/HtmlReport';
import theme from 'redux/slice/theme.slice';
import { store } from 'redux/store';

interface IApp {
isDarkMode: boolean;
isModalOpen?: boolean;
swarmState: SwarmState;
}

function App({ isDarkMode, swarmState }: IApp) {
useSwarmUi();
useLogViewer();

const theme = useMemo(
() => createTheme(isDarkMode ? THEME_MODE.DARK : THEME_MODE.LIGHT),
[isDarkMode],
);
export default function App() {
if (authArgs) {
const authStore = configureStore({
reducer: combineReducers({ theme }),
});

return (
<ThemeProvider theme={theme}>
<CssBaseline />
<Layout>{swarmState === SWARM_STATE.READY ? <SwarmForm /> : <Tabs />}</Layout>
</ThemeProvider>
);
return (
<Provider store={authStore}>
<Auth {...authArgs} />
</Provider>
);
} else if (htmlReportProps) {
return <HtmlReport {...htmlReportProps} />;
} else {
return (
<Provider store={store}>
<Dashboard />
</Provider>
);
}
}

const storeConnector = ({ swarm: { state }, theme: { isDarkMode } }: IRootState) => ({
isDarkMode,
swarmState: state,
});

export default connect(storeConnector)(App);
37 changes: 37 additions & 0 deletions locust/webui/src/components/FallbackRender/FallbackRender.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* eslint-disable no-console */
import { ErrorBoundary } from 'react-error-boundary';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';

import App from 'App';
import FallbackRender from 'components/FallbackRender/FallbackRender';
import * as swarmConstants from 'constants/swarm';
import { renderWithProvider } from 'test/testUtils';

describe('Fallback', () => {
const originalConsoleError = console.error;

beforeAll(() => {
/* Suppress console.error
Because our render function will not return an error,
Vitest will not suppress the error for us
*/
console.error = () => {};
});

afterAll(() => {
console.error = originalConsoleError;
});

it('renders the FallbackRender when something unexpected happens', () => {
// break the app
(vi.mocked(swarmConstants) as any).htmlReportProps = {};

const { getByText } = renderWithProvider(
<ErrorBoundary fallbackRender={FallbackRender}>
<App />
</ErrorBoundary>,
);

expect(getByText('Something went wrong')).toBeTruthy();
});
});
4 changes: 4 additions & 0 deletions locust/webui/src/constants/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { IAuthArgs } from 'types/auth.types';
import { camelCaseKeys } from 'utils/string';

export const authArgs: IAuthArgs | false = !!window.authArgs && camelCaseKeys(window.authArgs);
29 changes: 29 additions & 0 deletions locust/webui/src/constants/swarm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ISwarmState } from 'redux/slice/swarm.slice';
import { IReport, IReportTemplateArgs } from 'types/swarm.types';
import { ICharts } from 'types/ui.types';
import { updateArraysAtProps } from 'utils/object';
import { camelCaseKeys } from 'utils/string';

export const SWARM_STATE = {
READY: 'ready',
RUNNING: 'running',
STOPPED: 'stopped',
SPAWNING: 'spawning',
CLEANUP: 'cleanup',
STOPPING: 'stopping',
MISSING: 'missing',
};

export const swarmTemplateArgs = window.templateArgs
? camelCaseKeys(window.templateArgs)
: ({} as ISwarmState | IReportTemplateArgs);

export const htmlReportProps: IReport | false = !!(swarmTemplateArgs as IReportTemplateArgs)
.isReport && {
...(swarmTemplateArgs as IReportTemplateArgs),
charts: swarmTemplateArgs.history?.reduce(
(charts, { currentResponseTimePercentiles, ...history }) =>
updateArraysAtProps(charts, { ...currentResponseTimePercentiles, ...history }),
{} as ICharts,
) as ICharts,
};
17 changes: 0 additions & 17 deletions locust/webui/src/constants/swarm.tsx

This file was deleted.

46 changes: 5 additions & 41 deletions locust/webui/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,13 @@
import { combineReducers, configureStore } from '@reduxjs/toolkit';
import Auth from 'auth';
import ReactDOM from 'react-dom/client';
import { ErrorBoundary } from 'react-error-boundary';
import { Provider } from 'react-redux';

import App from 'App';
import FallbackRender from 'components/FallbackRender/FallbackRender';
import { swarmTemplateArgs } from 'constants/swarm';
import theme from 'redux/slice/theme.slice';
import { store } from 'redux/store';
import Report from 'Report';
import { IReportTemplateArgs } from 'types/swarm.types';
import { ICharts } from 'types/ui.types';
import { updateArraysAtProps } from 'utils/object';

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);

function componentToRender() {
if (window.authArgs) {
const authStore = configureStore({
reducer: combineReducers({ theme }),
});

return (
<Provider store={authStore}>
<Auth />
</Provider>
);
} else if ((swarmTemplateArgs as IReportTemplateArgs).isReport) {
const reportProps = {
...(swarmTemplateArgs as IReportTemplateArgs),
charts: swarmTemplateArgs.history?.reduce(
(charts, { currentResponseTimePercentiles, ...history }) =>
updateArraysAtProps(charts, { ...currentResponseTimePercentiles, ...history }),
{} as ICharts,
) as ICharts,
};
return <Report {...reportProps} />;
} else {
return (
<Provider store={store}>
<App />
</Provider>
);
}
}

root.render(<ErrorBoundary fallbackRender={FallbackRender}>{componentToRender()}</ErrorBoundary>);
root.render(
<ErrorBoundary fallbackRender={FallbackRender}>
<App />
</ErrorBoundary>,
);
11 changes: 3 additions & 8 deletions locust/webui/src/auth.tsx → locust/webui/src/pages/Auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,8 @@ import DarkLightToggle from 'components/Layout/Navbar/DarkLightToggle';
import { THEME_MODE } from 'constants/theme';
import createTheme from 'styles/theme';
import { IAuthArgs } from 'types/auth.types';
import { camelCaseKeys } from 'utils/string';

const { authProviders, error, usernamePasswordCallback } = window.authArgs
? camelCaseKeys(window.authArgs)
: ({} as IAuthArgs);

export default function Auth() {
export default function Auth({ authProviders, error, usernamePasswordCallback }: IAuthArgs) {
const isDarkMode = useSelector(({ theme }) => theme.isDarkMode);

const theme = useMemo(
Expand Down Expand Up @@ -60,7 +55,6 @@ export default function Auth() {
Locust
</Typography>
</Box>

{usernamePasswordCallback && (
<form action={usernamePasswordCallback}>
<Box sx={{ display: 'flex', flexDirection: 'column', rowGap: 2 }}>
Expand All @@ -75,9 +69,10 @@ export default function Auth() {
)}
{authProviders && (
<Box sx={{ display: 'flex', flexDirection: 'column', rowGap: 1 }}>
{authProviders.map(({ label, callbackUrl, iconUrl }) => (
{authProviders.map(({ label, callbackUrl, iconUrl }, index) => (
<IconButton
href={callbackUrl}
key={`auth-provider-${index}`}
sx={{
display: 'flex',
justifyContent: 'center',
Expand Down
45 changes: 45 additions & 0 deletions locust/webui/src/pages/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useMemo } from 'react';
import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider } from '@mui/material/styles';
import { connect } from 'react-redux';

import Layout from 'components/Layout/Layout';
import useLogViewer from 'components/LogViewer/useLogViewer';
import SwarmForm from 'components/SwarmForm/SwarmForm';
import Tabs from 'components/Tabs/Tabs';
import { SWARM_STATE } from 'constants/swarm';
import { THEME_MODE } from 'constants/theme';
import useSwarmUi from 'hooks/useSwarmUi';
import { IRootState } from 'redux/store';
import createTheme from 'styles/theme';
import { SwarmState } from 'types/ui.types';

interface IDashboard {
isDarkMode: boolean;
isModalOpen?: boolean;
swarmState: SwarmState;
}

function Dashboard({ isDarkMode, swarmState }: IDashboard) {
useSwarmUi();
useLogViewer();

const theme = useMemo(
() => createTheme(isDarkMode ? THEME_MODE.DARK : THEME_MODE.LIGHT),
[isDarkMode],
);

return (
<ThemeProvider theme={theme}>
<CssBaseline />
<Layout>{swarmState === SWARM_STATE.READY ? <SwarmForm /> : <Tabs />}</Layout>
</ThemeProvider>
);
}

const storeConnector = ({ swarm: { state }, theme: { isDarkMode } }: IRootState) => ({
isDarkMode,
swarmState: state,
});

export default connect(storeConnector)(Dashboard);
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { IReport } from 'types/swarm.types';

const theme = createTheme(window.theme || INITIAL_THEME);

export default function Report({
export default function HtmlReport({
locustfile,
showDownloadLink,
startTime,
Expand Down Expand Up @@ -73,12 +73,14 @@ export default function Report({
</Typography>
<StatsTable stats={requestsStatistics} />
</Box>
<Box>
<Typography component='h2' mb={1} noWrap variant='h4'>
Response Time Statistics
</Typography>
<ResponseTimeTable responseTimes={responseTimeStatistics} />
</Box>
{!!responseTimeStatistics.length && (
<Box>
<Typography component='h2' mb={1} noWrap variant='h4'>
Response Time Statistics
</Typography>
<ResponseTimeTable responseTimes={responseTimeStatistics} />
</Box>
)}
<Box>
<Typography component='h2' mb={1} noWrap variant='h4'>
Failures Statistics
Expand Down

0 comments on commit 355d2e8

Please sign in to comment.