test(scaffolder): add tests for TemplatePage
This commit is contained in:
@@ -70,9 +70,9 @@ export class CatalogClient implements CatalogApi {
|
||||
return await this.getOptional(`/locations/${id}`);
|
||||
}
|
||||
|
||||
async getEntities<EntityType extends Entity = Entity>(
|
||||
async getEntities(
|
||||
filter?: Record<string, string | string[]>,
|
||||
): Promise<EntityType[]> {
|
||||
): Promise<Entity[]> {
|
||||
let path = `/entities`;
|
||||
if (filter) {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
@@ -34,18 +34,13 @@ export interface CatalogApi {
|
||||
getEntityByName(
|
||||
compoundName: EntityCompoundName,
|
||||
): Promise<Entity | undefined>;
|
||||
getEntities<EntityType extends Entity = Entity>(
|
||||
filter?: Record<string, string | string[]>,
|
||||
): Promise<EntityType[]>;
|
||||
addLocation<EntityType extends Entity = Entity>(
|
||||
type: string,
|
||||
target: string,
|
||||
): Promise<AddLocationResponse<EntityType>>;
|
||||
getEntities(filter?: Record<string, string | string[]>): Promise<Entity[]>;
|
||||
addLocation(type: string, target: string): Promise<AddLocationResponse>;
|
||||
getLocationByEntity(entity: Entity): Promise<Location | undefined>;
|
||||
removeEntityByUid(uid: string): Promise<void>;
|
||||
}
|
||||
|
||||
export type AddLocationResponse<EntityType extends Entity = Entity> = {
|
||||
export type AddLocationResponse = {
|
||||
location: Location;
|
||||
entities: EntityType[];
|
||||
entities: Entity[];
|
||||
};
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.12",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.12",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.12",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
"@testing-library/user-event": "^12.0.7",
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
import { JobStage } from '../JobStage/JobStage';
|
||||
import { useJobPolling } from './useJobPolling';
|
||||
import { Job } from '../../types';
|
||||
import { ComponentEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
import { Button } from '@backstage/core';
|
||||
import { entityRoute } from '@backstage/plugin-catalog';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
@@ -33,7 +33,7 @@ type Props = {
|
||||
onClose: () => void;
|
||||
onComplete: (job: Job) => void;
|
||||
jobId: string;
|
||||
entity: ComponentEntityV1alpha1 | null;
|
||||
entity: TemplateEntityV1alpha1 | null;
|
||||
};
|
||||
|
||||
export const JobStatusModal = ({
|
||||
|
||||
@@ -1 +1,16 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
export { JobStatusModal } from './JobStatusModal';
|
||||
|
||||
@@ -1 +1,16 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
export { MultistepJsonForm } from './MultistepJsonForm';
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright 2020 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 { TemplatePage } from './TemplatePage';
|
||||
import { wrapInTestApp, renderWithEffects } from '@backstage/test-utils';
|
||||
import { ApiRegistry, errorApiRef, ApiProvider } from '@backstage/core';
|
||||
import { scaffolderApiRef, ScaffolderApi } from '../../api';
|
||||
import { catalogApiRef, CatalogApi } from '@backstage/plugin-catalog';
|
||||
import { mutate } from 'swr';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Route, MemoryRouter } from 'react-router';
|
||||
import { rootRoute } from '../../routes';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import { lightTheme } from '@backstage/theme';
|
||||
|
||||
const templateMock = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Template',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'file:/something/sample-templates/react-ssr-template/template.yaml',
|
||||
},
|
||||
name: 'react-ssr-template',
|
||||
title: 'React SSR Template',
|
||||
description:
|
||||
'Next.js application skeleton for creating isomorphic web applications.',
|
||||
tags: ['Recommended', 'React'],
|
||||
uid: '55efc748-4a2b-460f-9e47-3f4fd23b46f7',
|
||||
etag: 'MTM3YThjY2QtYTc1MS00MTFkLTk3YTAtNzgyMDg3MDVmZTVm',
|
||||
generation: 1,
|
||||
},
|
||||
spec: {
|
||||
processor: 'cookiecutter',
|
||||
type: 'website',
|
||||
path: '.',
|
||||
schema: {
|
||||
required: ['component_id', 'description'],
|
||||
properties: {
|
||||
component_id: {
|
||||
title: 'Name',
|
||||
type: 'string',
|
||||
description: 'Unique name of the component',
|
||||
},
|
||||
description: {
|
||||
title: 'Description',
|
||||
type: 'string',
|
||||
description: 'Description of the component',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
return {
|
||||
...(jest.requireActual('react-router-dom') as any),
|
||||
useParams: () => ({
|
||||
templateName: 'test',
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const scaffolderApiMock: Partial<ScaffolderApi> = {
|
||||
scaffold: jest.fn(),
|
||||
};
|
||||
|
||||
const catalogApiMock = {
|
||||
getEntities: jest.fn() as jest.MockedFunction<CatalogApi['getEntities']>,
|
||||
};
|
||||
const errorApiMock = { post: jest.fn(), error$: jest.fn() };
|
||||
|
||||
const apis = ApiRegistry.from([
|
||||
[scaffolderApiRef, scaffolderApiMock],
|
||||
[errorApiRef, errorApiMock],
|
||||
[catalogApiRef, catalogApiMock],
|
||||
]);
|
||||
|
||||
describe('TemplatePage', () => {
|
||||
afterEach(async () => {
|
||||
// Cleaning up swr's cache
|
||||
await act(async () => {
|
||||
await mutate('templates/test');
|
||||
});
|
||||
});
|
||||
it('renders correctly', async () => {
|
||||
catalogApiMock.getEntities.mockResolvedValueOnce([templateMock]);
|
||||
const rendered = await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<TemplatePage />
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
expect(rendered.queryByText('Create a new component')).toBeInTheDocument();
|
||||
expect(rendered.queryByText('React SSR Template')).toBeInTheDocument();
|
||||
// await act(async () => await mutate('templates/test'));
|
||||
});
|
||||
it('renders spinner while loading', async () => {
|
||||
let resolve: Function;
|
||||
const promise = new Promise<any>(res => {
|
||||
resolve = res;
|
||||
});
|
||||
catalogApiMock.getEntities.mockResolvedValueOnce(promise);
|
||||
const rendered = await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<TemplatePage />
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
expect(rendered.queryByText('Create a new component')).toBeInTheDocument();
|
||||
expect(rendered.queryByTestId('loading-progress')).toBeInTheDocument();
|
||||
// Need to cleanup the promise or will timeout
|
||||
resolve!();
|
||||
});
|
||||
|
||||
it('navigates away if no template was loaded', async () => {
|
||||
catalogApiMock.getEntities.mockResolvedValueOnce([]);
|
||||
|
||||
const rendered = await renderWithEffects(
|
||||
<ApiProvider apis={apis}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<MemoryRouter initialEntries={['/create/test']}>
|
||||
<Route path="/create/test">
|
||||
<TemplatePage />
|
||||
</Route>
|
||||
<Route path={rootRoute.path} element={<>This is root</>} />
|
||||
</MemoryRouter>
|
||||
</ThemeProvider>
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
expect(
|
||||
rendered.queryByText('Create a new component'),
|
||||
).not.toBeInTheDocument();
|
||||
expect(rendered.queryByText('This is root')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -13,10 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
ComponentEntityV1alpha1,
|
||||
TemplateEntityV1alpha1,
|
||||
} from '@backstage/catalog-model';
|
||||
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
|
||||
import {
|
||||
Content,
|
||||
errorApiRef,
|
||||
@@ -46,10 +43,10 @@ const useTemplate = (
|
||||
const { data, error } = useStaleWhileRevalidate(
|
||||
`templates/${templateName}`,
|
||||
async () =>
|
||||
catalogApi.getEntities<TemplateEntityV1alpha1>({
|
||||
catalogApi.getEntities({
|
||||
kind: 'Template',
|
||||
'metadata.name': templateName,
|
||||
}),
|
||||
}) as Promise<TemplateEntityV1alpha1[]>,
|
||||
);
|
||||
return { template: data?.[0], loading: !error && !data, error };
|
||||
};
|
||||
@@ -97,7 +94,7 @@ export const TemplatePage = () => {
|
||||
setJobId(job);
|
||||
};
|
||||
|
||||
const [entity, setEntity] = React.useState<ComponentEntityV1alpha1 | null>(
|
||||
const [entity, setEntity] = React.useState<TemplateEntityV1alpha1 | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
@@ -118,12 +115,9 @@ export const TemplatePage = () => {
|
||||
|
||||
const {
|
||||
entities: [createdEntity],
|
||||
} = await catalogApi.addLocation<ComponentEntityV1alpha1>(
|
||||
'github',
|
||||
componentYaml,
|
||||
);
|
||||
} = await catalogApi.addLocation('github', componentYaml);
|
||||
|
||||
setEntity(createdEntity);
|
||||
setEntity((createdEntity as any) as TemplateEntityV1alpha1);
|
||||
};
|
||||
|
||||
if (!loading && !template) {
|
||||
@@ -152,7 +146,7 @@ export const TemplatePage = () => {
|
||||
subtitle="Create new software components using standard templates"
|
||||
/>
|
||||
<Content>
|
||||
{loading && <LinearProgress />}
|
||||
{loading && <LinearProgress data-testid="loading-progress" />}
|
||||
{jobId && (
|
||||
<JobStatusModal
|
||||
onComplete={handleCreateComplete}
|
||||
|
||||
Reference in New Issue
Block a user