Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 30 additions & 29 deletions awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironment.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { useEffect, useCallback } from 'react';
import { Link } from 'react-router-dom';
import {
Link,
Redirect,
Routes,
Route,
Switch,
Navigate,
useLocation,
useParams,
} from 'react-router-dom';
} from 'react-router-dom-v5-compat';

import { useLingui } from '@lingui/react/macro';
import { Card, PageSection } from '@patternfly/react-core';
Expand Down Expand Up @@ -104,32 +104,33 @@ function ExecutionEnvironment({ setBreadcrumb }) {
{cardHeader}
{isLoading && <ContentLoading />}
{!isLoading && executionEnvironment && (
<Switch>
<Redirect
from="/execution_environments/:id"
to="/execution_environments/:id/details"
exact
<Routes>
<Route index element={<Navigate to="details" replace />} />
<Route
path="edit"
element={
<ExecutionEnvironmentEdit
executionEnvironment={executionEnvironment}
/>
}
/>
{executionEnvironment && (
<>
<Route path="/execution_environments/:id/edit">
<ExecutionEnvironmentEdit
executionEnvironment={executionEnvironment}
/>
</Route>
<Route path="/execution_environments/:id/details">
<ExecutionEnvironmentDetails
executionEnvironment={executionEnvironment}
/>
</Route>
<Route path="/execution_environments/:id/templates">
<ExecutionEnvironmentTemplateList
executionEnvironment={executionEnvironment}
/>
</Route>
</>
)}
</Switch>
<Route
path="details"
element={
<ExecutionEnvironmentDetails
executionEnvironment={executionEnvironment}
/>
}
/>
<Route
path="templates"
element={
<ExecutionEnvironmentTemplateList
executionEnvironment={executionEnvironment}
/>
}
/>
</Routes>
)}
</Card>
</PageSection>
Expand Down
119 changes: 119 additions & 0 deletions awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironment.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import React from 'react';
import { screen, waitFor } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import { Routes, Route } from 'react-router-dom-v5-compat';
import { ExecutionEnvironmentsAPI } from 'api';
import { renderWithContexts } from '../../../testUtils/rtlContexts';
import ExecutionEnvironment from './ExecutionEnvironment';

jest.mock('../../api/models/ExecutionEnvironments');

// Markers for the routed tab panels, so assertions are about which branch of
// the nested v6 <Routes> tree resolves.
jest.mock('./ExecutionEnvironmentDetails', () => {
const ReactLib = require('react');
return {
__esModule: true,
default: () =>
ReactLib.createElement('div', null, 'ExecutionEnvironmentDetails'),
};
});
jest.mock('./ExecutionEnvironmentEdit', () => {
const ReactLib = require('react');
return {
__esModule: true,
default: () =>
ReactLib.createElement('div', null, 'ExecutionEnvironmentEdit'),
};
});
jest.mock('./ExecutionEnvironmentTemplate', () => {
const ReactLib = require('react');
return {
__esModule: true,
default: () =>
ReactLib.createElement('div', null, 'ExecutionEnvironmentTemplateList'),
};
});

const executionEnvironment = {
id: 42,
name: 'Foo',
image: 'quay.io/foo/bar',
summary_fields: { user_capabilities: { edit: true, delete: true } },
};

// ExecutionEnvironment uses paths relative to its parent route, so mount it
// under the same /execution_environments/:id/* route that
// ExecutionEnvironments.js gives it in the app.
function renderAt(path) {
const history = createMemoryHistory({ initialEntries: [path] });
return renderWithContexts(
<Routes>
<Route
path="/execution_environments/:id/*"
element={<ExecutionEnvironment setBreadcrumb={() => {}} />}
/>
</Routes>,
{ context: { router: { history } } }
);
}

describe('<ExecutionEnvironment />', () => {
beforeEach(() => {
ExecutionEnvironmentsAPI.readDetail.mockResolvedValue({
data: executionEnvironment,
});
});

afterEach(() => {
jest.clearAllMocks();
});

test('fetches the execution environment detail', async () => {
renderAt('/execution_environments/42/details');
expect(
await screen.findByText('ExecutionEnvironmentDetails')
).toBeInTheDocument();
// real route params are strings (the old enzyme test mocked a number)
expect(ExecutionEnvironmentsAPI.readDetail).toHaveBeenCalledWith('42');
});

test('renders the edit panel at /edit', async () => {
renderAt('/execution_environments/42/edit');
expect(
await screen.findByText('ExecutionEnvironmentEdit')
).toBeInTheDocument();
});

test('renders the templates panel at /templates', async () => {
renderAt('/execution_environments/42/templates');
expect(
await screen.findByText('ExecutionEnvironmentTemplateList')
).toBeInTheDocument();
});

test('redirects the index path to details', async () => {
const { history } = renderAt('/execution_environments/42');
expect(
await screen.findByText('ExecutionEnvironmentDetails')
).toBeInTheDocument();
await waitFor(() =>
expect(history.location.pathname).toBe(
'/execution_environments/42/details'
)
);
});

test('shows a not-found error when the detail request 404s', async () => {
const err = new Error('not found');
err.response = { status: 404 };
ExecutionEnvironmentsAPI.readDetail.mockRejectedValue(err);
renderAt('/execution_environments/42/details');
expect(
await screen.findByText('Execution environment not found.')
).toBeInTheDocument();
expect(
screen.queryByText('ExecutionEnvironmentDetails')
).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect, useCallback } from 'react';
import { useLocation, useRouteMatch } from 'react-router-dom';
import { useLocation } from 'react-router-dom';
import { useLingui } from '@lingui/react/macro';
import { Card, PageSection } from '@patternfly/react-core';

Expand Down Expand Up @@ -30,7 +30,6 @@ const QS_CONFIG = getQSConfig('execution_environments', {
function ExecutionEnvironmentList() {
const { t } = useLingui();
const location = useLocation();
const match = useRouteMatch();
const { addToast, Toast, toastProps } = useToast();

const {
Expand Down Expand Up @@ -182,7 +181,7 @@ function ExecutionEnvironmentList() {
<ToolbarAddButton
ouiaId="add-execution-environment"
key="add"
linkTo={`${match.url}/add`}
linkTo="/execution_environments/add"
/>,
]
: []),
Expand All @@ -206,7 +205,7 @@ function ExecutionEnvironmentList() {
key={executionEnvironment.id}
rowIndex={index}
executionEnvironment={executionEnvironment}
detailUrl={`${match.url}/${executionEnvironment.id}/details`}
detailUrl={`/execution_environments/${executionEnvironment.id}/details`}
onSelect={() => handleSelect(executionEnvironment)}
onCopy={handleCopy}
isSelected={selected.some(
Expand All @@ -217,7 +216,7 @@ function ExecutionEnvironmentList() {
)}
emptyStateControls={
canAdd && (
<ToolbarAddButton key="add" linkTo={`${match.url}/add`} />
<ToolbarAddButton key="add" linkTo="/execution_environments/add" />
)
}
/>
Expand Down
36 changes: 22 additions & 14 deletions awx/ui/src/screens/ExecutionEnvironment/ExecutionEnvironments.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useState, useCallback } from 'react';
import { useLingui } from '@lingui/react/macro';
import { Route, Switch } from 'react-router-dom';
import { Routes, Route } from 'react-router-dom-v5-compat';
import PersistentFilters from 'components/PersistentFilters';
import ScreenHeader from 'components/ScreenHeader/ScreenHeader';
import ExecutionEnvironment from './ExecutionEnvironment';
Expand Down Expand Up @@ -35,19 +35,27 @@ function ExecutionEnvironments() {
streamType="execution_environment"
breadcrumbConfig={breadcrumbConfig}
/>
<Switch>
<Route path="/execution_environments/add">
<ExecutionEnvironmentAdd />
</Route>
<Route path="/execution_environments/:id">
<ExecutionEnvironment setBreadcrumb={buildBreadcrumbConfig} />
</Route>
<Route path="/execution_environments">
<PersistentFilters pageKey="executionEnvironments">
<ExecutionEnvironmentList />
</PersistentFilters>
</Route>
</Switch>
<Routes>
<Route
path="/execution_environments/add"
element={<ExecutionEnvironmentAdd />}
/>
{/* so the nested <ExecutionEnvironment> route tree can match the rest */}
<Route
path="/execution_environments/:id/*"
element={
<ExecutionEnvironment setBreadcrumb={buildBreadcrumbConfig} />
}
/>
<Route
path="/execution_environments"
element={
<PersistentFilters pageKey="executionEnvironments">
<ExecutionEnvironmentList />
</PersistentFilters>
}
/>
</Routes>
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,71 @@
import React from 'react';

import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import { screen } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import { renderWithContexts } from '../../../testUtils/rtlContexts';

import ExecutionEnvironments from './ExecutionEnvironments';

describe('<ExecutionEnvironments/>', () => {
let pageWrapper;
let pageSections;
jest.mock('../../api/models/ExecutionEnvironments');

// Replace the routed children with markers so the assertions are purely about
// which branch of the v6 <Routes> tree resolves for a given URL.
jest.mock('./ExecutionEnvironmentList', () => {
const ReactLib = require('react');
return {
__esModule: true,
default: () =>
ReactLib.createElement('div', null, 'ExecutionEnvironmentList'),
};
});
jest.mock('./ExecutionEnvironmentAdd', () => {
const ReactLib = require('react');
return {
__esModule: true,
default: () =>
ReactLib.createElement('div', null, 'ExecutionEnvironmentAdd'),
};
});
jest.mock('./ExecutionEnvironment', () => {
const ReactLib = require('react');
return {
__esModule: true,
default: () =>
ReactLib.createElement('div', null, 'ExecutionEnvironment detail'),
};
});

function renderAt(path) {
const history = createMemoryHistory({ initialEntries: [path] });
return renderWithContexts(<ExecutionEnvironments />, {
context: { router: { history } },
});
}

describe('<ExecutionEnvironments />', () => {
test('renders the list at /execution_environments', async () => {
renderAt('/execution_environments');
expect(
await screen.findByText('ExecutionEnvironmentList')
).toBeInTheDocument();
});

beforeEach(() => {
pageWrapper = mountWithContexts(<ExecutionEnvironments />);
pageSections = pageWrapper.find('PageSection');
test('renders the add form at /execution_environments/add', async () => {
renderAt('/execution_environments/add');
expect(
await screen.findByText('ExecutionEnvironmentAdd')
).toBeInTheDocument();
expect(
screen.queryByText('ExecutionEnvironmentList')
).not.toBeInTheDocument();
});

test('initially renders without crashing', () => {
expect(pageWrapper.length).toBe(1);
expect(pageSections.length).toBe(1);
expect(pageSections.first().props().variant).toBe('light');
test('renders the detail subtree at /execution_environments/:id', async () => {
renderAt('/execution_environments/42/details');
expect(
await screen.findByText('ExecutionEnvironment detail')
).toBeInTheDocument();
expect(
screen.queryByText('ExecutionEnvironmentList')
).not.toBeInTheDocument();
});
});