test(scaffolder): editor toolbar files menu
Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Add tests for the `TemplateEditorToolbarFilesMenu` component.
|
||||
@@ -382,7 +382,7 @@ export type TemplateWizardPageProps = {
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
// src/alpha/components/TemplateEditorPage/CustomFieldExplorer.d.ts:4:1 - (ae-undocumented) Missing documentation for "ScaffolderCustomFieldExplorerClassKey".
|
||||
// src/alpha/components/TemplateEditorPage/TemplateEditor.d.ts:5:1 - (ae-undocumented) Missing documentation for "ScaffolderTemplateEditorClassKey".
|
||||
// src/alpha/components/TemplateEditorPage/TemplateEditor.d.ts:4:1 - (ae-undocumented) Missing documentation for "ScaffolderTemplateEditorClassKey".
|
||||
// src/alpha/components/TemplateEditorPage/TemplateFormPreviewer.d.ts:4:1 - (ae-undocumented) Missing documentation for "ScaffolderTemplateFormPreviewerClassKey".
|
||||
// src/alpha/components/TemplateListPage/TemplateListPage.d.ts:7:1 - (ae-undocumented) Missing documentation for "TemplateListPageProps".
|
||||
// src/alpha/components/TemplateWizardPage/TemplateWizardPage.d.ts:6:1 - (ae-undocumented) Missing documentation for "TemplateWizardPageProps".
|
||||
|
||||
@@ -13,12 +13,19 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React, { useState } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useRouteRef } from '@backstage/core-plugin-api';
|
||||
import type {
|
||||
FormProps,
|
||||
LayoutOptions,
|
||||
FieldExtensionOptions,
|
||||
} from '@backstage/plugin-scaffolder-react';
|
||||
import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react';
|
||||
|
||||
import { editRouteRef } from '../../../routes';
|
||||
|
||||
import { useTemplateDirectory } from './useTemplateDirectory';
|
||||
import { DirectoryEditorProvider } from './DirectoryEditorContext';
|
||||
import {
|
||||
TemplateEditorLayout,
|
||||
@@ -31,11 +38,10 @@ import {
|
||||
import { TemplateEditorToolbar } from './TemplateEditorToolbar';
|
||||
import { TemplateEditorToolbarFileMenu } from './TemplateEditorToolbarFileMenu';
|
||||
import { TemplateEditorBrowser } from './TemplateEditorBrowser';
|
||||
import { DryRunProvider } from './DryRunContext';
|
||||
import { TemplateEditorTextArea } from './TemplateEditorTextArea';
|
||||
import { TemplateEditorForm } from './TemplateEditorForm';
|
||||
import { DryRunProvider } from './DryRunContext';
|
||||
import { DryRunResults } from './DryRunResults';
|
||||
import { useTemplateDirectory } from './useTemplateDirectory';
|
||||
|
||||
/** @public */
|
||||
export type ScaffolderTemplateEditorClassKey =
|
||||
@@ -53,13 +59,19 @@ export const TemplateEditor = (props: {
|
||||
}) => {
|
||||
const { layouts, formProps, fieldExtensions } = props;
|
||||
const [errorText, setErrorText] = useState<string>();
|
||||
const navigate = useNavigate();
|
||||
const editLink = useRouteRef(editRouteRef);
|
||||
const {
|
||||
directory,
|
||||
handleOpenDirectory,
|
||||
handleCreateDirectory,
|
||||
handleCloseDirectory,
|
||||
openDirectory: handleOpenDirectory,
|
||||
createDirectory: handleCreateDirectory,
|
||||
closeDirectory,
|
||||
} = useTemplateDirectory();
|
||||
|
||||
const handleCloseDirectory = useCallback(() => {
|
||||
closeDirectory().then(() => navigate(editLink()));
|
||||
}, [closeDirectory, navigate, editLink]);
|
||||
|
||||
return (
|
||||
<DirectoryEditorProvider directory={directory}>
|
||||
<DryRunProvider>
|
||||
@@ -68,13 +80,13 @@ export const TemplateEditor = (props: {
|
||||
<TemplateEditorToolbar fieldExtensions={fieldExtensions}>
|
||||
<TemplateEditorToolbarFileMenu
|
||||
onOpenDirectory={handleOpenDirectory}
|
||||
onCloseDirectory={handleCloseDirectory}
|
||||
onCreateDirectory={handleCreateDirectory}
|
||||
onCloseDirectory={handleCloseDirectory}
|
||||
/>
|
||||
</TemplateEditorToolbar>
|
||||
</TemplateEditorLayoutToolbar>
|
||||
<TemplateEditorLayoutBrowser>
|
||||
<TemplateEditorBrowser onClose={handleCloseDirectory} />
|
||||
<TemplateEditorBrowser onClose={closeDirectory} />
|
||||
</TemplateEditorLayoutBrowser>
|
||||
<TemplateEditorLayoutFiles>
|
||||
<TemplateEditorTextArea.DirectoryEditor errorText={errorText} />
|
||||
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* 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 userEvent from '@testing-library/user-event';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { TemplateEditorToolbarFileMenu } from './TemplateEditorToolbarFileMenu';
|
||||
import { rootRouteRef } from '../../../routes';
|
||||
|
||||
describe('TemplateEditorToolbarFileMenu', () => {
|
||||
it('should disable open directory by default', async () => {
|
||||
await renderInTestApp(<TemplateEditorToolbarFileMenu />, {
|
||||
mountedRoutes: {
|
||||
'/': rootRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.queryByRole('menuitem', { name: 'Open template directory' }),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'File' }));
|
||||
|
||||
expect(
|
||||
screen.getByRole('menuitem', { name: 'Open template directory' }),
|
||||
).toHaveAttribute('aria-disabled', 'true');
|
||||
});
|
||||
|
||||
it('should disable create directory by default', async () => {
|
||||
await renderInTestApp(<TemplateEditorToolbarFileMenu />, {
|
||||
mountedRoutes: {
|
||||
'/': rootRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.queryByRole('menuitem', { name: 'Create template directory' }),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'File' }));
|
||||
|
||||
expect(
|
||||
screen.getByRole('menuitem', { name: 'Create template directory' }),
|
||||
).toHaveAttribute('aria-disabled', 'true');
|
||||
});
|
||||
|
||||
it('should disable close editor by default', async () => {
|
||||
await renderInTestApp(<TemplateEditorToolbarFileMenu />, {
|
||||
mountedRoutes: {
|
||||
'/': rootRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.queryByRole('menuitem', { name: 'Close template editor' }),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'File' }));
|
||||
|
||||
expect(
|
||||
screen.getByRole('menuitem', { name: 'Close template editor' }),
|
||||
).toHaveAttribute('aria-disabled', 'true');
|
||||
});
|
||||
|
||||
it('should have an option to open the directory', async () => {
|
||||
const onOpenDirectory = jest.fn();
|
||||
|
||||
await renderInTestApp(
|
||||
<TemplateEditorToolbarFileMenu onOpenDirectory={onOpenDirectory} />,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByRole('menuitem', { name: 'Open template directory' }),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'File' }));
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByRole('menuitem', { name: 'Open template directory' }),
|
||||
);
|
||||
|
||||
expect(onOpenDirectory).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have an option to create the directory', async () => {
|
||||
const onCreateDirectory = jest.fn();
|
||||
|
||||
await renderInTestApp(
|
||||
<TemplateEditorToolbarFileMenu onCreateDirectory={onCreateDirectory} />,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByRole('menuitem', { name: 'Create template directory' }),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'File' }));
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByRole('menuitem', { name: 'Create template directory' }),
|
||||
);
|
||||
|
||||
expect(onCreateDirectory).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have an option to close the editor', async () => {
|
||||
const onCloseDirectory = jest.fn();
|
||||
|
||||
await renderInTestApp(
|
||||
<TemplateEditorToolbarFileMenu onCloseDirectory={onCloseDirectory} />,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByRole('menuitem', { name: 'Close template editor' }),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: 'File' }));
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByRole('menuitem', { name: 'Close template editor' }),
|
||||
);
|
||||
|
||||
expect(onCloseDirectory).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
+2
-9
@@ -15,16 +15,13 @@
|
||||
*/
|
||||
|
||||
import React, { MouseEvent, useCallback, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Menu from '@material-ui/core/Menu';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
|
||||
import { useRouteRef } from '@backstage/core-plugin-api';
|
||||
import { useTranslationRef } from '@backstage/frontend-plugin-api';
|
||||
|
||||
import { editRouteRef } from '../../../routes';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
export function TemplateEditorToolbarFileMenu(props: {
|
||||
@@ -33,8 +30,6 @@ export function TemplateEditorToolbarFileMenu(props: {
|
||||
onCloseDirectory?: () => void;
|
||||
}) {
|
||||
const { onOpenDirectory, onCreateDirectory, onCloseDirectory } = props;
|
||||
const navigate = useNavigate();
|
||||
const editLink = useRouteRef(editRouteRef);
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
|
||||
@@ -62,8 +57,7 @@ export function TemplateEditorToolbarFileMenu(props: {
|
||||
const handleCloseEditor = useCallback(() => {
|
||||
handleCloseMenu();
|
||||
onCloseDirectory?.();
|
||||
navigate(editLink());
|
||||
}, [handleCloseMenu, onCloseDirectory, navigate, editLink]);
|
||||
}, [handleCloseMenu, onCloseDirectory]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -88,7 +82,6 @@ export function TemplateEditorToolbarFileMenu(props: {
|
||||
vertical: 'top',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
keepMounted
|
||||
>
|
||||
<MenuItem onClick={handleOpenDirectory} disabled={!onOpenDirectory}>
|
||||
{t('templateEditorToolbarFileMenu.options.openDirectory')}
|
||||
@@ -100,7 +93,7 @@ export function TemplateEditorToolbarFileMenu(props: {
|
||||
>
|
||||
{t('templateEditorToolbarFileMenu.options.createDirectory')}
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleCloseEditor}>
|
||||
<MenuItem onClick={handleCloseEditor} disabled={!onCloseDirectory}>
|
||||
{t('templateEditorToolbarFileMenu.options.closeEditor')}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
+21
-6
@@ -14,20 +14,26 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { alertApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import yaml from 'yaml';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import useAsync from 'react-use/esm/useAsync';
|
||||
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
|
||||
import { alertApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
catalogApiRef,
|
||||
humanizeEntityRef,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import useAsync from 'react-use/esm/useAsync';
|
||||
import yaml from 'yaml';
|
||||
import {
|
||||
LayoutOptions,
|
||||
FieldExtensionOptions,
|
||||
FormProps,
|
||||
} from '@backstage/plugin-scaffolder-react';
|
||||
|
||||
import { editRouteRef } from '../../../routes';
|
||||
|
||||
import {
|
||||
TemplateEditorLayout,
|
||||
TemplateEditorLayoutToolbar,
|
||||
@@ -132,11 +138,18 @@ export const TemplateFormPreviewer = ({
|
||||
const classes = useStyles();
|
||||
const alertApi = useApi(alertApiRef);
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const navigate = useNavigate();
|
||||
const editLink = useRouteRef(editRouteRef);
|
||||
|
||||
const [errorText, setErrorText] = useState<string>();
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<TemplateOption>();
|
||||
const [templateOptions, setTemplateOptions] = useState<TemplateOption[]>([]);
|
||||
const [templateYaml, setTemplateYaml] = useState(defaultPreviewTemplate);
|
||||
|
||||
const handleCloseDirectory = useCallback(() => {
|
||||
navigate(editLink());
|
||||
}, [navigate, editLink]);
|
||||
|
||||
useAsync(
|
||||
() =>
|
||||
catalogApi
|
||||
@@ -184,7 +197,9 @@ export const TemplateFormPreviewer = ({
|
||||
<TemplateEditorLayout classes={{ root: classes.root }}>
|
||||
<TemplateEditorLayoutToolbar>
|
||||
<TemplateEditorToolbar fieldExtensions={customFieldExtensions}>
|
||||
<TemplateEditorToolbarFileMenu />
|
||||
<TemplateEditorToolbarFileMenu
|
||||
onCloseDirectory={handleCloseDirectory}
|
||||
/>
|
||||
<TemplateEditorToolbarTemplatesMenu
|
||||
options={templateOptions}
|
||||
selectedOption={selectedTemplate}
|
||||
|
||||
+3
-3
@@ -78,7 +78,7 @@ describe('useTemplateDirectory', () => {
|
||||
expect(result.current.directory).toBeUndefined();
|
||||
|
||||
await act(async () => {
|
||||
result.current.handleOpenDirectory();
|
||||
result.current.openDirectory();
|
||||
});
|
||||
|
||||
expect(requestDirectoryAccess).toHaveBeenCalled();
|
||||
@@ -103,7 +103,7 @@ describe('useTemplateDirectory', () => {
|
||||
const { result } = renderHook(() => useTemplateDirectory());
|
||||
|
||||
await act(async () => {
|
||||
result.current.handleCreateDirectory();
|
||||
result.current.createDirectory();
|
||||
});
|
||||
|
||||
expect(requestDirectoryAccess).toHaveBeenCalled();
|
||||
@@ -124,7 +124,7 @@ describe('useTemplateDirectory', () => {
|
||||
expect(result.current.directory).toBeUndefined();
|
||||
|
||||
await act(async () => {
|
||||
result.current.handleCloseDirectory();
|
||||
result.current.closeDirectory();
|
||||
});
|
||||
|
||||
expect(setDirectory).toHaveBeenCalledWith(undefined);
|
||||
|
||||
+12
-12
@@ -28,9 +28,9 @@ export function useTemplateDirectory(): {
|
||||
directory?: WebDirectoryAccess;
|
||||
loading: boolean;
|
||||
error?: Error;
|
||||
handleOpenDirectory: () => void;
|
||||
handleCreateDirectory: () => void;
|
||||
handleCloseDirectory: () => void;
|
||||
openDirectory: () => Promise<void>;
|
||||
createDirectory: () => Promise<void>;
|
||||
closeDirectory: () => Promise<void>;
|
||||
} {
|
||||
const { value, loading, error, retry } = useAsyncRetry(async () => {
|
||||
const directory = await WebFileSystemStore.getDirectory();
|
||||
@@ -38,29 +38,29 @@ export function useTemplateDirectory(): {
|
||||
return WebFileSystemAccess.fromHandle(directory);
|
||||
}, []);
|
||||
|
||||
const handleOpenDirectory = useCallback(() => {
|
||||
WebFileSystemAccess.requestDirectoryAccess()
|
||||
const openDirectory = useCallback(() => {
|
||||
return WebFileSystemAccess.requestDirectoryAccess()
|
||||
.then(WebFileSystemStore.setDirectory)
|
||||
.then(retry);
|
||||
}, [retry]);
|
||||
|
||||
const handleCreateDirectory = useCallback(() => {
|
||||
WebFileSystemAccess.requestDirectoryAccess()
|
||||
const createDirectory = useCallback(() => {
|
||||
return WebFileSystemAccess.requestDirectoryAccess()
|
||||
.then(createExampleTemplate)
|
||||
.then(WebFileSystemStore.setDirectory)
|
||||
.then(retry);
|
||||
}, [retry]);
|
||||
|
||||
const handleCloseDirectory = useCallback(() => {
|
||||
WebFileSystemStore.setDirectory(undefined).then(retry);
|
||||
const closeDirectory = useCallback(() => {
|
||||
return WebFileSystemStore.setDirectory(undefined).then(retry);
|
||||
}, [retry]);
|
||||
|
||||
return {
|
||||
directory: value,
|
||||
loading,
|
||||
error,
|
||||
handleOpenDirectory,
|
||||
handleCreateDirectory,
|
||||
handleCloseDirectory,
|
||||
openDirectory,
|
||||
createDirectory,
|
||||
closeDirectory,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user