[Chore] Dashboard: Tests for dashboard's add content buttons and menu (#66233)

* add tests for empty dashboard page

* add tests for AddPanelButton

* export default components

* test +Add menu in dashboard page

* use userEvent instead of firing an event on the DOM itself

* fix test to match new analytic events
This commit is contained in:
Polina Boneva
2023-06-08 13:16:08 +03:00
committed by GitHub
parent 6d82962edf
commit 4db6679460
12 changed files with 321 additions and 25 deletions

View File

@@ -10,7 +10,7 @@ export const smokeTestScenario = {
scenario: () => {
// wait for time to be set to account for any layout shift
e2e().contains('2020-01-01 00:00:00 to 2020-01-01 06:00:00').should('be.visible');
e2e.components.PageToolbar.itemButton('Add panel button').click();
e2e.components.PageToolbar.itemButton('Add button').click();
e2e.components.PageToolbar.itemButton('Add new visualization menu item').click();
e2e.components.DataSource.TestData.QueryTab.scenarioSelectContainer()

View File

@@ -12,12 +12,12 @@ e2e.scenario({
// TODO: Try and use e2e.flows.addPanel() instead of block below
try {
e2e.components.PageToolbar.itemButton('Add panel button').should('be.visible');
e2e.components.PageToolbar.itemButton('Add panel button').click();
e2e.components.PageToolbar.itemButton('Add button').should('be.visible');
e2e.components.PageToolbar.itemButton('Add button').click();
} catch (e) {
// Depending on the screen size, the "Add panel" button might be hidden
e2e.components.PageToolbar.item('Show more items').click();
e2e.components.PageToolbar.item('Add panel button').last().click();
e2e.components.PageToolbar.item('Add button').last().click();
}
e2e.pages.AddDashboard.itemButton('Add new visualization menu item').should('be.visible');
e2e.pages.AddDashboard.itemButton('Add new visualization menu item').click();

View File

@@ -144,7 +144,7 @@ export const addDashboard = (config?: Partial<AddDashboardConfig>) => {
e2e.pages.SaveDashboardAsModal.newName().clear().type(title, { force: true });
e2e.pages.SaveDashboardAsModal.save().click();
e2e.flows.assertSuccessNotification();
e2e.pages.AddDashboard.addNewPanel().should('be.visible');
e2e.pages.AddDashboard.itemButton('Create new panel button').should('be.visible');
e2e().logToConsole('Added dashboard with title:', title);

View File

@@ -92,12 +92,12 @@ export const configurePanel = (config: PartialAddPanelConfig | PartialEditPanelC
e2e.components.Panels.Panel.headerItems('Edit').click();
} else {
try {
e2e.components.PageToolbar.itemButton('Add panel button').should('be.visible');
e2e.components.PageToolbar.itemButton('Add panel button').click();
e2e.components.PageToolbar.itemButton('Add button').should('be.visible');
e2e.components.PageToolbar.itemButton('Add button').click();
} catch (e) {
// Depending on the screen size, the "Add panel" button might be hidden
// Depending on the screen size, the "Add" button might be hidden
e2e.components.PageToolbar.item('Show more items').click();
e2e.components.PageToolbar.item('Add panel button').last().click();
e2e.components.PageToolbar.item('Add button').last().click();
}
e2e.pages.AddDashboard.itemButton('Add new visualization menu item').should('be.visible');
e2e.pages.AddDashboard.itemButton('Add new visualization menu item').click();

View File

@@ -0,0 +1,48 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { defaultDashboard } from '@grafana/schema';
import { createDashboardModelFixture } from '../../state/__fixtures__/dashboardFixtures';
import AddPanelButton, { Props } from './AddPanelButton';
jest.mock('./AddPanelMenu', () => ({
...jest.requireActual('./AddPanelMenu'),
__esModule: true,
default: () => <div>Menu</div>,
}));
function setup(options?: Partial<Props>) {
const props = {
dashboard: createDashboardModelFixture(defaultDashboard),
};
const { rerender } = render(<AddPanelButton dashboard={props.dashboard} />);
return rerender;
}
beforeEach(() => {
jest.clearAllMocks();
});
it('renders button', () => {
setup();
expect(screen.getByRole('button', { name: 'Add' })).toBeInTheDocument();
});
it('renders button without menu when menu is not open', () => {
setup();
expect(screen.queryByText('Menu')).not.toBeInTheDocument();
});
it('renders button with menu when menu is open', async () => {
const user = userEvent.setup();
setup();
await user.click(screen.getByRole('button', { name: 'Add' }));
expect(screen.queryByText('Menu')).toBeInTheDocument();
});

View File

@@ -7,13 +7,13 @@ import { Dropdown, Button, useTheme2, Icon } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { DashboardModel } from 'app/features/dashboard/state';
import { AddPanelMenu } from './AddPanelMenu';
import AddPanelMenu from './AddPanelMenu';
interface Props {
export interface Props {
dashboard: DashboardModel;
}
export const AddPanelButton = ({ dashboard }: Props) => {
const AddPanelButton = ({ dashboard }: Props) => {
const styles = getStyles(useTheme2());
const [isMenuOpen, setIsMenuOpen] = useState(false);
@@ -29,7 +29,7 @@ export const AddPanelButton = ({ dashboard }: Props) => {
size="lg"
fill="text"
className={cx(styles.button, styles.buttonIcon, styles.buttonText)}
data-testid={selectors.components.PageToolbar.itemButton('Add panel button')}
data-testid={selectors.components.PageToolbar.itemButton('Add button')}
>
<Trans i18nKey="dashboard.toolbar.add">Add</Trans>
<Icon name={isMenuOpen ? 'angle-up' : 'angle-down'} size="lg" />
@@ -38,6 +38,8 @@ export const AddPanelButton = ({ dashboard }: Props) => {
);
};
export default AddPanelButton;
function getStyles(theme: GrafanaTheme2) {
return {
button: css({

View File

@@ -0,0 +1,138 @@
import { act, fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
import { PluginType } from '@grafana/data';
import { locationService, reportInteraction } from '@grafana/runtime';
import { defaultDashboard } from '@grafana/schema';
import { createDashboardModelFixture } from 'app/features/dashboard/state/__fixtures__/dashboardFixtures';
import {
onCreateNewPanel,
onCreateNewRow,
onAddLibraryPanel,
getCopiedPanelPlugin,
} from 'app/features/dashboard/utils/dashboard';
import AddPanelMenu from './AddPanelMenu';
jest.mock('app/types', () => ({
...jest.requireActual('app/types'),
useDispatch: () => jest.fn(),
useSelector: () => jest.fn(),
}));
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
locationService: {
partial: jest.fn(),
},
reportInteraction: jest.fn(),
config: {},
}));
jest.mock('app/features/dashboard/utils/dashboard', () => ({
onCreateNewPanel: jest.fn(),
onCreateNewRow: jest.fn(),
onAddLibraryPanel: jest.fn(),
getCopiedPanelPlugin: jest.fn(),
}));
function setup() {
const props = {
dashboard: createDashboardModelFixture(defaultDashboard),
};
const { rerender } = render(<AddPanelMenu dashboard={props.dashboard} />);
return rerender;
}
beforeEach(() => {
jest.clearAllMocks();
});
it('renders menu list with correct menu items', () => {
setup();
expect(screen.getByText('visualization', { exact: false })).toBeInTheDocument();
expect(screen.getByText('row', { exact: false })).toBeInTheDocument();
expect(screen.getByText('library', { exact: false })).toBeInTheDocument();
expect(screen.getByText('paste panel', { exact: false })).toBeInTheDocument();
});
it('renders with all buttons enabled except paste a panel', () => {
// getCopiedPanelPluginMock().mockReset();
setup();
expect(screen.getByText('visualization', { exact: false })).not.toBeDisabled();
expect(screen.getByText('row', { exact: false })).not.toBeDisabled();
expect(screen.getByText('library', { exact: false })).not.toBeDisabled();
expect(screen.getByText('paste panel', { exact: false })).toBeDisabled();
});
it('renders with all buttons enabled', () => {
(getCopiedPanelPlugin as jest.Mock).mockReturnValue({
id: 'someid',
name: 'nameofit',
type: PluginType.panel,
info: {
author: {
name: 'author name',
},
description: 'description',
links: [],
logos: {
small: 'small',
large: 'large',
},
updated: 'updated',
version: 'version',
},
module: 'module',
baseUrl: 'url',
sort: 2,
defaults: { gridPos: { w: 200, h: 100 }, title: 'some title' },
});
setup();
expect(screen.getByText('visualization', { exact: false })).not.toBeDisabled();
expect(screen.getByText('row', { exact: false })).not.toBeDisabled();
expect(screen.getByText('library', { exact: false })).not.toBeDisabled();
expect(screen.getByText('paste panel', { exact: false })).not.toBeDisabled();
});
it('creates new visualization when clicked on menu item Visualization', () => {
setup();
act(() => {
fireEvent.click(screen.getByRole('menuitem', { name: 'Visualization' }));
});
expect(reportInteraction).toHaveBeenCalledWith('dashboards_toolbar_add_clicked', { item: 'add_visualization' });
expect(locationService.partial).toHaveBeenCalled();
expect(onCreateNewPanel).toHaveBeenCalled();
});
it('creates new row when clicked on menu item Row', () => {
setup();
act(() => {
fireEvent.click(screen.getByRole('menuitem', { name: 'Row' }));
});
expect(reportInteraction).toHaveBeenCalledWith('dashboards_toolbar_add_clicked', { item: 'add_row' });
expect(locationService.partial).not.toHaveBeenCalled();
expect(onCreateNewRow).toHaveBeenCalled();
});
it('adds a library panel when clicked on menu item Import from library', () => {
setup();
act(() => {
fireEvent.click(screen.getByRole('menuitem', { name: 'Import from library' }));
});
expect(reportInteraction).toHaveBeenCalledWith('dashboards_toolbar_add_clicked', { item: 'import_from_library' });
expect(locationService.partial).not.toHaveBeenCalled();
expect(onAddLibraryPanel).toHaveBeenCalled();
});

View File

@@ -16,11 +16,11 @@ import { useDispatch, useSelector } from 'app/types';
import { setInitialDatasource } from '../../state/reducers';
interface Props {
export interface Props {
dashboard: DashboardModel;
}
export const AddPanelMenu = ({ dashboard }: Props) => {
const AddPanelMenu = ({ dashboard }: Props) => {
const copiedPanelPlugin = useMemo(() => getCopiedPanelPlugin(), []);
const dispatch = useDispatch();
const initialDatasource = useSelector((state) => state.dashboard.initialDatasource);
@@ -29,8 +29,8 @@ export const AddPanelMenu = ({ dashboard }: Props) => {
<Menu>
<Menu.Item
key="add-visualisation"
testId={selectors.pages.AddDashboard.itemButton('Add new visualization menu item')}
label={t('dashboard.add-menu.visualization', 'Visualization')}
testId={selectors.components.PageToolbar.itemButton('Add new visualization menu item')}
onClick={() => {
const id = onCreateNewPanel(dashboard, initialDatasource);
reportInteraction('dashboards_toolbar_add_clicked', { item: 'add_visualization' });
@@ -40,8 +40,8 @@ export const AddPanelMenu = ({ dashboard }: Props) => {
/>
<Menu.Item
key="add-row"
testId={selectors.pages.AddDashboard.itemButton('Add new row menu item')}
label={t('dashboard.add-menu.row', 'Row')}
testId={selectors.components.PageToolbar.itemButton('Add new row menu item')}
onClick={() => {
reportInteraction('dashboards_toolbar_add_clicked', { item: 'add_row' });
onCreateNewRow(dashboard);
@@ -49,8 +49,8 @@ export const AddPanelMenu = ({ dashboard }: Props) => {
/>
<Menu.Item
key="add-panel-lib"
testId={selectors.pages.AddDashboard.itemButton('Add new panel from panel library menu item')}
label={t('dashboard.add-menu.import', 'Import from library')}
testId={selectors.components.PageToolbar.itemButton('Add new panel from panel library menu item')}
onClick={() => {
reportInteraction('dashboards_toolbar_add_clicked', { item: 'import_from_library' });
onAddLibraryPanel(dashboard);
@@ -58,8 +58,8 @@ export const AddPanelMenu = ({ dashboard }: Props) => {
/>
<Menu.Item
key="add-panel-clipboard"
testId={selectors.pages.AddDashboard.itemButton('Add new panel from clipboard menu item')}
label={t('dashboard.add-menu.paste-panel', 'Paste panel')}
testId={selectors.components.PageToolbar.itemButton('Add new panel from clipboard menu item')}
onClick={() => {
reportInteraction('dashboards_toolbar_add_clicked', { item: 'paste_panel' });
onPasteCopiedPanel(dashboard, copiedPanelPlugin);
@@ -69,3 +69,5 @@ export const AddPanelMenu = ({ dashboard }: Props) => {
</Menu>
);
};
export default AddPanelMenu;

View File

@@ -23,7 +23,7 @@ import { appEvents } from 'app/core/core';
import { useBusEvent } from 'app/core/hooks/useBusEvent';
import { t, Trans } from 'app/core/internationalization';
import { setStarred } from 'app/core/reducers/navBarTree';
import { AddPanelButton } from 'app/features/dashboard/components/AddPanelButton/AddPanelButton';
import AddPanelButton from 'app/features/dashboard/components/AddPanelButton/AddPanelButton';
import { SaveDashboardDrawer } from 'app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { DashboardModel } from 'app/features/dashboard/state';

View File

@@ -0,0 +1,103 @@
import { act, fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
import { locationService, reportInteraction } from '@grafana/runtime';
import { defaultDashboard } from '@grafana/schema';
import { createDashboardModelFixture } from '../state/__fixtures__/dashboardFixtures';
import { onCreateNewPanel, onCreateNewRow, onAddLibraryPanel } from '../utils/dashboard';
import DashboardEmpty, { Props } from './DashboardEmpty';
jest.mock('app/types', () => ({
...jest.requireActual('app/types'),
useDispatch: () => jest.fn(),
useSelector: () => jest.fn(),
}));
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
locationService: {
partial: jest.fn(),
},
reportInteraction: jest.fn(),
config: {},
}));
jest.mock('app/features/dashboard/utils/dashboard', () => ({
onCreateNewPanel: jest.fn(),
onCreateNewRow: jest.fn(),
onAddLibraryPanel: jest.fn(),
}));
function setup(options?: Partial<Props>) {
const props = {
dashboard: createDashboardModelFixture(defaultDashboard),
canCreate: options?.canCreate ?? true,
};
const { rerender } = render(<DashboardEmpty dashboard={props.dashboard} canCreate={props.canCreate} />);
return rerender;
}
beforeEach(() => {
jest.clearAllMocks();
});
it('renders page with correct title for an empty dashboard', () => {
setup();
expect(screen.getByText('your new dashboard', { exact: false })).toBeInTheDocument();
});
it('renders with all buttons enabled when canCreate is true', () => {
setup();
expect(screen.getByRole('button', { name: 'Add visualization' })).not.toBeDisabled();
expect(screen.getByRole('button', { name: 'Add row' })).not.toBeDisabled();
expect(screen.getByRole('button', { name: 'Import library panel' })).not.toBeDisabled();
});
it('renders with all buttons disabled when canCreate is false', () => {
setup({ canCreate: false });
expect(screen.getByRole('button', { name: 'Add visualization' })).toBeDisabled();
expect(screen.getByRole('button', { name: 'Add row' })).toBeDisabled();
expect(screen.getByRole('button', { name: 'Import library panel' })).toBeDisabled();
});
it('creates new visualization when clicked Add visualization', () => {
setup();
act(() => {
fireEvent.click(screen.getByRole('button', { name: 'Add visualization' }));
});
expect(reportInteraction).toHaveBeenCalledWith('dashboards_emptydashboard_clicked', { item: 'add_visualization' });
expect(locationService.partial).toHaveBeenCalled();
expect(onCreateNewPanel).toHaveBeenCalled();
});
it('creates new row when clicked Add row', () => {
setup();
act(() => {
fireEvent.click(screen.getByRole('button', { name: 'Add row' }));
});
expect(reportInteraction).toHaveBeenCalledWith('dashboards_emptydashboard_clicked', { item: 'add_row' });
expect(locationService.partial).not.toHaveBeenCalled();
expect(onCreateNewRow).toHaveBeenCalled();
});
it('adds a library panel when clicked Import library panel', () => {
setup();
act(() => {
fireEvent.click(screen.getByRole('button', { name: 'Import library panel' }));
});
expect(reportInteraction).toHaveBeenCalledWith('dashboards_emptydashboard_clicked', { item: 'import_from_library' });
expect(locationService.partial).not.toHaveBeenCalled();
expect(onAddLibraryPanel).toHaveBeenCalled();
});

View File

@@ -2,6 +2,7 @@ import { css, cx } from '@emotion/css';
import React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { locationService, reportInteraction } from '@grafana/runtime';
import { Button, useStyles2 } from '@grafana/ui';
import { H1, H3, P } from '@grafana/ui/src/unstable';
@@ -17,7 +18,7 @@ export interface Props {
canCreate: boolean;
}
export const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
const styles = useStyles2(getStyles);
const dispatch = useDispatch();
const initialDatasource = useSelector((state) => state.dashboard.initialDatasource);
@@ -44,7 +45,7 @@ export const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
<Button
size="lg"
icon="plus"
aria-label="Add new panel"
data-testid={selectors.pages.AddDashboard.itemButton('Create new panel button')}
onClick={() => {
const id = onCreateNewPanel(dashboard, initialDatasource);
reportInteraction('dashboards_emptydashboard_clicked', { item: 'add_visualization' });
@@ -73,7 +74,7 @@ export const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
<Button
icon="plus"
fill="outline"
aria-label="Add new row"
data-testid={selectors.pages.AddDashboard.itemButton('Create new row button')}
onClick={() => {
reportInteraction('dashboards_emptydashboard_clicked', { item: 'add_row' });
onCreateNewRow(dashboard);
@@ -99,7 +100,7 @@ export const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
<Button
icon="plus"
fill="outline"
aria-label="Add new panel from panel library"
data-testid={selectors.pages.AddDashboard.itemButton('Add a panel from the panel library button')}
onClick={() => {
reportInteraction('dashboards_emptydashboard_clicked', { item: 'import_from_library' });
onAddLibraryPanel(dashboard);
@@ -115,6 +116,8 @@ export const DashboardEmpty = ({ dashboard, canCreate }: Props) => {
);
};
export default DashboardEmpty;
function getStyles(theme: GrafanaTheme2) {
return {
wrapper: css({

View File

@@ -14,7 +14,7 @@ import { DashboardRow } from '../components/DashboardRow';
import { DashboardModel, PanelModel } from '../state';
import { GridPos } from '../state/PanelModel';
import { DashboardEmpty } from './DashboardEmpty';
import DashboardEmpty from './DashboardEmpty';
import { DashboardPanel } from './DashboardPanel';
export interface Props {