Navigation: share logic between buildBreadcrumbs and usePageTitle (#58819)

* simplify usePageTitle logic a bit

* use buildBreadcrumbs logic in usePageTitle

* always add home item to navTree, fix some tests

* fix remaining unit tests
This commit is contained in:
Ashley Harrison 2022-11-22 16:48:07 +00:00 committed by GitHub
parent 26a7423151
commit 824a562b03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 216 additions and 83 deletions

View File

@ -73,9 +73,7 @@ func (s *ServiceImpl) GetNavTree(c *models.ReqContext, hasEditPerm bool, prefs *
hasAccess := ac.HasAccess(s.accessControl, c)
treeRoot := &navtree.NavTreeRoot{}
if s.features.IsEnabled(featuremgmt.FlagTopnav) {
treeRoot.AddSection(s.getHomeNode(c, prefs))
}
treeRoot.AddSection(s.getHomeNode(c, prefs))
if hasAccess(ac.ReqSignedIn, ac.EvalPermission(dashboards.ActionDashboardsRead)) {
starredItemsLinks, err := s.buildStarredItemsNavLinks(c)
@ -224,7 +222,7 @@ func (s *ServiceImpl) getHomeNode(c *models.ReqContext, prefs *pref.Preference)
}
}
return &navtree.NavLink{
homeNode := &navtree.NavLink{
Text: "Home",
Id: "home",
Url: homeUrl,
@ -232,6 +230,10 @@ func (s *ServiceImpl) getHomeNode(c *models.ReqContext, prefs *pref.Preference)
Section: navtree.NavSectionCore,
SortWeight: navtree.WeightHome,
}
if !s.features.IsEnabled(featuremgmt.FlagTopnav) {
homeNode.HideFromMenu = true
}
return homeNode
}
func (s *ServiceImpl) addHelpLinks(treeRoot *navtree.NavTreeRoot, c *models.ReqContext) {

View File

@ -33,7 +33,7 @@ export function NavToolbar({
}: Props) {
const homeNav = useSelector((state) => state.navIndex)[HOME_NAV_ID];
const styles = useStyles2(getStyles);
const breadcrumbs = buildBreadcrumbs(homeNav, sectionNav, pageNav);
const breadcrumbs = buildBreadcrumbs(sectionNav, pageNav, homeNav);
return (
<div className={styles.pageToolbar}>

View File

@ -15,7 +15,7 @@ describe('breadcrumb utils', () => {
text: 'My section',
url: '/my-section',
};
expect(buildBreadcrumbs(mockHomeNav, sectionNav)).toEqual([{ text: 'My section', href: '/my-section' }]);
expect(buildBreadcrumbs(sectionNav)).toEqual([{ text: 'My section', href: '/my-section' }]);
});
it('includes breadcrumbs for the page nav', () => {
@ -28,7 +28,7 @@ describe('breadcrumb utils', () => {
text: 'My page',
url: '/my-page',
};
expect(buildBreadcrumbs(mockHomeNav, sectionNav, pageNav)).toEqual([
expect(buildBreadcrumbs(sectionNav, pageNav)).toEqual([
{ text: 'My section', href: '/my-section' },
{ text: 'My page', href: '/my-page' },
]);
@ -43,7 +43,7 @@ describe('breadcrumb utils', () => {
url: '/my-parent-section',
},
};
expect(buildBreadcrumbs(mockHomeNav, sectionNav)).toEqual([
expect(buildBreadcrumbs(sectionNav)).toEqual([
{ text: 'My parent section', href: '/my-parent-section' },
{ text: 'My section', href: '/my-section' },
]);
@ -66,7 +66,7 @@ describe('breadcrumb utils', () => {
url: '/my-parent-section',
},
};
expect(buildBreadcrumbs(mockHomeNav, sectionNav, pageNav)).toEqual([
expect(buildBreadcrumbs(sectionNav, pageNav)).toEqual([
{ text: 'My parent section', href: '/my-parent-section' },
{ text: 'My section', href: '/my-section' },
{ text: 'My parent page', href: '/my-parent-page' },
@ -91,7 +91,7 @@ describe('breadcrumb utils', () => {
url: '/my-parent-section',
},
};
expect(buildBreadcrumbs(mockHomeNav, sectionNav, pageNav)).toEqual([
expect(buildBreadcrumbs(sectionNav, pageNav, mockHomeNav)).toEqual([
{ text: 'Home', href: '/home' },
{ text: 'My page', href: '/my-page' },
]);
@ -114,7 +114,7 @@ describe('breadcrumb utils', () => {
url: '/my-parent-section',
},
};
expect(buildBreadcrumbs(mockHomeNav, sectionNav, pageNav)).toEqual([
expect(buildBreadcrumbs(sectionNav, pageNav, mockHomeNav)).toEqual([
{ text: 'Home', href: '/home?orgId=1' },
{ text: 'My page', href: '/my-page' },
]);
@ -137,7 +137,7 @@ describe('breadcrumb utils', () => {
url: '/my-parent-section',
},
};
expect(buildBreadcrumbs(mockHomeNav, sectionNav, pageNav)).toEqual([
expect(buildBreadcrumbs(sectionNav, pageNav, mockHomeNav)).toEqual([
{ text: 'My parent section', href: '/my-parent-section' },
{ text: 'My section', href: '/my-section' },
{ text: 'My parent page', href: '/home?orgId=1&editview=settings' },

View File

@ -4,7 +4,7 @@ import { getNavTitle } from '../NavBar/navBarItem-translations';
import { Breadcrumb } from './types';
export function buildBreadcrumbs(homeNav: NavModelItem, sectionNav: NavModelItem, pageNav?: NavModelItem) {
export function buildBreadcrumbs(sectionNav: NavModelItem, pageNav?: NavModelItem, homeNav?: NavModelItem) {
const crumbs: Breadcrumb[] = [];
let foundHome = false;
@ -17,7 +17,7 @@ export function buildBreadcrumbs(homeNav: NavModelItem, sectionNav: NavModelItem
urlToMatch += `?editview=${urlSearchParams.get('editview')}`;
}
if (!foundHome && !node.hideFromBreadcrumbs) {
if (urlToMatch === homeNav.url) {
if (homeNav && urlToMatch === homeNav.url) {
crumbs.unshift({ text: getNavTitle(homeNav.id) ?? homeNav.text, href: node.url ?? '' });
foundHome = true;
} else {

View File

@ -61,7 +61,7 @@ export const NavBar = React.memo(() => {
menuOpen
);
const navTree = cloneDeep(navBarTree);
const navTree = cloneDeep(navBarTree).filter((item) => item.hideFromMenu !== true);
const coreItems = navTree
.filter((item) => item.section === NavSection.Core)

View File

@ -1,37 +1,23 @@
import { useEffect } from 'react';
import { NavModel, NavModelItem } from '@grafana/data';
import { HOME_NAV_ID } from 'app/core/reducers/navModel';
import { useSelector } from 'app/types';
import { Branding } from '../Branding/Branding';
import { buildBreadcrumbs } from '../Breadcrumbs/utils';
export function usePageTitle(navModel?: NavModel, pageNav?: NavModelItem) {
const homeNav = useSelector((state) => state.navIndex)[HOME_NAV_ID];
useEffect(() => {
const parts: string[] = [];
if (pageNav) {
if (pageNav.children) {
const activePage = pageNav.children.find((x) => x.active);
if (activePage) {
addTitleSegment(parts, activePage);
}
}
addTitleSegment(parts, pageNav);
}
const sectionNav = (navModel?.node !== navModel?.main ? navModel?.node : navModel?.main) ?? { text: 'Grafana' };
const parts: string[] = buildBreadcrumbs(sectionNav, pageNav, homeNav)
.map((crumb) => crumb.text)
.reverse();
if (navModel) {
if (navModel.node !== navModel.main) {
addTitleSegment(parts, navModel.node);
}
addTitleSegment(parts, navModel.main);
}
parts.push(Branding.AppTitle);
// Override `Home` with the custom brand title
parts[parts.length - 1] = Branding.AppTitle;
document.title = parts.join(' - ');
}, [navModel, pageNav]);
}
function addTitleSegment(parts: string[], node: NavModelItem) {
if (!node.hideFromBreadcrumbs) {
parts.push(node.text);
}
}, [homeNav, navModel, pageNav]);
}

View File

@ -6,6 +6,7 @@ import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
import { NavModelItem } from '@grafana/data';
import { config } from '@grafana/runtime';
import { GrafanaContext } from 'app/core/context/GrafanaContext';
import { HOME_NAV_ID } from 'app/core/reducers/navModel';
import { configureStore } from 'app/store/configureStore';
import { PageProps } from '../Page/types';
@ -19,9 +20,12 @@ const pageNav: NavModelItem = {
{ text: 'pageNav child2', url: '2' },
],
};
const setup = (props: Partial<PageProps>) => {
config.bootData.navTree = [
{
id: HOME_NAV_ID,
text: 'Home',
},
{
text: 'Section name',
id: 'section',
@ -92,7 +96,7 @@ describe('Render', () => {
it('should update document title', async () => {
setup({ navId: 'child1', pageNav });
expect(document.title).toBe('pageNav child1 - pageNav title - Child1 - Section name - Grafana');
expect(document.title).toBe('pageNav title - Child1 - Section name - Grafana');
});
it('should not include hideFromBreadcrumb nodes in title', async () => {

View File

@ -1,16 +1,19 @@
import { render, screen } from '@testing-library/react';
import React, { ComponentType } from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
import { setEchoSrv } from '@grafana/runtime';
import { configureStore } from '../../store/configureStore';
import { GrafanaContext } from '../context/GrafanaContext';
import { Echo } from '../services/echo/Echo';
import { GrafanaRoute, Props } from './GrafanaRoute';
function setup(overrides: Partial<Props>) {
const store = configureStore();
const props: Props = {
location: { search: '?query=hello&test=asd' } as any,
history: {} as any,
@ -25,7 +28,9 @@ function setup(overrides: Partial<Props>) {
render(
<BrowserRouter>
<GrafanaContext.Provider value={getGrafanaContextMock()}>
<GrafanaRoute {...props} />
<Provider store={store}>
<GrafanaRoute {...props} />
</Provider>
</GrafanaContext.Provider>
</BrowserRouter>
);

View File

@ -71,7 +71,7 @@ export class ContextSrv {
constructor() {
if (!config.bootData) {
config.bootData = { user: {}, settings: {} } as any;
config.bootData = { user: {}, settings: {}, navTree: [] } as any;
}
this.user = new User();

View File

@ -1,6 +1,7 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';
import { openMenu } from 'react-select-event';
import { mockToolkitActionCreator } from 'test/core/redux/mocks';
@ -8,6 +9,7 @@ import { locationService } from '@grafana/runtime';
import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
import appEvents from '../../core/app_events';
import { configureStore } from '../../store/configureStore';
import { ShowModalReactEvent } from '../../types/events';
import { AlertHowToModal } from './AlertHowToModal';
@ -29,15 +31,20 @@ const defaultProps: Props = {
};
const setup = (propOverrides?: object) => {
const store = configureStore();
const props: Props = {
...defaultProps,
...propOverrides,
};
const { rerender } = render(<AlertRuleListUnconnected {...props} />);
const { rerender } = render(
<Provider store={store}>
<AlertRuleListUnconnected {...props} />
</Provider>
);
return {
rerender,
rerender: (element: JSX.Element) => rerender(<Provider store={store}>{element}</Provider>),
};
};

View File

@ -1,12 +1,14 @@
import { render, screen, within } from '@testing-library/react';
import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';
import { selectors } from '@grafana/e2e-selectors';
import { ApiKey, OrgRole } from 'app/types';
import { mockToolkitActionCreator } from '../../../test/core/redux/mocks';
import { silenceConsoleOutput } from '../../../test/core/utils/silenceConsoleOutput';
import { configureStore } from '../../store/configureStore';
import { ApiKeysPageUnconnected, Props } from './ApiKeysPage';
import { getMultipleMockKeys } from './__mocks__/apiKeysMock';
@ -22,6 +24,7 @@ jest.mock('app/core/core', () => {
});
const setup = (propOverrides: Partial<Props>) => {
const store = configureStore();
const loadApiKeysMock = jest.fn();
const deleteApiKeyMock = jest.fn();
const migrateApiKeyMock = jest.fn();
@ -54,9 +57,13 @@ const setup = (propOverrides: Partial<Props>) => {
Object.assign(props, propOverrides);
const { rerender } = render(<ApiKeysPageUnconnected {...props} />);
const { rerender } = render(
<Provider store={store}>
<ApiKeysPageUnconnected {...props} />
</Provider>
);
return {
rerender,
rerender: (element: JSX.Element) => rerender(<Provider store={store}>{element}</Provider>),
props,
loadApiKeysMock,
setSearchQueryMock,

View File

@ -2,6 +2,7 @@ import { within } from '@testing-library/dom';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
@ -10,11 +11,13 @@ import { locationService, setAngularLoader, setDataSourceSrv } from '@grafana/ru
import { GrafanaContext } from 'app/core/context/GrafanaContext';
import { mockDataSource, MockDataSourceSrv } from 'app/features/alerting/unified/mocks';
import { configureStore } from '../../../../store/configureStore';
import { DashboardModel } from '../../state/DashboardModel';
import { AnnotationsSettings } from './AnnotationsSettings';
function setup(dashboard: DashboardModel, editIndex?: number) {
const store = configureStore();
const sectionNav = {
main: { text: 'Dashboard' },
node: {
@ -24,9 +27,11 @@ function setup(dashboard: DashboardModel, editIndex?: number) {
return render(
<GrafanaContext.Provider value={getGrafanaContextMock()}>
<BrowserRouter>
<AnnotationsSettings sectionNav={sectionNav} dashboard={dashboard} editIndex={editIndex} />
</BrowserRouter>
<Provider store={store}>
<BrowserRouter>
<AnnotationsSettings sectionNav={sectionNav} dashboard={dashboard} editIndex={editIndex} />
</BrowserRouter>
</Provider>
</GrafanaContext.Provider>
);
}

View File

@ -1,6 +1,7 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import { selectOptionInTest } from 'test/helpers/selectOptionInTest';
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
@ -10,6 +11,7 @@ import { selectors } from '@grafana/e2e-selectors';
import { BackendSrv, setBackendSrv } from '@grafana/runtime';
import { GrafanaContext } from 'app/core/context/GrafanaContext';
import { configureStore } from '../../../../store/configureStore';
import { DashboardModel } from '../../state';
import { GeneralSettingsUnconnected as GeneralSettings, Props } from './GeneralSettings';
@ -19,6 +21,7 @@ setBackendSrv({
} as unknown as BackendSrv);
const setupTestContext = (options: Partial<Props>) => {
const store = configureStore();
const defaults: Props = {
dashboard: new DashboardModel(
{
@ -49,9 +52,11 @@ const setupTestContext = (options: Partial<Props>) => {
const { rerender } = render(
<GrafanaContext.Provider value={getGrafanaContextMock()}>
<BrowserRouter>
<GeneralSettings {...props} />
</BrowserRouter>
<Provider store={store}>
<BrowserRouter>
<GeneralSettings {...props} />
</BrowserRouter>
</Provider>
</GrafanaContext.Provider>
);

View File

@ -2,6 +2,7 @@ import { within } from '@testing-library/dom';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
@ -9,11 +10,13 @@ import { selectors } from '@grafana/e2e-selectors';
import { locationService } from '@grafana/runtime';
import { GrafanaContext } from 'app/core/context/GrafanaContext';
import { configureStore } from '../../../../store/configureStore';
import { DashboardModel } from '../../state';
import { DashboardSettings } from './DashboardSettings';
function setup(dashboard: DashboardModel) {
const store = configureStore();
const sectionNav = {
main: { text: 'Dashboard' },
node: {
@ -24,9 +27,11 @@ function setup(dashboard: DashboardModel) {
// Need to use DashboardSettings here as it's responsible for fetching the state back from location
return render(
<GrafanaContext.Provider value={getGrafanaContextMock()}>
<Router history={locationService.getHistory()}>
<DashboardSettings editview="links" dashboard={dashboard} sectionNav={sectionNav} pageNav={sectionNav.node} />
</Router>
<Provider store={store}>
<Router history={locationService.getHistory()}>
<DashboardSettings editview="links" dashboard={dashboard} sectionNav={sectionNav} pageNav={sectionNav.node} />
</Router>
</Provider>
</GrafanaContext.Provider>
);
}

View File

@ -2,11 +2,13 @@ import { within } from '@testing-library/dom';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
import { GrafanaContext } from 'app/core/context/GrafanaContext';
import { configureStore } from '../../../../store/configureStore';
import { DashboardModel } from '../../state/DashboardModel';
import { historySrv } from '../VersionHistory/HistorySrv';
@ -27,6 +29,7 @@ const queryByFullText = (text: string) =>
});
function setup() {
const store = configureStore();
const dashboard = new DashboardModel({
id: 74,
version: 11,
@ -43,9 +46,11 @@ function setup() {
return render(
<GrafanaContext.Provider value={getGrafanaContextMock()}>
<BrowserRouter>
<VersionsSettings sectionNav={sectionNav} dashboard={dashboard} />
</BrowserRouter>
<Provider store={store}>
<BrowserRouter>
<VersionsSettings sectionNav={sectionNav} dashboard={dashboard} />
</BrowserRouter>
</Provider>
</GrafanaContext.Provider>
);
}

View File

@ -13,6 +13,7 @@ import { config, locationService, setDataSourceSrv } from '@grafana/runtime';
import { notifyApp } from 'app/core/actions';
import { GrafanaContext } from 'app/core/context/GrafanaContext';
import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
import { HOME_NAV_ID } from 'app/core/reducers/navModel';
import { DashboardInitPhase, DashboardMeta, DashboardRoutes } from 'app/types';
import { configureStore } from '../../../store/configureStore';
@ -105,7 +106,10 @@ function dashboardPageScenario(description: string, scenarioFn: (ctx: ScenarioCo
setupFn = fn;
},
mount: (propOverrides?: Partial<Props>) => {
config.bootData.navTree = [{ text: 'Dashboards', id: 'dashboards' }];
config.bootData.navTree = [
{ text: 'Dashboards', id: 'dashboards' },
{ text: 'Home', id: HOME_NAV_ID },
];
const store = configureStore();
const props: Props = {
@ -114,7 +118,8 @@ function dashboardPageScenario(description: string, scenarioFn: (ctx: ScenarioCo
route: { routeName: DashboardRoutes.Normal } as any,
}),
navIndex: {
dashboards: { text: 'Dashboards' },
dashboards: { text: 'Dashboards', id: 'dashboards', parentItem: { text: 'Home', id: HOME_NAV_ID } },
[HOME_NAV_ID]: { text: 'Home', id: HOME_NAV_ID },
},
initPhase: DashboardInitPhase.NotStarted,
initError: null,

View File

@ -1,10 +1,12 @@
import { render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';
import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps';
import { backendSrv } from '../../core/services/backend_srv';
import { configureStore } from '../../store/configureStore';
import { SignupInvitedPage, Props } from './SignupInvited';
@ -28,6 +30,7 @@ const defaultGet = {
async function setupTestContext({ get = defaultGet }: { get?: typeof defaultGet | null } = {}) {
jest.clearAllMocks();
const store = configureStore();
const getSpy = jest.spyOn(backendSrv, 'get');
getSpy.mockResolvedValue(get);
@ -43,7 +46,11 @@ async function setupTestContext({ get = defaultGet }: { get?: typeof defaultGet
}),
};
render(<SignupInvitedPage {...props} />);
render(
<Provider store={store}>
<SignupInvitedPage {...props} />
</Provider>
);
await waitFor(() => expect(getSpy).toHaveBeenCalled());
expect(getSpy).toHaveBeenCalledTimes(1);

View File

@ -6,9 +6,9 @@ import { mockToolkitActionCreator } from 'test/core/redux/mocks';
import { NavModel } from '@grafana/data';
import { ModalManager } from 'app/core/services/ModalManager';
import { configureStore } from 'app/store/configureStore';
import { backendSrv } from '../../core/services/backend_srv';
import { configureStore } from '../../store/configureStore';
import { Organization } from '../../types';
import { OrgDetailsPage, Props } from './OrgDetailsPage';
@ -37,6 +37,7 @@ jest.mock('@grafana/runtime', () => {
});
const setup = (propOverrides?: object) => {
const store = configureStore();
jest.clearAllMocks();
// needed because SharedPreferences is rendered in the test
jest.spyOn(backendSrv, 'put');
@ -62,7 +63,7 @@ const setup = (propOverrides?: object) => {
Object.assign(props, propOverrides);
render(
<Provider store={configureStore()}>
<Provider store={store}>
<OrgDetailsPage {...props} />
</Provider>
);

View File

@ -1,10 +1,13 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';
import { locationService } from '@grafana/runtime';
import { backendSrv } from 'app/core/services/backend_srv';
import { configureStore } from '../../store/configureStore';
import { PlaylistEditPage } from './PlaylistEditPage';
import { Playlist } from './types';
@ -21,6 +24,7 @@ jest.mock('app/core/components/TagFilter/TagFilter', () => ({
async function getTestContext({ name, interval, items, uid }: Partial<Playlist> = {}) {
jest.clearAllMocks();
const store = configureStore();
const playlist = { name, items, interval, uid } as unknown as Playlist;
const queryParams = {};
const route: any = {};
@ -36,7 +40,9 @@ async function getTestContext({ name, interval, items, uid }: Partial<Playlist>
uid: 'foo',
});
const { rerender } = render(
<PlaylistEditPage queryParams={queryParams} route={route} match={match} location={location} history={history} />
<Provider store={store}>
<PlaylistEditPage queryParams={queryParams} route={route} match={match} location={location} history={history} />
</Provider>
);
await waitFor(() => expect(getMock).toHaveBeenCalledTimes(1));

View File

@ -1,10 +1,13 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';
import { selectors } from '@grafana/e2e-selectors';
import { locationService } from '@grafana/runtime';
import { backendSrv } from 'app/core/services/backend_srv';
import { backendSrv } from '../../core/services/backend_srv';
import { configureStore } from '../../store/configureStore';
import { PlaylistNewPage } from './PlaylistNewPage';
import { Playlist } from './types';
@ -21,11 +24,16 @@ jest.mock('app/core/components/TagFilter/TagFilter', () => ({
}));
function getTestContext({ name, interval, items }: Partial<Playlist> = {}) {
const store = configureStore();
jest.clearAllMocks();
const playlist = { name, items, interval } as unknown as Playlist;
const backendSrvMock = jest.spyOn(backendSrv, 'post');
const { rerender } = render(<PlaylistNewPage />);
const { rerender } = render(
<Provider store={store}>
<PlaylistNewPage />
</Provider>
);
return { playlist, rerender, backendSrvMock };
}

View File

@ -1,8 +1,11 @@
import { render, waitFor } from '@testing-library/react';
import React from 'react';
import { Provider } from 'react-redux';
import { contextSrv } from 'app/core/services/context_srv';
import { configureStore } from '../../store/configureStore';
import { PlaylistPage } from './PlaylistPage';
const fnMock = jest.fn();
@ -21,7 +24,12 @@ jest.mock('app/core/services/context_srv', () => ({
}));
function getTestContext() {
return render(<PlaylistPage />);
const store = configureStore();
return render(
<Provider store={store}>
<PlaylistPage />
</Provider>
);
}
describe('PlaylistPage', () => {

View File

@ -1,10 +1,12 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';
import config from 'app/core/config';
import { backendSrv } from '../../core/services/backend_srv';
import { configureStore } from '../../store/configureStore';
import { Props, ChangePasswordPage } from './ChangePasswordPage';
import { initialUserState } from './state/reducers';
@ -26,6 +28,7 @@ const defaultProps: Props = {
};
async function getTestContext(overrides: Partial<Props> = {}) {
const store = configureStore();
jest.clearAllMocks();
jest.spyOn(backendSrv, 'get').mockResolvedValue({
id: 1,
@ -38,7 +41,11 @@ async function getTestContext(overrides: Partial<Props> = {}) {
});
const props = { ...defaultProps, ...overrides };
const { rerender } = render(<ChangePasswordPage {...props} />);
const { rerender } = render(
<Provider store={store}>
<ChangePasswordPage {...props} />
</Provider>
);
await waitFor(() => expect(props.loadUser).toHaveBeenCalledTimes(1));

View File

@ -2,12 +2,14 @@ import { within } from '@testing-library/dom';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';
import { OrgRole } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import TestProvider from '../../../test/helpers/TestProvider';
import { backendSrv } from '../../core/services/backend_srv';
import { configureStore } from '../../store/configureStore';
import { TeamPermissionLevel } from '../../types';
import { Props, UserProfileEditPage } from './UserProfileEditPage';
@ -96,6 +98,7 @@ function getSelectors() {
}
async function getTestContext(overrides: Partial<Props> = {}) {
const store = configureStore();
jest.clearAllMocks();
const putSpy = jest.spyOn(backendSrv, 'put');
const getSpy = jest
@ -105,9 +108,11 @@ async function getTestContext(overrides: Partial<Props> = {}) {
const props = { ...defaultProps, ...overrides };
const { rerender } = render(
<TestProvider>
<UserProfileEditPage {...props} />
</TestProvider>
<Provider store={store}>
<TestProvider>
<UserProfileEditPage {...props} />
</TestProvider>
</Provider>
);
await waitFor(() => expect(props.initUserProfilePage).toHaveBeenCalledTimes(1));

View File

@ -1,5 +1,8 @@
import { screen, render } from '@testing-library/react';
import React from 'react';
import { Provider } from 'react-redux';
import { configureStore } from '../../../store/configureStore';
import { NestedScene } from './NestedScene';
import { Scene } from './Scene';
@ -7,6 +10,7 @@ import { SceneCanvasText } from './SceneCanvasText';
import { SceneFlexLayout } from './layout/SceneFlexLayout';
function setup() {
const store = configureStore();
const scene = new Scene({
title: 'Hello',
layout: new SceneFlexLayout({
@ -23,7 +27,11 @@ function setup() {
}),
});
render(<scene.Component model={scene} />);
render(
<Provider store={store}>
<scene.Component model={scene} />
</Provider>
);
}
describe('NestedScene', () => {

View File

@ -1,6 +1,8 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
import { Provider } from 'react-redux';
import { configureStore } from '../../../../store/configureStore';
import { SceneObjectBase } from '../../core/SceneObjectBase';
import { SceneComponentProps, SceneLayoutChildState } from '../../core/types';
import { Scene } from '../Scene';
@ -21,6 +23,11 @@ class TestObject extends SceneObjectBase<SceneLayoutChildState> {
};
}
function renderWithProvider(element: JSX.Element) {
const store = configureStore();
return render(<Provider store={store}>{element}</Provider>);
}
describe('SceneGridLayout', () => {
describe('rendering', () => {
it('should render all grid children', async () => {
@ -34,7 +41,7 @@ describe('SceneGridLayout', () => {
}),
});
render(<scene.Component model={scene} />);
renderWithProvider(<scene.Component model={scene} />);
expect(screen.queryAllByTestId('test-object')).toHaveLength(2);
});
@ -57,7 +64,7 @@ describe('SceneGridLayout', () => {
}),
});
render(<scene.Component model={scene} />);
renderWithProvider(<scene.Component model={scene} />);
expect(screen.queryAllByTestId('test-object')).toHaveLength(2);
});
@ -80,7 +87,7 @@ describe('SceneGridLayout', () => {
}),
});
render(<scene.Component model={scene} />);
renderWithProvider(<scene.Component model={scene} />);
expect(screen.queryAllByTestId('test-object')).toHaveLength(3);
});

View File

@ -1,6 +1,9 @@
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';
import { configureStore } from '../../store/configureStore';
import { ServiceAccountCreatePage, Props } from './ServiceAccountCreatePage';
@ -9,6 +12,7 @@ const patchMock = jest.fn().mockResolvedValue({});
const putMock = jest.fn().mockResolvedValue({});
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'),
getBackendSrv: () => ({
post: postMock,
patch: patchMock,
@ -41,6 +45,7 @@ jest.mock('app/core/core', () => ({
}));
const setup = (propOverrides: Partial<Props>) => {
const store = configureStore();
const props: Props = {
navModel: {
main: {
@ -54,7 +59,11 @@ const setup = (propOverrides: Partial<Props>) => {
Object.assign(props, propOverrides);
render(<ServiceAccountCreatePage {...props} />);
render(
<Provider store={store}>
<ServiceAccountCreatePage {...props} />
</Provider>
);
};
describe('ServiceAccountCreatePage tests', () => {

View File

@ -1,9 +1,12 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';
import { ApiKey, OrgRole, ServiceAccountDTO } from 'app/types';
import { configureStore } from '../../store/configureStore';
import { ServiceAccountPageUnconnected, Props } from './ServiceAccountPage';
jest.mock('app/core/core', () => ({
@ -16,6 +19,7 @@ jest.mock('app/core/core', () => ({
}));
const setup = (propOverrides: Partial<Props>) => {
const store = configureStore();
const createServiceAccountTokenMock = jest.fn();
const deleteServiceAccountMock = jest.fn();
const deleteServiceAccountTokenMock = jest.fn();
@ -49,7 +53,11 @@ const setup = (propOverrides: Partial<Props>) => {
Object.assign(props, propOverrides);
const { rerender } = render(<ServiceAccountPageUnconnected {...props} />);
const { rerender } = render(
<Provider store={store}>
<ServiceAccountPageUnconnected {...props} />
</Provider>
);
return {
rerender,
props,

View File

@ -1,9 +1,12 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';
import { OrgRole, ServiceAccountDTO, ServiceAccountStateFilter } from 'app/types';
import { configureStore } from '../../store/configureStore';
import { Props, ServiceAccountsListPageUnconnected } from './ServiceAccountsListPage';
jest.mock('app/core/core', () => ({
@ -15,6 +18,7 @@ jest.mock('app/core/core', () => ({
}));
const setup = (propOverrides: Partial<Props>) => {
const store = configureStore();
const changeQueryMock = jest.fn();
const fetchACOptionsMock = jest.fn();
const fetchServiceAccountsMock = jest.fn();
@ -51,9 +55,13 @@ const setup = (propOverrides: Partial<Props>) => {
Object.assign(props, propOverrides);
const { rerender } = render(<ServiceAccountsListPageUnconnected {...props} />);
const { rerender } = render(
<Provider store={store}>
<ServiceAccountsListPageUnconnected {...props} />
</Provider>
);
return {
rerender,
rerender: (element: JSX.Element) => rerender(<Provider store={store}>{element}</Provider>),
props,
changeQueryMock,
fetchACOptionsMock,

View File

@ -1,9 +1,12 @@
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';
import { BackendSrv, setBackendSrv } from '@grafana/runtime';
import { configureStore } from '../../store/configureStore';
import { CreateTeam } from './CreateTeam';
beforeEach(() => {
@ -32,7 +35,12 @@ setBackendSrv({
} as unknown as BackendSrv);
const setup = () => {
return render(<CreateTeam />);
const store = configureStore();
return render(
<Provider store={store}>
<CreateTeam />
</Provider>
);
};
describe('Create team', () => {

View File

@ -1,9 +1,11 @@
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';
import { contextSrv, User } from 'app/core/services/context_srv';
import { configureStore } from '../../store/configureStore';
import { OrgRole, Team } from '../../types';
import { Props, TeamList } from './TeamList';
@ -15,6 +17,7 @@ jest.mock('app/core/config', () => ({
}));
const setup = (propOverrides?: object) => {
const store = configureStore();
const props: Props = {
teams: [] as Team[],
noTeams: false,
@ -37,7 +40,11 @@ const setup = (propOverrides?: object) => {
contextSrv.user = props.signedInUser;
render(<TeamList {...props} />);
render(
<Provider store={store}>
<TeamList {...props} />
</Provider>
);
};
describe('TeamList', () => {