mirror of
https://github.com/grafana/grafana.git
synced 2024-11-29 20:24:18 -06:00
Nested folders: Create basic Move/Delete modals (#67140)
* add modal scaffolding * add some unit tests * remove dummy api, add some TODO comments * small test refactor * another small test refactor * fix unit tests due to aria-label/data-testid change
This commit is contained in:
parent
bb66f14c1d
commit
e6e741546f
@ -962,9 +962,6 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "2"]
|
||||
],
|
||||
"packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx:5381": [
|
||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"]
|
||||
],
|
||||
"packages/grafana-ui/src/components/DataLinks/DataLinkInput.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "1"],
|
||||
|
@ -38,7 +38,7 @@ export const Pages = {
|
||||
dataSourcePluginsV2: (pluginName: string) => `Add new data source ${pluginName}`,
|
||||
},
|
||||
ConfirmModal: {
|
||||
delete: 'Confirm Modal Danger Button',
|
||||
delete: 'data-testid Confirm Modal Danger Button',
|
||||
},
|
||||
AddDashboard: {
|
||||
url: '/dashboard/new',
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { render, screen, within } from '@testing-library/react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { ConfirmModal } from './ConfirmModal';
|
||||
@ -23,8 +23,7 @@ describe('ConfirmModal', () => {
|
||||
expect(screen.getByText('Some Body')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Dismiss Text' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Alternative Text' })).toBeInTheDocument();
|
||||
const button = screen.getByRole('button', { name: 'Confirm Modal Danger Button' });
|
||||
expect(within(button).getByText('Please Confirm')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Please Confirm' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render nothing when isOpen is false', () => {
|
||||
@ -43,6 +42,6 @@ describe('ConfirmModal', () => {
|
||||
expect(screen.queryByText('Some Body')).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: 'Dismiss Text' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: 'Alternative Text' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: 'Confirm Modal Danger Button' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: 'Confirm' })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -105,7 +105,7 @@ export const ConfirmModal = ({
|
||||
onClick={onConfirm}
|
||||
disabled={disabled}
|
||||
ref={buttonRef}
|
||||
aria-label={selectors.pages.ConfirmModal.delete}
|
||||
data-testid={selectors.pages.ConfirmModal.delete}
|
||||
>
|
||||
{confirmText}
|
||||
</Button>
|
||||
|
@ -84,7 +84,7 @@ export function Modal(props: PropsWithChildren<Props>) {
|
||||
typeof title !== 'string' && title
|
||||
}
|
||||
<div className={styles.modalHeaderClose}>
|
||||
<IconButton aria-label="Close dialogue" name="times" size="xl" onClick={onDismiss} />
|
||||
<IconButton aria-label="Close dialog" name="times" size="xl" onClick={onDismiss} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={cx(styles.modalContent, contentClassName)}>{children}</div>
|
||||
|
@ -75,7 +75,7 @@ const dataSources = {
|
||||
};
|
||||
|
||||
const ui = {
|
||||
confirmButton: byRole('button', { name: /Confirm Modal Danger Button/ }),
|
||||
confirmButton: byRole('button', { name: /Yes, reset configuration/ }),
|
||||
resetButton: byRole('button', { name: /Reset configuration/ }),
|
||||
saveButton: byRole('button', { name: /Save/ }),
|
||||
configInput: byLabelText<HTMLTextAreaElement>(/Configuration/),
|
||||
|
@ -12,7 +12,7 @@ import { buildNavModel } from '../folders/state/navModel';
|
||||
import { parseRouteParams } from '../search/utils';
|
||||
|
||||
import { skipToken, useGetFolderQuery } from './api/browseDashboardsAPI';
|
||||
import { BrowseActions } from './components/BrowseActions';
|
||||
import { BrowseActions } from './components/BrowseActions/BrowseActions';
|
||||
import { BrowseFilters } from './components/BrowseFilters';
|
||||
import { BrowseView } from './components/BrowseView';
|
||||
import { SearchView } from './components/SearchView';
|
||||
|
@ -1,41 +0,0 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Button, useStyles2 } from '@grafana/ui';
|
||||
|
||||
export interface Props {}
|
||||
|
||||
export function BrowseActions() {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const onMove = () => {
|
||||
// TODO real implemenation, stub for now
|
||||
console.log('onMoveClicked');
|
||||
};
|
||||
|
||||
const onDelete = () => {
|
||||
// TODO real implementation, stub for now
|
||||
console.log('onDeleteClicked');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.row} data-testid="manage-actions">
|
||||
<Button onClick={onMove} variant="secondary">
|
||||
Move
|
||||
</Button>
|
||||
<Button onClick={onDelete} variant="destructive">
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
row: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: theme.spacing(1),
|
||||
marginBottom: theme.spacing(2),
|
||||
}),
|
||||
});
|
@ -1,8 +1,13 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { render as rtlRender, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { TestProvider } from 'test/helpers/TestProvider';
|
||||
|
||||
import { BrowseActions } from './BrowseActions';
|
||||
|
||||
function render(...[ui, options]: Parameters<typeof rtlRender>) {
|
||||
rtlRender(<TestProvider>{ui}</TestProvider>, options);
|
||||
}
|
||||
|
||||
describe('browse-dashboards BrowseActions', () => {
|
||||
it('displays Move and Delete buttons', () => {
|
||||
render(<BrowseActions />);
|
@ -0,0 +1,67 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Button, useStyles2 } from '@grafana/ui';
|
||||
import appEvents from 'app/core/app_events';
|
||||
import { ShowModalReactEvent } from 'app/types/events';
|
||||
|
||||
import { useSelectedItemsState } from '../../state';
|
||||
|
||||
import { DeleteModal } from './DeleteModal';
|
||||
import { MoveModal } from './MoveModal';
|
||||
|
||||
export interface Props {}
|
||||
|
||||
export function BrowseActions() {
|
||||
const styles = useStyles2(getStyles);
|
||||
const selectedItems = useSelectedItemsState();
|
||||
|
||||
const onMove = () => {
|
||||
appEvents.publish(
|
||||
new ShowModalReactEvent({
|
||||
component: MoveModal,
|
||||
props: {
|
||||
selectedItems,
|
||||
onConfirm: (moveTarget: string) => {
|
||||
console.log(`MoveModal onConfirm clicked with target ${moveTarget}!`);
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const onDelete = () => {
|
||||
appEvents.publish(
|
||||
new ShowModalReactEvent({
|
||||
component: DeleteModal,
|
||||
props: {
|
||||
selectedItems,
|
||||
onConfirm: () => {
|
||||
console.log('DeleteModal onConfirm clicked!');
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.row} data-testid="manage-actions">
|
||||
<Button onClick={onMove} variant="secondary">
|
||||
Move
|
||||
</Button>
|
||||
<Button onClick={onDelete} variant="destructive">
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
row: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: theme.spacing(1),
|
||||
marginBottom: theme.spacing(2),
|
||||
}),
|
||||
});
|
@ -0,0 +1,72 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
|
||||
import { DeleteModal, Props } from './DeleteModal';
|
||||
|
||||
describe('browse-dashboards DeleteModal', () => {
|
||||
const mockOnDismiss = jest.fn();
|
||||
const mockOnConfirm = jest.fn();
|
||||
|
||||
const defaultProps: Props = {
|
||||
isOpen: true,
|
||||
onConfirm: mockOnConfirm,
|
||||
onDismiss: mockOnDismiss,
|
||||
selectedItems: {
|
||||
folder: {},
|
||||
dashboard: {},
|
||||
panel: {},
|
||||
},
|
||||
};
|
||||
|
||||
it('renders a dialog with the correct title', async () => {
|
||||
render(<DeleteModal {...defaultProps} />);
|
||||
|
||||
expect(await screen.findByRole('dialog', { name: 'Delete Compute Resources' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays a `Delete` button', async () => {
|
||||
render(<DeleteModal {...defaultProps} />);
|
||||
|
||||
expect(await screen.findByRole('button', { name: 'Delete' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays a `Cancel` button', async () => {
|
||||
render(<DeleteModal {...defaultProps} />);
|
||||
|
||||
expect(await screen.findByRole('button', { name: 'Cancel' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('only enables the `Delete` button if the confirmation text is typed', async () => {
|
||||
render(<DeleteModal {...defaultProps} />);
|
||||
|
||||
const confirmationInput = await screen.findByPlaceholderText('Type Delete to confirm');
|
||||
await userEvent.type(confirmationInput, 'Delete');
|
||||
|
||||
expect(await screen.findByRole('button', { name: 'Delete' })).toBeEnabled();
|
||||
});
|
||||
|
||||
it('calls onConfirm when clicking the `Delete` button', async () => {
|
||||
render(<DeleteModal {...defaultProps} />);
|
||||
|
||||
const confirmationInput = await screen.findByPlaceholderText('Type Delete to confirm');
|
||||
await userEvent.type(confirmationInput, 'Delete');
|
||||
|
||||
await userEvent.click(await screen.findByRole('button', { name: 'Delete' }));
|
||||
expect(mockOnConfirm).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls onDismiss when clicking the `Cancel` button', async () => {
|
||||
render(<DeleteModal {...defaultProps} />);
|
||||
|
||||
await userEvent.click(await screen.findByRole('button', { name: 'Cancel' }));
|
||||
expect(mockOnDismiss).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls onDismiss when clicking the X', async () => {
|
||||
render(<DeleteModal {...defaultProps} />);
|
||||
|
||||
await userEvent.click(await screen.findByRole('button', { name: 'Close dialog' }));
|
||||
expect(mockOnDismiss).toHaveBeenCalled();
|
||||
});
|
||||
});
|
@ -0,0 +1,62 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2, isTruthy } from '@grafana/data';
|
||||
import { ConfirmModal, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { DashboardTreeSelection } from '../../types';
|
||||
|
||||
import { buildBreakdownString } from './utils';
|
||||
|
||||
export interface Props {
|
||||
isOpen: boolean;
|
||||
onConfirm: () => void;
|
||||
onDismiss: () => void;
|
||||
selectedItems: DashboardTreeSelection;
|
||||
}
|
||||
|
||||
export const DeleteModal = ({ onConfirm, onDismiss, selectedItems, ...props }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
// TODO abstract all this counting logic out
|
||||
const folderCount = Object.values(selectedItems.folder).filter(isTruthy).length;
|
||||
const dashboardCount = Object.values(selectedItems.dashboard).filter(isTruthy).length;
|
||||
// hardcoded values for now
|
||||
// TODO replace with dummy API
|
||||
const libraryPanelCount = 1;
|
||||
const alertRuleCount = 1;
|
||||
|
||||
const onDelete = () => {
|
||||
onConfirm();
|
||||
onDismiss();
|
||||
};
|
||||
|
||||
return (
|
||||
<ConfirmModal
|
||||
body={
|
||||
<div className={styles.modalBody}>
|
||||
This action will delete the following content:
|
||||
<p className={styles.breakdown}>
|
||||
{buildBreakdownString(folderCount, dashboardCount, libraryPanelCount, alertRuleCount)}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
confirmationText="Delete"
|
||||
confirmText="Delete"
|
||||
onDismiss={onDismiss}
|
||||
onConfirm={onDelete}
|
||||
title="Delete Compute Resources"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
breakdown: css({
|
||||
...theme.typography.bodySmall,
|
||||
color: theme.colors.text.secondary,
|
||||
}),
|
||||
modalBody: css({
|
||||
...theme.typography.body,
|
||||
}),
|
||||
});
|
@ -0,0 +1,101 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { selectOptionInTest } from 'test/helpers/selectOptionInTest';
|
||||
|
||||
import * as api from 'app/features/manage-dashboards/state/actions';
|
||||
import { DashboardSearchHit } from 'app/features/search/types';
|
||||
|
||||
import { MoveModal, Props } from './MoveModal';
|
||||
|
||||
describe('browse-dashboards MoveModal', () => {
|
||||
const mockOnDismiss = jest.fn();
|
||||
const mockOnConfirm = jest.fn();
|
||||
const mockFolders = [
|
||||
{ title: 'General', uid: '' } as DashboardSearchHit,
|
||||
{ title: 'Folder 1', uid: 'wfTJJL5Wz' } as DashboardSearchHit,
|
||||
];
|
||||
let props: Props;
|
||||
|
||||
beforeEach(() => {
|
||||
props = {
|
||||
isOpen: true,
|
||||
onConfirm: mockOnConfirm,
|
||||
onDismiss: mockOnDismiss,
|
||||
selectedItems: {
|
||||
folder: {},
|
||||
dashboard: {},
|
||||
panel: {},
|
||||
},
|
||||
};
|
||||
|
||||
// mock the searchFolders api call so the folder picker has some folders in it
|
||||
jest.spyOn(api, 'searchFolders').mockResolvedValue(mockFolders);
|
||||
});
|
||||
|
||||
it('renders a dialog with the correct title', async () => {
|
||||
render(<MoveModal {...props} />);
|
||||
|
||||
expect(await screen.findByRole('dialog', { name: 'Move' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays a `Move` button', async () => {
|
||||
render(<MoveModal {...props} />);
|
||||
|
||||
expect(await screen.findByRole('button', { name: 'Move' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays a `Cancel` button', async () => {
|
||||
render(<MoveModal {...props} />);
|
||||
|
||||
expect(await screen.findByRole('button', { name: 'Cancel' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays a folder picker', async () => {
|
||||
render(<MoveModal {...props} />);
|
||||
|
||||
expect(await screen.findByRole('combobox', { name: 'Select a folder' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays a warning about permissions if a folder is selected', async () => {
|
||||
props.selectedItems.folder = {
|
||||
myFolderUid: true,
|
||||
};
|
||||
render(<MoveModal {...props} />);
|
||||
|
||||
expect(await screen.findByText('Moving this item may change its permissions.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('only enables the `Move` button if a folder is selected', async () => {
|
||||
render(<MoveModal {...props} />);
|
||||
|
||||
expect(await screen.findByRole('button', { name: 'Move' })).toBeDisabled();
|
||||
const folderPicker = await screen.findByRole('combobox', { name: 'Select a folder' });
|
||||
|
||||
await selectOptionInTest(folderPicker, mockFolders[1].title);
|
||||
expect(await screen.findByRole('button', { name: 'Move' })).toBeEnabled();
|
||||
});
|
||||
|
||||
it('calls onConfirm when clicking the `Move` button', async () => {
|
||||
render(<MoveModal {...props} />);
|
||||
const folderPicker = await screen.findByRole('combobox', { name: 'Select a folder' });
|
||||
|
||||
await selectOptionInTest(folderPicker, mockFolders[1].title);
|
||||
await userEvent.click(await screen.findByRole('button', { name: 'Move' }));
|
||||
expect(mockOnConfirm).toHaveBeenCalledWith(mockFolders[1].uid);
|
||||
});
|
||||
|
||||
it('calls onDismiss when clicking the `Cancel` button', async () => {
|
||||
render(<MoveModal {...props} />);
|
||||
|
||||
await userEvent.click(await screen.findByRole('button', { name: 'Cancel' }));
|
||||
expect(mockOnDismiss).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls onDismiss when clicking the X', async () => {
|
||||
render(<MoveModal {...props} />);
|
||||
|
||||
await userEvent.click(await screen.findByRole('button', { name: 'Close dialog' }));
|
||||
expect(mockOnDismiss).toHaveBeenCalled();
|
||||
});
|
||||
});
|
@ -0,0 +1,65 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2, isTruthy } from '@grafana/data';
|
||||
import { Alert, Button, Field, Modal, useStyles2 } from '@grafana/ui';
|
||||
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
|
||||
|
||||
import { DashboardTreeSelection } from '../../types';
|
||||
|
||||
import { buildBreakdownString } from './utils';
|
||||
|
||||
export interface Props {
|
||||
isOpen: boolean;
|
||||
onConfirm: (targetFolderUid: string) => void;
|
||||
onDismiss: () => void;
|
||||
selectedItems: DashboardTreeSelection;
|
||||
}
|
||||
|
||||
export const MoveModal = ({ onConfirm, onDismiss, selectedItems, ...props }: Props) => {
|
||||
const [moveTarget, setMoveTarget] = useState<string>();
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
// TODO abstract all this counting logic out
|
||||
const folderCount = Object.values(selectedItems.folder).filter(isTruthy).length;
|
||||
const dashboardCount = Object.values(selectedItems.dashboard).filter(isTruthy).length;
|
||||
// hardcoded values for now
|
||||
// TODO replace with dummy API
|
||||
const libraryPanelCount = 1;
|
||||
const alertRuleCount = 1;
|
||||
|
||||
const onMove = () => {
|
||||
if (moveTarget !== undefined) {
|
||||
onConfirm(moveTarget);
|
||||
}
|
||||
onDismiss();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal title="Move" onDismiss={onDismiss} {...props}>
|
||||
{folderCount > 0 && <Alert severity="warning" title="Moving this item may change its permissions." />}
|
||||
This action will move the following content:
|
||||
<p className={styles.breakdown}>
|
||||
{buildBreakdownString(folderCount, dashboardCount, libraryPanelCount, alertRuleCount)}
|
||||
</p>
|
||||
<Field label="Folder name">
|
||||
<FolderPicker allowEmpty onChange={({ uid }) => setMoveTarget(uid)} />
|
||||
</Field>
|
||||
<Modal.ButtonRow>
|
||||
<Button onClick={onDismiss} variant="secondary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button disabled={moveTarget === undefined} onClick={onMove} variant="primary">
|
||||
Move
|
||||
</Button>
|
||||
</Modal.ButtonRow>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
breakdown: css({
|
||||
...theme.typography.bodySmall,
|
||||
color: theme.colors.text.secondary,
|
||||
}),
|
||||
});
|
@ -0,0 +1,23 @@
|
||||
import { buildBreakdownString } from './utils';
|
||||
|
||||
describe('browse-dashboards utils', () => {
|
||||
describe('buildBreakdownString', () => {
|
||||
it.each`
|
||||
folderCount | dashboardCount | libraryPanelCount | alertRuleCount | expected
|
||||
${0} | ${0} | ${0} | ${0} | ${'0 items'}
|
||||
${1} | ${0} | ${0} | ${0} | ${'1 item: 1 folder'}
|
||||
${2} | ${0} | ${0} | ${0} | ${'2 items: 2 folders'}
|
||||
${0} | ${1} | ${0} | ${0} | ${'1 item: 1 dashboard'}
|
||||
${0} | ${2} | ${0} | ${0} | ${'2 items: 2 dashboards'}
|
||||
${1} | ${0} | ${1} | ${1} | ${'3 items: 1 folder, 1 library panel, 1 alert rule'}
|
||||
${2} | ${0} | ${3} | ${4} | ${'9 items: 2 folders, 3 library panels, 4 alert rules'}
|
||||
${1} | ${1} | ${1} | ${1} | ${'4 items: 1 folder, 1 dashboard, 1 library panel, 1 alert rule'}
|
||||
${1} | ${2} | ${3} | ${4} | ${'10 items: 1 folder, 2 dashboards, 3 library panels, 4 alert rules'}
|
||||
`(
|
||||
'returns the correct message for the various inputs',
|
||||
({ folderCount, dashboardCount, libraryPanelCount, alertRuleCount, expected }) => {
|
||||
expect(buildBreakdownString(folderCount, dashboardCount, libraryPanelCount, alertRuleCount)).toEqual(expected);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
@ -0,0 +1,26 @@
|
||||
export function buildBreakdownString(
|
||||
folderCount: number,
|
||||
dashboardCount: number,
|
||||
libraryPanelCount: number,
|
||||
alertRuleCount: number
|
||||
) {
|
||||
const total = folderCount + dashboardCount + libraryPanelCount + alertRuleCount;
|
||||
const parts = [];
|
||||
if (folderCount) {
|
||||
parts.push(`${folderCount} ${folderCount === 1 ? 'folder' : 'folders'}`);
|
||||
}
|
||||
if (dashboardCount) {
|
||||
parts.push(`${dashboardCount} ${dashboardCount === 1 ? 'dashboard' : 'dashboards'}`);
|
||||
}
|
||||
if (libraryPanelCount) {
|
||||
parts.push(`${libraryPanelCount} ${libraryPanelCount === 1 ? 'library panel' : 'library panels'}`);
|
||||
}
|
||||
if (alertRuleCount) {
|
||||
parts.push(`${alertRuleCount} ${alertRuleCount === 1 ? 'alert rule' : 'alert rules'}`);
|
||||
}
|
||||
let breakdownString = `${total} ${total === 1 ? 'item' : 'items'}`;
|
||||
if (parts.length > 0) {
|
||||
breakdownString += `: ${parts.join(', ')}`;
|
||||
}
|
||||
return breakdownString;
|
||||
}
|
@ -184,7 +184,7 @@ describe('FolderSettingsPage', () => {
|
||||
await userEvent.click(deleteButton);
|
||||
const deleteModal = screen.getByRole('dialog', { name: 'Delete' });
|
||||
expect(deleteModal).toBeInTheDocument();
|
||||
const deleteButtonModal = within(deleteModal).getByRole('button', { name: 'Confirm Modal Danger Button' });
|
||||
const deleteButtonModal = within(deleteModal).getByRole('button', { name: 'Delete' });
|
||||
await userEvent.click(deleteButtonModal);
|
||||
expect(mockDeleteFolder).toHaveBeenCalledWith(mockFolder.uid);
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { render, screen, within } from '@testing-library/react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { config } from 'app/core/config';
|
||||
@ -14,8 +14,7 @@ describe('ConfirmModal', () => {
|
||||
expect(screen.getByRole('heading', { name: 'Delete' })).toBeInTheDocument();
|
||||
expect(screen.getByText('Do you want to delete the 2 selected dashboards?')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
|
||||
const button = screen.getByRole('button', { name: 'Confirm Modal Danger Button' });
|
||||
expect(within(button).getByText('Delete')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Delete' })).toBeInTheDocument();
|
||||
|
||||
expect(screen.queryByPlaceholderText('Type delete to confirm')).not.toBeInTheDocument();
|
||||
});
|
||||
|
@ -135,7 +135,7 @@ describe('ServiceAccountsListPage tests', () => {
|
||||
|
||||
const user = userEvent.setup();
|
||||
await user.click(screen.getByRole('button', { name: /Disable/ }));
|
||||
await user.click(screen.getByLabelText(/Confirm Modal Danger Button/));
|
||||
await user.click(screen.getByRole('button', { name: 'Disable service account' }));
|
||||
|
||||
expect(updateServiceAccountMock).toHaveBeenCalledWith({
|
||||
...getDefaultServiceAccount(),
|
||||
@ -152,7 +152,7 @@ describe('ServiceAccountsListPage tests', () => {
|
||||
|
||||
const user = userEvent.setup();
|
||||
await user.click(screen.getByLabelText(/Delete service account/));
|
||||
await user.click(screen.getByLabelText(/Confirm Modal Danger Button/));
|
||||
await user.click(screen.getByRole('button', { name: 'Delete' }));
|
||||
|
||||
expect(deleteServiceAccountMock).toHaveBeenCalledWith(42);
|
||||
});
|
||||
|
@ -68,7 +68,7 @@ describe('SelectedLogsGroups', () => {
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText('Are you sure you want to clear all log groups?')).toBeInTheDocument()
|
||||
);
|
||||
await waitFor(() => userEvent.click(screen.getByLabelText('Confirm Modal Danger Button')));
|
||||
await waitFor(() => userEvent.click(screen.getByRole('button', { name: 'Yes' })));
|
||||
expect(defaultProps.onChange).toHaveBeenCalledWith([]);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user