test(scaffolder): pages header navigation

Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
Camila Belo
2024-10-04 16:04:27 +02:00
parent f27ec8eb8e
commit 4e9702e8cc
7 changed files with 347 additions and 47 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder': patch
---
Add tests for the new pages header navigation.
@@ -88,27 +88,28 @@ export const CustomFieldExplorer = ({
const classes = useStyles();
const { t } = useTranslationRef(scaffolderTranslationRef);
const fieldOptions = customFieldExtensions.filter(field => !!field.schema);
const [selectedField, setSelectedField] = useState(fieldOptions[0]);
const [selectedField, setSelectedField] = useState(fieldOptions?.[0]);
const [fieldFormState, setFieldFormState] = useState({});
const [refreshKey, setRefreshKey] = useState(Date.now());
const sampleFieldTemplate = useMemo(
() =>
yaml.stringify({
parameters: [
{
title: `${selectedField.name} Example`,
properties: {
[selectedField.name]: {
type: selectedField.schema?.returnValue?.type,
'ui:field': selectedField.name,
'ui:options': fieldFormState,
},
const sampleFieldTemplate = useMemo(() => {
if (!selectedField) {
return '';
}
return yaml.stringify({
parameters: [
{
title: `${selectedField.name} Example`,
properties: {
[selectedField.name]: {
type: selectedField.schema?.returnValue?.type,
'ui:field': selectedField.name,
'ui:options': fieldFormState,
},
},
],
}),
[fieldFormState, selectedField],
);
},
],
});
}, [fieldFormState, selectedField]);
const fieldComponents = useMemo(() => {
return Object.fromEntries(
@@ -185,7 +186,7 @@ export const CustomFieldExplorer = ({
formContext={{ fieldFormState }}
onSubmit={e => handleFieldConfigChange(e.formData)}
validator={validator}
schema={selectedField.schema?.uiOptions || {}}
schema={selectedField?.schema?.uiOptions || {}}
experimental_defaultFormStateBehavior={{
allOf: 'populateDefaults',
}}
@@ -194,7 +195,7 @@ export const CustomFieldExplorer = ({
variant="contained"
color="primary"
type="submit"
disabled={!selectedField.schema?.uiOptions}
disabled={!selectedField?.schema?.uiOptions}
>
{t(
'templateEditorPage.customFieldExplorer.fieldForm.applyButtonTitle',
@@ -0,0 +1,71 @@
/*
* Copyright 2024 The Backstage Authors
*
* 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 { screen } from '@testing-library/react';
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react';
import { rootRouteRef } from '../../../routes';
import { CustomFieldsPage } from './CustomFieldsPage';
describe('CustomFieldsPage', () => {
const catalogApiMock = { getEntities: jest.fn().mockResolvedValue([]) };
const scaffolderApiMock = {};
it('Should render without exploding', async () => {
await renderInTestApp(
<TestApiProvider
apis={[
[catalogApiRef, catalogApiMock],
[scaffolderApiRef, scaffolderApiMock],
]}
>
<CustomFieldsPage fieldExtensions={[]} />
</TestApiProvider>,
{
mountedRoutes: {
'/': rootRouteRef,
},
},
);
expect(
screen.getByRole('heading', { name: 'Custom Field Explorer' }),
).toBeInTheDocument();
});
it('Should have an link back to the edit page', async () => {
await renderInTestApp(
<TestApiProvider
apis={[
[catalogApiRef, catalogApiMock],
[scaffolderApiRef, scaffolderApiMock],
]}
>
<CustomFieldsPage />
</TestApiProvider>,
{
mountedRoutes: {
'/': rootRouteRef,
},
routeEntries: ['/edit'],
},
);
expect(
screen.getByRole('link', { name: /Manage Templates/ }),
).toHaveAttribute('href', '/edit');
});
});
@@ -0,0 +1,71 @@
/*
* Copyright 2024 The Backstage Authors
*
* 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 { screen } from '@testing-library/react';
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react';
import { TemplateEditorPage } from './TemplateEditorPage';
import { rootRouteRef } from '../../../routes';
describe('TemplateEditorPage', () => {
const catalogApiMock = { getEntities: jest.fn().mockResolvedValue([]) };
const scaffolderApiMock = {};
it('Should render without exploding', async () => {
await renderInTestApp(
<TestApiProvider
apis={[
[catalogApiRef, catalogApiMock],
[scaffolderApiRef, scaffolderApiMock],
]}
>
<TemplateEditorPage />
</TestApiProvider>,
{
mountedRoutes: {
'/': rootRouteRef,
},
},
);
expect(
screen.getByRole('heading', { name: 'Template Editor' }),
).toBeInTheDocument();
});
it('Should have an link back to the edit page', async () => {
await renderInTestApp(
<TestApiProvider
apis={[
[catalogApiRef, catalogApiMock],
[scaffolderApiRef, scaffolderApiMock],
]}
>
<TemplateEditorPage />
</TestApiProvider>,
{
mountedRoutes: {
'/': rootRouteRef,
},
routeEntries: ['/edit'],
},
);
expect(
screen.getByRole('link', { name: /Manage Templates/ }),
).toHaveAttribute('href', '/edit');
});
});
@@ -0,0 +1,59 @@
/*
* Copyright 2024 The Backstage Authors
*
* 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 { screen } from '@testing-library/react';
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { TemplateFormPage } from './TemplateFormPage';
import { rootRouteRef } from '../../../routes';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
describe('TemplateFormPage', () => {
const catalogApiMock = { getEntities: jest.fn().mockResolvedValue([]) };
it('Should render without exploding', async () => {
await renderInTestApp(
<TestApiProvider apis={[[catalogApiRef, catalogApiMock]]}>
<TemplateFormPage />
</TestApiProvider>,
{
mountedRoutes: {
'/': rootRouteRef,
},
},
);
expect(
screen.getByRole('heading', { name: 'Template Editor' }),
).toBeInTheDocument();
});
it('Should have an link back to the edit page', async () => {
await renderInTestApp(
<TestApiProvider apis={[[catalogApiRef, catalogApiMock]]}>
<TemplateFormPage />
</TestApiProvider>,
{
mountedRoutes: {
'/': rootRouteRef,
},
routeEntries: ['/edit'],
},
);
expect(
screen.getByRole('link', { name: /Manage Templates/ }),
).toHaveAttribute('href', '/edit');
});
});
@@ -0,0 +1,91 @@
/*
* Copyright 2024 The Backstage Authors
*
* 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 { screen } from '@testing-library/react';
import { renderInTestApp } from '@backstage/test-utils';
import { TemplateIntroPage } from './TemplateIntroPage';
import { rootRouteRef } from '../../../routes';
describe('TemplateIntroPage', () => {
it('Should render without exploding', async () => {
await renderInTestApp(<TemplateIntroPage />, {
mountedRoutes: {
'/': rootRouteRef,
},
});
expect(
screen.getByRole('heading', { name: 'Manage Templates' }),
).toBeInTheDocument();
});
it('Should have an link back to the create page', async () => {
await renderInTestApp(<TemplateIntroPage />, {
mountedRoutes: {
'/': rootRouteRef,
},
});
expect(screen.getByRole('link', { name: /Scaffolder/ })).toHaveAttribute(
'href',
'/',
);
});
it('Should have an action to load a template directory', async () => {
await renderInTestApp(<TemplateIntroPage />, {
mountedRoutes: {
'/': rootRouteRef,
},
});
expect(
screen.getByRole('button', { name: /Load Template Directory/ }),
).toBeInTheDocument();
});
it('Should have an action to create a template directory', async () => {
await renderInTestApp(<TemplateIntroPage />, {
mountedRoutes: {
'/': rootRouteRef,
},
});
expect(
screen.getByRole('button', { name: /Create New Template/ }),
).toBeInTheDocument();
});
it('Should have an action to open the template playground', async () => {
await renderInTestApp(<TemplateIntroPage />, {
mountedRoutes: {
'/': rootRouteRef,
},
});
expect(
screen.getByRole('button', { name: /Template Form Playground/ }),
).toBeInTheDocument();
});
it('Should have an action to open the custom fields explorer', async () => {
await renderInTestApp(<TemplateIntroPage />, {
mountedRoutes: {
'/': rootRouteRef,
},
});
expect(
screen.getByRole('button', { name: /Custom Field Explorer/ }),
).toBeInTheDocument();
});
});
@@ -13,11 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import React, { useCallback } from 'react';
import { Content, Header, Page } from '@backstage/core-components';
import { WebFileSystemAccess } from '../../../lib/filesystem';
import { TemplateEditorIntro } from './TemplateEditorIntro';
import { useNavigate } from 'react-router-dom';
import { useRouteRef } from '@backstage/core-plugin-api';
@@ -29,8 +27,7 @@ import {
} from '../../../routes';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { scaffolderTranslationRef } from '../../../translation';
import { WebFileSystemStore } from '../../../lib/filesystem/WebFileSystemStore';
import { createExampleTemplate } from '../../../lib/filesystem/createExampleTemplate';
import { useTemplateDirectory } from './useTemplateDirectory';
export function TemplateIntroPage() {
const navigate = useNavigate();
@@ -39,6 +36,33 @@ export function TemplateIntroPage() {
const templateFormLink = useRouteRef(templateFormRouteRef);
const customFieldsLink = useRouteRef(customFieldsRouteRef);
const { t } = useTranslationRef(scaffolderTranslationRef);
const { openDirectory, createDirectory } = useTemplateDirectory();
const handleSelect = useCallback(
(option: 'create-template' | 'local' | 'form' | 'field-explorer') => {
if (option === 'local') {
openDirectory()
.then(() => navigate(editorLink()))
.catch(() => {});
} else if (option === 'create-template') {
createDirectory()
.then(() => navigate(editorLink()))
.catch(() => {});
} else if (option === 'form') {
navigate(templateFormLink());
} else if (option === 'field-explorer') {
navigate(customFieldsLink());
}
},
[
openDirectory,
createDirectory,
navigate,
editorLink,
templateFormLink,
customFieldsLink,
],
);
return (
<Page themeId="home">
@@ -49,29 +73,7 @@ export function TemplateIntroPage() {
subtitle={t('templateIntroPage.subtitle')}
/>
<Content>
<TemplateEditorIntro
onSelect={option => {
if (option === 'local') {
WebFileSystemAccess.requestDirectoryAccess()
.then(directory => WebFileSystemStore.setDirectory(directory))
.then(() => navigate(editorLink()))
.catch(() => {});
} else if (option === 'create-template') {
WebFileSystemAccess.requestDirectoryAccess()
.then(directory => {
createExampleTemplate(directory).then(() => {
WebFileSystemStore.setDirectory(directory);
navigate(editorLink());
});
})
.catch(() => {});
} else if (option === 'form') {
navigate(templateFormLink());
} else if (option === 'field-explorer') {
navigate(customFieldsLink());
}
}}
/>
<TemplateEditorIntro onSelect={handleSelect} />
</Content>
</Page>
);