Add tests for Owner & move hooks to their own folder

Signed-off-by: Erik Engervall <erik.engervall@gmail.com>
This commit is contained in:
Erik Engervall
2021-04-18 12:36:36 +02:00
parent fe3f6f7028
commit 3e308bc4ca
11 changed files with 205 additions and 88 deletions
@@ -37,12 +37,16 @@ import { Info } from './cards/info/Info';
import { InfoCardPlus } from './components/InfoCardPlus';
import { isProjectValid } from './helpers/isProjectValid';
import { Patch } from './cards/patchRc/Patch';
import { ProjectContext, Project } from './contexts/ProjectContext';
import {
ProjectContext,
Project,
useProjectContext,
} from './contexts/ProjectContext';
import { PromoteRc } from './cards/promoteRc/PromoteRc';
import { RefetchContext } from './contexts/RefetchContext';
import { RepoDetailsForm } from './cards/projectForm/RepoDetailsForm';
import { useVersioningStrategyMatchesRepoTags } from './helpers/useVersioningStrategyMatchesRepoTags';
import { useQueryHandler } from './helpers/useQueryHandler';
import { useVersioningStrategyMatchesRepoTags } from './hooks/useVersioningStrategyMatchesRepoTags';
import { useQueryHandler } from './hooks/useQueryHandler';
interface GitHubReleaseManagerProps {
components?: {
@@ -92,32 +96,28 @@ export function GitHubReleaseManager({
return (
<PluginApiClientContext.Provider value={pluginApiClient}>
<div className={classes.root}>
<ContentHeader title="GitHub Release Manager" />
<ProjectContext.Provider value={project}>
<div className={classes.root}>
<ContentHeader title="GitHub Release Manager" />
<InfoCardPlus>
<RepoDetailsForm
username={usernameResponse.value.username}
project={project}
/>
</InfoCardPlus>
<InfoCardPlus>
<RepoDetailsForm username={usernameResponse.value.username} />
</InfoCardPlus>
{isProjectValid(project) && (
<Cards components={components} project={project} />
)}
</div>
{isProjectValid(project) && <Cards components={components} />}
</div>
</ProjectContext.Provider>
</PluginApiClientContext.Provider>
);
}
function Cards({
components,
project,
}: {
components: GitHubReleaseManagerProps['components'];
project: Project;
}) {
const pluginApiClient = usePluginApiClientContext();
const project = useProjectContext();
const [refetchTrigger, setRefetchTrigger] = useState(0);
const gitHubBatchInfo = useAsync(
getGitHubBatchInfo({ project, pluginApiClient }),
@@ -159,52 +159,50 @@ function Cards({
}
return (
<ProjectContext.Provider value={project}>
<RefetchContext.Provider value={{ refetchTrigger, setRefetchTrigger }}>
<ErrorBoundary>
{gitHubBatchInfo.value.latestRelease && !versioningStrategyMatches && (
<Alert severity="warning" style={{ marginBottom: 20 }}>
Versioning mismatch, expected {project.versioningStrategy}{' '}
version, got "{gitHubBatchInfo.value.latestRelease.tagName}"
</Alert>
)}
<RefetchContext.Provider value={{ refetchTrigger, setRefetchTrigger }}>
<ErrorBoundary>
{gitHubBatchInfo.value.latestRelease && !versioningStrategyMatches && (
<Alert severity="warning" style={{ marginBottom: 20 }}>
Versioning mismatch, expected {project.versioningStrategy} version,
got "{gitHubBatchInfo.value.latestRelease.tagName}"
</Alert>
)}
{!gitHubBatchInfo.value.latestRelease && (
<Alert severity="info" style={{ marginBottom: 20 }}>
This repository has not releases yet
</Alert>
)}
{!gitHubBatchInfo.value.latestRelease && (
<Alert severity="info" style={{ marginBottom: 20 }}>
This repository has not releases yet
</Alert>
)}
<Info
<Info
latestRelease={gitHubBatchInfo.value.latestRelease}
releaseBranch={gitHubBatchInfo.value.releaseBranch}
/>
{components?.default?.createRc?.omit !== true && (
<CreateRc
latestRelease={gitHubBatchInfo.value.latestRelease}
releaseBranch={gitHubBatchInfo.value.releaseBranch}
defaultBranch={gitHubBatchInfo.value.repository.defaultBranch}
successCb={components?.default?.createRc?.successCb}
/>
)}
{components?.default?.createRc?.omit !== true && (
<CreateRc
latestRelease={gitHubBatchInfo.value.latestRelease}
releaseBranch={gitHubBatchInfo.value.releaseBranch}
defaultBranch={gitHubBatchInfo.value.repository.defaultBranch}
successCb={components?.default?.createRc?.successCb}
/>
)}
{components?.default?.promoteRc?.omit !== true && (
<PromoteRc
latestRelease={gitHubBatchInfo.value.latestRelease}
successCb={components?.default?.promoteRc?.successCb}
/>
)}
{components?.default?.promoteRc?.omit !== true && (
<PromoteRc
latestRelease={gitHubBatchInfo.value.latestRelease}
successCb={components?.default?.promoteRc?.successCb}
/>
)}
{components?.default?.patch?.omit !== true && (
<Patch
latestRelease={gitHubBatchInfo.value.latestRelease}
releaseBranch={gitHubBatchInfo.value.releaseBranch}
successCb={components?.default?.patch?.successCb}
/>
)}
</ErrorBoundary>
</RefetchContext.Provider>
</ProjectContext.Provider>
{components?.default?.patch?.omit !== true && (
<Patch
latestRelease={gitHubBatchInfo.value.latestRelease}
releaseBranch={gitHubBatchInfo.value.releaseBranch}
successCb={components?.default?.patch?.successCb}
/>
)}
</ErrorBoundary>
</RefetchContext.Provider>
);
}
@@ -0,0 +1,113 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { render, waitFor, screen } from '@testing-library/react';
import {
mockApiClient,
mockCalverProject,
} from '../../test-helpers/test-helpers';
import { TEST_IDS } from '../../test-helpers/test-ids';
jest.mock('react-router', () => ({
useNavigate: jest.fn(),
useLocation: jest.fn(() => ({
search: `?versioningStrategy=${mockCalverProject.versioningStrategy}&owner=${mockCalverProject.owner}&repo=${mockCalverProject.repo}`,
})),
}));
jest.mock('../../contexts/PluginApiClientContext', () => ({
usePluginApiClientContext: jest.fn(() => mockApiClient),
}));
jest.mock('../../contexts/ProjectContext', () => ({
useProjectContext: jest.fn(() => mockCalverProject),
}));
import { useProjectContext } from '../../contexts/ProjectContext';
import { Owner } from './Owner';
describe('Owner', () => {
beforeEach(jest.clearAllMocks);
it('should render select', async () => {
const { getByTestId } = render(
<Owner username={mockCalverProject.owner} />,
);
expect(getByTestId(TEST_IDS.form.owner.loading)).toBeInTheDocument();
await waitFor(() => screen.getByTestId(TEST_IDS.form.owner.select));
expect(getByTestId(TEST_IDS.form.owner.select)).toBeInTheDocument();
});
it('should render select for empty owners', async () => {
(useProjectContext as jest.Mock).mockImplementation(() => ({
...mockCalverProject,
owner: '',
}));
const { getAllByTestId, getByTestId } = render(
<Owner username={mockCalverProject.owner} />,
);
expect(getByTestId(TEST_IDS.form.owner.loading)).toBeInTheDocument();
await waitFor(() => screen.getAllByTestId(TEST_IDS.form.owner.empty));
expect(getAllByTestId(TEST_IDS.form.owner.empty)).toMatchInlineSnapshot(`
Array [
<p
class="MuiFormHelperText-root Mui-required"
data-testid="grm--form--owner--empty"
>
Select an owner (org or user)
</p>,
<p
class="MuiFormHelperText-root Mui-required"
data-testid="grm--form--owner--empty"
>
Custom queries can be made via the query param
<strong>
owner
</strong>
</p>,
]
`);
});
it('should handle errors', async () => {
(mockApiClient.getOwners as jest.Mock).mockImplementationOnce(async () => {
throw new Error('Kaboom');
});
const { getByTestId } = render(
<Owner username={mockCalverProject.owner} />,
);
expect(getByTestId(TEST_IDS.form.owner.loading)).toBeInTheDocument();
await waitFor(() => screen.getByTestId(TEST_IDS.form.owner.error));
expect(getByTestId(TEST_IDS.form.owner.error)).toMatchInlineSnapshot(`
<p
class="MuiFormHelperText-root Mui-error Mui-required"
data-testid="grm--form--owner--error"
>
Encountered an error (
Kaboom
)
</p>
`);
});
});
@@ -26,18 +26,14 @@ import {
} from '@material-ui/core';
import { CenteredCircularProgress } from '../../components/CenteredCircularProgress';
import { Project } from '../../contexts/ProjectContext';
import { useFormClasses } from './styles';
import { usePluginApiClientContext } from '../../contexts/PluginApiClientContext';
import { useQueryHandler } from '../../helpers/useQueryHandler';
import { useProjectContext } from '../../contexts/ProjectContext';
import { useQueryHandler } from '../../hooks/useQueryHandler';
import { TEST_IDS } from '../../test-helpers/test-ids';
export function Owner({
username,
project,
}: {
username: string;
project: Project;
}) {
export function Owner({ username }: { username: string }) {
const project = useProjectContext();
const formClasses = useFormClasses();
const navigate = useNavigate();
const pluginApiClient = usePluginApiClientContext();
@@ -52,11 +48,12 @@ export function Owner({
return (
<FormControl className={formClasses.formControl} required error={!!error}>
{loading ? (
<CenteredCircularProgress />
<CenteredCircularProgress data-testid={TEST_IDS.form.owner.loading} />
) : (
<>
<InputLabel id="owner-select-label">Owners</InputLabel>
<Select
data-testid={TEST_IDS.form.owner.select}
labelId="owner-select-label"
id="owner-select"
value={project.owner}
@@ -95,15 +92,17 @@ export function Owner({
</Select>
{error && (
<FormHelperText>
<FormHelperText data-testid={TEST_IDS.form.owner.error}>
Encountered an error ({error.message})
</FormHelperText>
)}
{!error && project.owner.length === 0 && (
<>
<FormHelperText>Select an owner (org or user)</FormHelperText>
<FormHelperText>
<FormHelperText data-testid={TEST_IDS.form.owner.empty}>
Select an owner (org or user)
</FormHelperText>
<FormHelperText data-testid={TEST_IDS.form.owner.empty}>
Custom queries can be made via the query param{' '}
<strong>owner</strong>
</FormHelperText>
@@ -28,11 +28,12 @@ import {
import { usePluginApiClientContext } from '../../contexts/PluginApiClientContext';
import { useFormClasses } from './styles';
import { CenteredCircularProgress } from '../../components/CenteredCircularProgress';
import { Project } from '../../contexts/ProjectContext';
import { useQueryHandler } from '../../helpers/useQueryHandler';
import { useProjectContext } from '../../contexts/ProjectContext';
import { useQueryHandler } from '../../hooks/useQueryHandler';
export function Repo({ project }: { project: Project }) {
export function Repo() {
const pluginApiClient = usePluginApiClientContext();
const project = useProjectContext();
const navigate = useNavigate();
const formClasses = useFormClasses();
const { getQueryParamsWithUpdates } = useQueryHandler();
@@ -42,6 +43,10 @@ export function Repo({ project }: { project: Project }) {
[project.owner],
);
if (project.owner.length === 0) {
return null;
}
const repositories = value?.repositories ?? [];
const customRepoFromUrl = !repositories.concat(['']).includes(project.repo);
@@ -17,24 +17,17 @@
import React from 'react';
import { Owner } from './Owner';
import { Project } from '../../contexts/ProjectContext';
import { Repo } from './Repo';
import { VersioningStrategy } from './VersioningStrategy';
export function RepoDetailsForm({
username,
project,
}: {
username: string;
project: Project;
}) {
export function RepoDetailsForm({ username }: { username: string }) {
return (
<>
<VersioningStrategy project={project} />
<VersioningStrategy />
<Owner project={project} username={username} />
<Owner username={username} />
{project.owner.length > 0 && <Repo project={project} />}
<Repo />
</>
);
}
@@ -24,11 +24,12 @@ import {
RadioGroup,
} from '@material-ui/core';
import { Project } from '../../contexts/ProjectContext';
import { useQueryHandler } from '../../helpers/useQueryHandler';
import { Project, useProjectContext } from '../../contexts/ProjectContext';
import { useQueryHandler } from '../../hooks/useQueryHandler';
export function VersioningStrategy({ project }: { project: Project }) {
export function VersioningStrategy() {
const navigate = useNavigate();
const project = useProjectContext();
const { getParsedQuery, getQueryParamsWithUpdates } = useQueryHandler();
useEffect(() => {
@@ -17,7 +17,7 @@
import { useEffect, useState } from 'react';
import { Project } from '../contexts/ProjectContext';
import { getTagParts } from './tagParts/getTagParts';
import { getTagParts } from '../helpers/tagParts/getTagParts';
export const useVersioningStrategyMatchesRepoTags = ({
project,
@@ -35,6 +35,14 @@ export const TEST_IDS = {
notPrerelease: 'grm--patch-body--not-prerelease--info',
body: 'grm--patch-body',
},
form: {
owner: {
loading: 'grm--form--owner--loading',
select: 'grm--form--owner--select',
error: 'grm--form--owner--error',
empty: 'grm--form--owner--empty',
},
},
components: {
divider: 'grm--divider',
reloadButton: 'grm--reload-button',