import { act, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React, { ReactNode } from 'react';
import { Provider } from 'react-redux';
import { DataQuery } from '@grafana/data';
import { locationService, setEchoSrv } from '@grafana/runtime';
import { backendSrv } from 'app/core/services/backend_srv';
import { Echo } from 'app/core/services/echo/Echo';
import * as initDashboard from 'app/features/dashboard/state/initDashboard';
import { DashboardSearchItemType } from 'app/features/search/types';
import { configureStore } from 'app/store/configureStore';
import { ExploreId, ExploreState } from 'app/types';
import { createEmptyQueryResponse } from '../state/utils';
import * as api from './addToDashboard';
import { AddToDashboard } from '.';
const setup = (children: ReactNode, queries: DataQuery[] = [{ refId: 'A' }]) => {
const store = configureStore({
explore: {
left: {
queries,
queryResponse: createEmptyQueryResponse(),
},
} as ExploreState,
});
return render({children});
};
const openModal = async () => {
await userEvent.click(screen.getByRole('button', { name: /add to dashboard/i }));
expect(await screen.findByRole('dialog', { name: 'Add panel to dashboard' })).toBeInTheDocument();
};
describe('AddToDashboardButton', () => {
beforeAll(() => {
setEchoSrv(new Echo());
});
it('Is disabled if explore pane has no queries', async () => {
setup(, []);
const button = await screen.findByRole('button', { name: /add to dashboard/i });
expect(button).toBeDisabled();
await userEvent.click(button);
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});
describe('Success path', () => {
const addToDashboardResponse = Promise.resolve();
const waitForAddToDashboardResponse = async () => {
return act(async () => {
await addToDashboardResponse;
});
};
beforeEach(() => {
jest.spyOn(api, 'setDashboardInLocalStorage').mockReturnValue(addToDashboardResponse);
});
afterEach(() => {
jest.restoreAllMocks();
});
it('Opens and closes the modal correctly', async () => {
setup();
await openModal();
await userEvent.click(screen.getByRole('button', { name: /cancel/i }));
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});
describe('navigation', () => {
it('Navigates to dashboard when clicking on "Open"', async () => {
// @ts-expect-error global.open should return a Window, but is not implemented in js-dom.
const openSpy = jest.spyOn(global, 'open').mockReturnValue(true);
const pushSpy = jest.spyOn(locationService, 'push');
setup();
await openModal();
await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i }));
await waitForAddToDashboardResponse();
expect(screen.queryByRole('dialog', { name: 'Add panel to dashboard' })).not.toBeInTheDocument();
expect(pushSpy).toHaveBeenCalled();
expect(openSpy).not.toHaveBeenCalled();
});
it('Navigates to dashboard in a new tab when clicking on "Open in a new tab"', async () => {
// @ts-expect-error global.open should return a Window, but is not implemented in js-dom.
const openSpy = jest.spyOn(global, 'open').mockReturnValue(true);
const pushSpy = jest.spyOn(locationService, 'push');
setup();
await openModal();
await userEvent.click(screen.getByRole('button', { name: /open in new tab/i }));
await waitForAddToDashboardResponse();
expect(openSpy).toHaveBeenCalledWith(expect.anything(), '_blank');
expect(pushSpy).not.toHaveBeenCalled();
});
});
describe('Save to new dashboard', () => {
describe('Navigate to correct dashboard when saving', () => {
it('Opens the new dashboard in a new tab', async () => {
// @ts-expect-error global.open should return a Window, but is not implemented in js-dom.
const openSpy = jest.spyOn(global, 'open').mockReturnValue(true);
setup();
await openModal();
await userEvent.click(screen.getByRole('button', { name: /open in new tab/i }));
await waitForAddToDashboardResponse();
expect(openSpy).toHaveBeenCalledWith('dashboard/new', '_blank');
});
it('Navigates to the new dashboard', async () => {
const pushSpy = jest.spyOn(locationService, 'push');
setup();
await openModal();
await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i }));
await waitForAddToDashboardResponse();
expect(screen.queryByRole('dialog', { name: 'Add panel to dashboard' })).not.toBeInTheDocument();
expect(pushSpy).toHaveBeenCalledWith('dashboard/new');
});
});
});
describe('Save to existing dashboard', () => {
it('Renders the dashboard picker when switching to "Existing Dashboard"', async () => {
setup();
await openModal();
expect(screen.queryByRole('combobox', { name: /dashboard/ })).not.toBeInTheDocument();
await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i }));
expect(screen.getByRole('combobox', { name: /dashboard/ })).toBeInTheDocument();
});
it('Does not submit if no dashboard is selected', async () => {
locationService.push = jest.fn();
setup();
await openModal();
await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i }));
await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i }));
await waitForAddToDashboardResponse();
expect(locationService.push).not.toHaveBeenCalled();
});
describe('Navigate to correct dashboard when saving', () => {
it('Opens the selected dashboard in a new tab', async () => {
// @ts-expect-error global.open should return a Window, but is not implemented in js-dom.
const openSpy = jest.spyOn(global, 'open').mockReturnValue(true);
jest.spyOn(backendSrv, 'getDashboardByUid').mockResolvedValue({
dashboard: { templating: { list: [] }, title: 'Dashboard Title', uid: 'someUid' },
meta: {},
});
jest.spyOn(backendSrv, 'search').mockResolvedValue([
{
id: 1,
uid: 'someUid',
isStarred: false,
items: [],
title: 'Dashboard Title',
tags: [],
type: DashboardSearchItemType.DashDB,
uri: 'someUri',
url: 'someUrl',
},
]);
setup();
await openModal();
await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i }));
await userEvent.click(screen.getByRole('combobox', { name: /dashboard/i }));
await waitFor(async () => {
await screen.findByLabelText('Select option');
});
await userEvent.click(screen.getByLabelText('Select option'));
await userEvent.click(screen.getByRole('button', { name: /open in new tab/i }));
await waitFor(async () => {
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});
expect(openSpy).toBeCalledWith('d/someUid', '_blank');
});
it('Navigates to the selected dashboard', async () => {
const pushSpy = jest.spyOn(locationService, 'push');
jest.spyOn(backendSrv, 'getDashboardByUid').mockResolvedValue({
dashboard: { templating: { list: [] }, title: 'Dashboard Title', uid: 'someUid' },
meta: {},
});
jest.spyOn(backendSrv, 'search').mockResolvedValue([
{
id: 1,
uid: 'someUid',
isStarred: false,
items: [],
title: 'Dashboard Title',
tags: [],
type: DashboardSearchItemType.DashDB,
uri: 'someUri',
url: 'someUrl',
},
]);
setup();
await openModal();
await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i }));
await userEvent.click(screen.getByRole('combobox', { name: /dashboard/i }));
await waitFor(async () => {
await screen.findByLabelText('Select option');
});
await userEvent.click(screen.getByLabelText('Select option'));
await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i }));
await waitFor(async () => {
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});
expect(pushSpy).toBeCalledWith('d/someUid');
});
});
});
});
describe('Error handling', () => {
afterEach(() => {
jest.restoreAllMocks();
});
it('Shows an error if opening a new tab fails', async () => {
jest.spyOn(global, 'open').mockReturnValue(null);
const removeDashboardSpy = jest.spyOn(initDashboard, 'removeDashboardToFetchFromLocalStorage');
setup();
await openModal();
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
await userEvent.click(screen.getByRole('button', { name: /open in new tab/i }));
await waitFor(async () => {
expect(await screen.findByRole('alert')).toBeInTheDocument();
});
expect(removeDashboardSpy).toHaveBeenCalled();
});
it('Shows an error if saving to localStorage fails', async () => {
jest.spyOn(initDashboard, 'setDashboardToFetchFromLocalStorage').mockImplementation(() => {
throw 'SOME ERROR';
});
setup();
await openModal();
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
await userEvent.click(screen.getByRole('button', { name: /open in new tab/i }));
await waitFor(async () => {
expect(await screen.findByRole('alert')).toBeInTheDocument();
});
});
it('Shows an error if fetching dashboard fails', async () => {
jest.spyOn(backendSrv, 'getDashboardByUid').mockRejectedValue('SOME ERROR');
jest.spyOn(backendSrv, 'search').mockResolvedValue([
{
id: 1,
uid: 'someUid',
isStarred: false,
items: [],
title: 'Dashboard Title',
tags: [],
type: DashboardSearchItemType.DashDB,
uri: 'someUri',
url: 'someUrl',
},
]);
setup();
await openModal();
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i }));
await userEvent.click(screen.getByRole('combobox', { name: /dashboard/i }));
await waitFor(async () => {
await screen.findByLabelText('Select option');
});
await userEvent.click(screen.getByLabelText('Select option'));
await userEvent.click(screen.getByRole('button', { name: /open in new tab/i }));
await waitFor(async () => {
expect(await screen.findByRole('alert')).toBeInTheDocument();
});
});
it('Shows an error if an unknown error happens', async () => {
jest.spyOn(api, 'setDashboardInLocalStorage').mockRejectedValue('SOME ERROR');
setup();
await openModal();
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
await userEvent.click(screen.getByRole('button', { name: /open in new tab/i }));
await waitFor(async () => {
expect(await screen.findByRole('alert')).toBeInTheDocument();
});
});
});
});