grafana/public/app/features/dashboard-scene/settings/DeleteProvisionedDashboardF...

303 lines
8.7 KiB
TypeScript

import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { AppEvents } from '@grafana/data';
import { getAppEvents } from '@grafana/runtime';
import { useDeleteRepositoryFilesWithPathMutation } from 'app/api/clients/provisioning/v0alpha1';
import { useProvisionedDashboardData, ProvisionedDashboardData } from '../saving/provisioned/hooks';
import { DashboardScene } from '../scene/DashboardScene';
import { DeleteProvisionedDashboardDrawer, Props } from './DeleteProvisionedDashboardDrawer';
// Mock the hooks and dependencies
jest.mock('app/api/clients/provisioning/v0alpha1', () => ({
useDeleteRepositoryFilesWithPathMutation: jest.fn(),
provisioningAPIv0alpha1: {
endpoints: {
listRepository: {
select: jest.fn(() => () => ({ data: { items: [] } })),
},
},
},
}));
jest.mock('../saving/provisioned/hooks');
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
getAppEvents: jest.fn(),
}));
jest.mock('react-router-dom-v5-compat', () => ({
...jest.requireActual('react-router-dom-v5-compat'),
useNavigate: () => mockNavigate,
}));
// Add this variable declaration near your other mock variables
const mockNavigate = jest.fn();
// Mock shared form components
jest.mock('../components/Provisioned/ResourceEditFormSharedFields', () => ({
ResourceEditFormSharedFields: ({ disabled }: { disabled: boolean }) => (
<textarea data-testid="shared-fields" disabled={disabled} />
),
}));
const mockDeleteRepoFile = jest.fn();
const mockPublish = jest.fn();
const mockUseDeleteRepositoryFiles = useDeleteRepositoryFilesWithPathMutation as jest.MockedFunction<
typeof useDeleteRepositoryFilesWithPathMutation
>;
const mockUseProvisionedDashboardData = useProvisionedDashboardData as jest.MockedFunction<
typeof useProvisionedDashboardData
>;
// Mock request state helper
type MockRequestState = {
isLoading: boolean;
isSuccess: boolean;
isError: boolean;
error?: Error;
reset: () => void;
};
const createMockRequestState = (overrides: Partial<MockRequestState> = {}): MockRequestState => ({
isLoading: false,
isSuccess: false,
isError: false,
reset: jest.fn(),
...overrides,
});
interface SetupOptions extends Partial<Props> {
provisionedData?: Partial<ProvisionedDashboardData>;
requestState?: Partial<MockRequestState>;
}
function setup(options: SetupOptions = {}) {
const { provisionedData = {}, requestState = {}, ...props } = options;
const user = userEvent.setup();
const defaultDashboard = new DashboardScene({
title: 'Test Dashboard',
uid: 'test-uid',
meta: { slug: 'test-slug' },
});
const defaultProvisionedData: ProvisionedDashboardData = {
isReady: true,
isLoading: false,
setIsLoading: jest.fn(),
defaultValues: {
repo: 'test-repo',
ref: 'main',
workflow: 'branch' as const,
path: 'dashboards/test.json',
comment: '',
title: 'Test Dashboard',
description: 'Test Description',
folder: {
uid: 'test-folder',
title: 'Test Folder',
},
},
repository: {
name: 'test-repo',
target: 'folder' as const,
title: 'Test Repository',
type: 'github' as const,
workflows: ['branch', 'write'] as Array<'branch' | 'write'>,
},
loadedFromRef: 'main',
readOnly: false,
isGitHub: true,
workflowOptions: [
{ label: 'Branch', value: 'branch' },
{ label: 'Write', value: 'write' },
],
isNew: false,
...provisionedData,
};
const defaultProps: Props = {
dashboard: defaultDashboard,
onDismiss: jest.fn(),
...props,
};
// Set up mocks with the merged data
mockUseProvisionedDashboardData.mockReturnValue(defaultProvisionedData);
mockUseDeleteRepositoryFiles.mockReturnValue([
mockDeleteRepoFile,
createMockRequestState(requestState) as ReturnType<typeof useDeleteRepositoryFilesWithPathMutation>[1],
]);
return {
user,
props: defaultProps,
defaultProvisionedData,
...render(<DeleteProvisionedDashboardDrawer {...defaultProps} />),
};
}
describe('DeleteProvisionedDashboardDrawer', () => {
beforeEach(() => {
jest.clearAllMocks();
// Mock getAppEvents
(getAppEvents as jest.Mock).mockReturnValue({
publish: mockPublish,
});
});
describe('Rendering', () => {
it('should render the drawer with correct title and subtitle', () => {
setup();
expect(screen.getByRole('heading', { name: 'Delete Provisioned Dashboard' })).toBeInTheDocument();
expect(screen.getByText('Test Dashboard')).toBeInTheDocument();
});
it('should return null when defaultValues are not provided', () => {
setup({
provisionedData: {
defaultValues: null,
},
});
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});
it('should render shared form fields correctly', () => {
setup();
expect(screen.getByTestId('shared-fields')).toBeInTheDocument();
});
it('should render delete and cancel buttons', () => {
setup();
expect(screen.getByRole('button', { name: /delete dashboard/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
});
});
describe('Form Submission', () => {
it('should successfully delete dashboard with branch workflow', async () => {
const { user } = setup();
const deleteButton = screen.getByRole('button', { name: /delete dashboard/i });
await user.click(deleteButton);
await waitFor(() => {
expect(mockDeleteRepoFile).toHaveBeenCalledWith({
name: 'test-repo',
path: 'dashboards/test.json',
ref: 'main',
message: 'Delete dashboard: Test Dashboard',
});
});
});
it('should handle missing repository name', async () => {
const { user } = setup({
provisionedData: {
defaultValues: {
repo: '',
ref: 'main',
workflow: 'branch' as const,
path: 'dashboards/test.json',
comment: '',
title: 'Test Dashboard',
description: 'Test Description',
folder: { uid: 'test-folder', title: 'Test Folder' },
},
repository: undefined,
},
});
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
const deleteButton = screen.getByRole('button', { name: /delete dashboard/i });
await user.click(deleteButton);
expect(consoleSpy).toHaveBeenCalledWith('Missing required fields for deletion:', {
repo: '',
path: 'dashboards/test.json',
});
expect(mockDeleteRepoFile).not.toHaveBeenCalled();
consoleSpy.mockRestore();
});
it('should handle missing path', async () => {
const { user } = setup({
provisionedData: {
defaultValues: {
repo: 'test-repo',
ref: 'main',
workflow: 'branch' as const,
path: '',
comment: '',
title: 'Test Dashboard',
description: 'Test Description',
folder: { uid: 'test-folder', title: 'Test Folder' },
},
},
});
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
const deleteButton = screen.getByRole('button', { name: /delete dashboard/i });
await user.click(deleteButton);
expect(consoleSpy).toHaveBeenCalledWith('Missing required fields for deletion:', {
repo: 'test-repo',
path: '',
});
expect(mockDeleteRepoFile).not.toHaveBeenCalled();
consoleSpy.mockRestore();
});
});
describe('Error Handling', () => {
it('should handle error state', async () => {
const error = new Error('API Error');
setup({
requestState: {
isError: true,
error,
},
});
await waitFor(() => {
expect(mockPublish).toHaveBeenCalledWith({
type: AppEvents.alertError.name,
payload: ['Failed to delete dashboard', error],
});
});
});
});
describe('Loading State', () => {
it('should show loading state when deletion is in progress', () => {
setup({
requestState: {
isLoading: true,
},
});
const deleteButton = screen.getByRole('button', { name: /deleting/i });
expect(deleteButton).toBeDisabled();
expect(deleteButton).toHaveTextContent('Deleting...');
});
});
describe('User Interactions', () => {
it('should call onDismiss when cancel button is clicked', async () => {
const { user, props } = setup();
const cancelButton = screen.getByRole('button', { name: /cancel/i });
await user.click(cancelButton);
expect(props.onDismiss).toHaveBeenCalled();
});
});
});