From 824a562b03595fe02e7d140d600f4c94c0b5a8d5 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Tue, 22 Nov 2022 16:48:07 +0000 Subject: [PATCH] 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 --- pkg/services/navtree/navtreeimpl/navtree.go | 10 +++--- .../core/components/AppChrome/NavToolbar.tsx | 2 +- .../core/components/Breadcrumbs/utils.test.ts | 14 ++++---- .../app/core/components/Breadcrumbs/utils.ts | 4 +-- public/app/core/components/NavBar/NavBar.tsx | 2 +- .../app/core/components/Page/usePageTitle.ts | 36 ++++++------------- .../app/core/components/PageNew/Page.test.tsx | 8 +++-- .../app/core/navigation/GrafanaRoute.test.tsx | 7 +++- public/app/core/services/context_srv.ts | 2 +- .../features/alerting/AlertRuleList.test.tsx | 11 ++++-- .../features/api-keys/ApiKeysPage.test.tsx | 11 ++++-- .../AnnotationsSettings.test.tsx | 11 ++++-- .../GeneralSettings.test.tsx | 11 ++++-- .../DashboardSettings/LinksSettings.test.tsx | 11 ++++-- .../VersionsSettings.test.tsx | 11 ++++-- .../containers/DashboardPage.test.tsx | 9 +++-- .../features/invites/SignupInvited.test.tsx | 9 ++++- .../app/features/org/OrgDetailsPage.test.tsx | 5 +-- .../playlist/PlaylistEditPage.test.tsx | 8 ++++- .../playlist/PlaylistNewPage.test.tsx | 12 +++++-- .../features/playlist/PlaylistPage.test.tsx | 10 +++++- .../profile/ChangePasswordPage.test.tsx | 9 ++++- .../profile/UserProfileEditPage.test.tsx | 11 ++++-- .../scenes/components/NestedScene.test.tsx | 10 +++++- .../layout/SceneGridLayout.test.tsx | 13 +++++-- .../ServiceAccountCreatePage.test.tsx | 11 +++++- .../ServiceAccountPage.test.tsx | 10 +++++- .../ServiceAccountsListPage.test.tsx | 12 +++++-- public/app/features/teams/CreateTeam.test.tsx | 10 +++++- public/app/features/teams/TeamList.test.tsx | 9 ++++- 30 files changed, 216 insertions(+), 83 deletions(-) diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index 73d601b6ba3..16f0a5e231d 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -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) { diff --git a/public/app/core/components/AppChrome/NavToolbar.tsx b/public/app/core/components/AppChrome/NavToolbar.tsx index 12c4c46d481..59cc55b552e 100644 --- a/public/app/core/components/AppChrome/NavToolbar.tsx +++ b/public/app/core/components/AppChrome/NavToolbar.tsx @@ -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 (
diff --git a/public/app/core/components/Breadcrumbs/utils.test.ts b/public/app/core/components/Breadcrumbs/utils.test.ts index c84ca5d0c6d..c696d65a4d3 100644 --- a/public/app/core/components/Breadcrumbs/utils.test.ts +++ b/public/app/core/components/Breadcrumbs/utils.test.ts @@ -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' }, diff --git a/public/app/core/components/Breadcrumbs/utils.ts b/public/app/core/components/Breadcrumbs/utils.ts index c41022f7f26..cbf68fefb50 100644 --- a/public/app/core/components/Breadcrumbs/utils.ts +++ b/public/app/core/components/Breadcrumbs/utils.ts @@ -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 { diff --git a/public/app/core/components/NavBar/NavBar.tsx b/public/app/core/components/NavBar/NavBar.tsx index e1765c71d61..9fc506b3ef0 100644 --- a/public/app/core/components/NavBar/NavBar.tsx +++ b/public/app/core/components/NavBar/NavBar.tsx @@ -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) diff --git a/public/app/core/components/Page/usePageTitle.ts b/public/app/core/components/Page/usePageTitle.ts index 730c696fc27..7a9e0e025a5 100644 --- a/public/app/core/components/Page/usePageTitle.ts +++ b/public/app/core/components/Page/usePageTitle.ts @@ -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]); } diff --git a/public/app/core/components/PageNew/Page.test.tsx b/public/app/core/components/PageNew/Page.test.tsx index 4ea9862923b..66556318fa1 100644 --- a/public/app/core/components/PageNew/Page.test.tsx +++ b/public/app/core/components/PageNew/Page.test.tsx @@ -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) => { 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 () => { diff --git a/public/app/core/navigation/GrafanaRoute.test.tsx b/public/app/core/navigation/GrafanaRoute.test.tsx index bf6efb99c83..396fe7e802a 100644 --- a/public/app/core/navigation/GrafanaRoute.test.tsx +++ b/public/app/core/navigation/GrafanaRoute.test.tsx @@ -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) { + 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) { render( - + + + ); diff --git a/public/app/core/services/context_srv.ts b/public/app/core/services/context_srv.ts index fe1c8dc41b1..97d2564a3e6 100644 --- a/public/app/core/services/context_srv.ts +++ b/public/app/core/services/context_srv.ts @@ -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(); diff --git a/public/app/features/alerting/AlertRuleList.test.tsx b/public/app/features/alerting/AlertRuleList.test.tsx index c146cdb322d..2589b5f4cd6 100644 --- a/public/app/features/alerting/AlertRuleList.test.tsx +++ b/public/app/features/alerting/AlertRuleList.test.tsx @@ -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(); + const { rerender } = render( + + + + ); return { - rerender, + rerender: (element: JSX.Element) => rerender({element}), }; }; diff --git a/public/app/features/api-keys/ApiKeysPage.test.tsx b/public/app/features/api-keys/ApiKeysPage.test.tsx index fd61e6683be..f1bcc796342 100644 --- a/public/app/features/api-keys/ApiKeysPage.test.tsx +++ b/public/app/features/api-keys/ApiKeysPage.test.tsx @@ -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) => { + const store = configureStore(); const loadApiKeysMock = jest.fn(); const deleteApiKeyMock = jest.fn(); const migrateApiKeyMock = jest.fn(); @@ -54,9 +57,13 @@ const setup = (propOverrides: Partial) => { Object.assign(props, propOverrides); - const { rerender } = render(); + const { rerender } = render( + + + + ); return { - rerender, + rerender: (element: JSX.Element) => rerender({element}), props, loadApiKeysMock, setSearchQueryMock, diff --git a/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.test.tsx b/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.test.tsx index a510c4b0eb8..3d68416cfa7 100644 --- a/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.test.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.test.tsx @@ -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( - - - + + + + + ); } diff --git a/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.test.tsx b/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.test.tsx index e8c4aba49d1..227765f674d 100644 --- a/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.test.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/GeneralSettings.test.tsx @@ -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) => { + const store = configureStore(); const defaults: Props = { dashboard: new DashboardModel( { @@ -49,9 +52,11 @@ const setupTestContext = (options: Partial) => { const { rerender } = render( - - - + + + + + ); diff --git a/public/app/features/dashboard/components/DashboardSettings/LinksSettings.test.tsx b/public/app/features/dashboard/components/DashboardSettings/LinksSettings.test.tsx index f3c1a51b72c..c6716812b13 100644 --- a/public/app/features/dashboard/components/DashboardSettings/LinksSettings.test.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/LinksSettings.test.tsx @@ -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( - - - + + + + + ); } diff --git a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.test.tsx b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.test.tsx index 2ab45f6268d..9de7e2b0ff1 100644 --- a/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.test.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/VersionsSettings.test.tsx @@ -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( - - - + + + + + ); } diff --git a/public/app/features/dashboard/containers/DashboardPage.test.tsx b/public/app/features/dashboard/containers/DashboardPage.test.tsx index be37870e6a3..5dd77df63b7 100644 --- a/public/app/features/dashboard/containers/DashboardPage.test.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.test.tsx @@ -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) => { - 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, diff --git a/public/app/features/invites/SignupInvited.test.tsx b/public/app/features/invites/SignupInvited.test.tsx index d16170371e9..693ff41b0e2 100644 --- a/public/app/features/invites/SignupInvited.test.tsx +++ b/public/app/features/invites/SignupInvited.test.tsx @@ -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(); + render( + + + + ); await waitFor(() => expect(getSpy).toHaveBeenCalled()); expect(getSpy).toHaveBeenCalledTimes(1); diff --git a/public/app/features/org/OrgDetailsPage.test.tsx b/public/app/features/org/OrgDetailsPage.test.tsx index 018e589d471..50c086d81e9 100644 --- a/public/app/features/org/OrgDetailsPage.test.tsx +++ b/public/app/features/org/OrgDetailsPage.test.tsx @@ -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( - + ); diff --git a/public/app/features/playlist/PlaylistEditPage.test.tsx b/public/app/features/playlist/PlaylistEditPage.test.tsx index d1894a85839..a64bab76278 100644 --- a/public/app/features/playlist/PlaylistEditPage.test.tsx +++ b/public/app/features/playlist/PlaylistEditPage.test.tsx @@ -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 = {}) { 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 uid: 'foo', }); const { rerender } = render( - + + + ); await waitFor(() => expect(getMock).toHaveBeenCalledTimes(1)); diff --git a/public/app/features/playlist/PlaylistNewPage.test.tsx b/public/app/features/playlist/PlaylistNewPage.test.tsx index 704dd9841fb..69cabd81048 100644 --- a/public/app/features/playlist/PlaylistNewPage.test.tsx +++ b/public/app/features/playlist/PlaylistNewPage.test.tsx @@ -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 = {}) { + const store = configureStore(); jest.clearAllMocks(); const playlist = { name, items, interval } as unknown as Playlist; const backendSrvMock = jest.spyOn(backendSrv, 'post'); - const { rerender } = render(); + const { rerender } = render( + + + + ); return { playlist, rerender, backendSrvMock }; } diff --git a/public/app/features/playlist/PlaylistPage.test.tsx b/public/app/features/playlist/PlaylistPage.test.tsx index 39b32744c00..70f1b6924b0 100644 --- a/public/app/features/playlist/PlaylistPage.test.tsx +++ b/public/app/features/playlist/PlaylistPage.test.tsx @@ -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(); + const store = configureStore(); + return render( + + + + ); } describe('PlaylistPage', () => { diff --git a/public/app/features/profile/ChangePasswordPage.test.tsx b/public/app/features/profile/ChangePasswordPage.test.tsx index 41cca32c284..dcef679b234 100644 --- a/public/app/features/profile/ChangePasswordPage.test.tsx +++ b/public/app/features/profile/ChangePasswordPage.test.tsx @@ -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 = {}) { + const store = configureStore(); jest.clearAllMocks(); jest.spyOn(backendSrv, 'get').mockResolvedValue({ id: 1, @@ -38,7 +41,11 @@ async function getTestContext(overrides: Partial = {}) { }); const props = { ...defaultProps, ...overrides }; - const { rerender } = render(); + const { rerender } = render( + + + + ); await waitFor(() => expect(props.loadUser).toHaveBeenCalledTimes(1)); diff --git a/public/app/features/profile/UserProfileEditPage.test.tsx b/public/app/features/profile/UserProfileEditPage.test.tsx index 19761e364a3..31ecb2d0fad 100644 --- a/public/app/features/profile/UserProfileEditPage.test.tsx +++ b/public/app/features/profile/UserProfileEditPage.test.tsx @@ -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 = {}) { + const store = configureStore(); jest.clearAllMocks(); const putSpy = jest.spyOn(backendSrv, 'put'); const getSpy = jest @@ -105,9 +108,11 @@ async function getTestContext(overrides: Partial = {}) { const props = { ...defaultProps, ...overrides }; const { rerender } = render( - - - + + + + + ); await waitFor(() => expect(props.initUserProfilePage).toHaveBeenCalledTimes(1)); diff --git a/public/app/features/scenes/components/NestedScene.test.tsx b/public/app/features/scenes/components/NestedScene.test.tsx index 0b9d0b16355..06d61eeacc3 100644 --- a/public/app/features/scenes/components/NestedScene.test.tsx +++ b/public/app/features/scenes/components/NestedScene.test.tsx @@ -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(); + render( + + + + ); } describe('NestedScene', () => { diff --git a/public/app/features/scenes/components/layout/SceneGridLayout.test.tsx b/public/app/features/scenes/components/layout/SceneGridLayout.test.tsx index 07c1e585d25..677f1a8a6d0 100644 --- a/public/app/features/scenes/components/layout/SceneGridLayout.test.tsx +++ b/public/app/features/scenes/components/layout/SceneGridLayout.test.tsx @@ -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 { }; } +function renderWithProvider(element: JSX.Element) { + const store = configureStore(); + return render({element}); +} + describe('SceneGridLayout', () => { describe('rendering', () => { it('should render all grid children', async () => { @@ -34,7 +41,7 @@ describe('SceneGridLayout', () => { }), }); - render(); + renderWithProvider(); expect(screen.queryAllByTestId('test-object')).toHaveLength(2); }); @@ -57,7 +64,7 @@ describe('SceneGridLayout', () => { }), }); - render(); + renderWithProvider(); expect(screen.queryAllByTestId('test-object')).toHaveLength(2); }); @@ -80,7 +87,7 @@ describe('SceneGridLayout', () => { }), }); - render(); + renderWithProvider(); expect(screen.queryAllByTestId('test-object')).toHaveLength(3); }); diff --git a/public/app/features/serviceaccounts/ServiceAccountCreatePage.test.tsx b/public/app/features/serviceaccounts/ServiceAccountCreatePage.test.tsx index 0eca4499524..e9dfe18c306 100644 --- a/public/app/features/serviceaccounts/ServiceAccountCreatePage.test.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountCreatePage.test.tsx @@ -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) => { + const store = configureStore(); const props: Props = { navModel: { main: { @@ -54,7 +59,11 @@ const setup = (propOverrides: Partial) => { Object.assign(props, propOverrides); - render(); + render( + + + + ); }; describe('ServiceAccountCreatePage tests', () => { diff --git a/public/app/features/serviceaccounts/ServiceAccountPage.test.tsx b/public/app/features/serviceaccounts/ServiceAccountPage.test.tsx index c70c984d064..9a3e5800c70 100644 --- a/public/app/features/serviceaccounts/ServiceAccountPage.test.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountPage.test.tsx @@ -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) => { + const store = configureStore(); const createServiceAccountTokenMock = jest.fn(); const deleteServiceAccountMock = jest.fn(); const deleteServiceAccountTokenMock = jest.fn(); @@ -49,7 +53,11 @@ const setup = (propOverrides: Partial) => { Object.assign(props, propOverrides); - const { rerender } = render(); + const { rerender } = render( + + + + ); return { rerender, props, diff --git a/public/app/features/serviceaccounts/ServiceAccountsListPage.test.tsx b/public/app/features/serviceaccounts/ServiceAccountsListPage.test.tsx index 632c9126c8e..4e4d74ec145 100644 --- a/public/app/features/serviceaccounts/ServiceAccountsListPage.test.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountsListPage.test.tsx @@ -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) => { + const store = configureStore(); const changeQueryMock = jest.fn(); const fetchACOptionsMock = jest.fn(); const fetchServiceAccountsMock = jest.fn(); @@ -51,9 +55,13 @@ const setup = (propOverrides: Partial) => { Object.assign(props, propOverrides); - const { rerender } = render(); + const { rerender } = render( + + + + ); return { - rerender, + rerender: (element: JSX.Element) => rerender({element}), props, changeQueryMock, fetchACOptionsMock, diff --git a/public/app/features/teams/CreateTeam.test.tsx b/public/app/features/teams/CreateTeam.test.tsx index 7ab05634da9..3cfdbb310ee 100644 --- a/public/app/features/teams/CreateTeam.test.tsx +++ b/public/app/features/teams/CreateTeam.test.tsx @@ -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(); + const store = configureStore(); + return render( + + + + ); }; describe('Create team', () => { diff --git a/public/app/features/teams/TeamList.test.tsx b/public/app/features/teams/TeamList.test.tsx index c368f2ae02f..5adaa2a8722 100644 --- a/public/app/features/teams/TeamList.test.tsx +++ b/public/app/features/teams/TeamList.test.tsx @@ -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(); + render( + + + + ); }; describe('TeamList', () => {