Files
Ashley Harrison 727a4bd9e4 Navigation: New NavBar designs behind feature toggle (#41045)
* Navigation: Remove plus button behind feature toggle

* Navigation: Add home button behind feature toggle

* Navigation: Move settings/admin to bottom section behind feature toggle

* Navigation: Refactor grafana logo to be a NavBarItem

* Navigation: Create new PluginSection and styling changes to support new sections

* Navigation: Hack to use mobile menu as a mega menu for now

* Navigation: Only render plugin section if there are items

* Navigation: mobile menu is always 100% width if toggle is off

* Navigation: Reset width back to 48 and fix broken css property

* Navigation: Create generic NavBarSection component to reduce repetition

* Navigation: Don't show sublinks for core items

* Navigation: Comments from UX review

* Navigation: Remove mobile menu hack

* Navigation: Unit tests for enrichConfigItems and other minor review comments

* Navigation: Move section logic to backend

* Navigation: Refactor alerting links out into a separate function

* Navigation: More tests for isLinkActive

* Linting...

* Navigation: Create new NavBar component for when feature toggle is enabled
2021-11-02 11:19:18 +00:00

360 lines
9.9 KiB
TypeScript

import { Location } from 'history';
import { NavModelItem } from '@grafana/data';
import { ContextSrv, setContextSrv } from 'app/core/services/context_srv';
import { getConfig, updateConfig } from '../../config';
import { enrichConfigItems, getForcedLoginUrl, isLinkActive, isSearchActive } from './utils';
jest.mock('../../app_events', () => ({
publish: jest.fn(),
}));
describe('getForcedLoginUrl', () => {
it.each`
appSubUrl | url | expected
${''} | ${'/whatever?a=1&b=2'} | ${'/whatever?a=1&b=2&forceLogin=true'}
${'/grafana'} | ${'/whatever?a=1&b=2'} | ${'/grafana/whatever?a=1&b=2&forceLogin=true'}
${'/grafana/test'} | ${'/whatever?a=1&b=2'} | ${'/grafana/test/whatever?a=1&b=2&forceLogin=true'}
${'/grafana'} | ${''} | ${'/grafana?forceLogin=true'}
${'/grafana'} | ${'/whatever'} | ${'/grafana/whatever?forceLogin=true'}
${'/grafana'} | ${'/whatever/'} | ${'/grafana/whatever/?forceLogin=true'}
`(
"when appUrl set to '$appUrl' and appSubUrl set to '$appSubUrl' then result should be '$expected'",
({ appSubUrl, url, expected }) => {
updateConfig({
appSubUrl,
});
const result = getForcedLoginUrl(url);
expect(result).toBe(expected);
}
);
});
describe('enrichConfigItems', () => {
let mockItems: NavModelItem[];
const mockLocation: Location<unknown> = {
hash: '',
pathname: '/',
search: '',
state: '',
};
beforeEach(() => {
mockItems = [
{
id: 'profile',
text: 'Profile',
hideFromMenu: true,
},
{
id: 'help',
text: 'Help',
hideFromMenu: true,
},
];
});
it('does not add a sign in item if a user signed in', () => {
const contextSrv = new ContextSrv();
contextSrv.user.isSignedIn = false;
setContextSrv(contextSrv);
const enrichedConfigItems = enrichConfigItems(mockItems, mockLocation, jest.fn());
const signInNode = enrichedConfigItems.find((item) => item.id === 'signin');
expect(signInNode).toBeDefined();
});
it('adds a sign in item if a user is not signed in', () => {
const contextSrv = new ContextSrv();
contextSrv.user.isSignedIn = true;
setContextSrv(contextSrv);
const enrichedConfigItems = enrichConfigItems(mockItems, mockLocation, jest.fn());
const signInNode = enrichedConfigItems.find((item) => item.id === 'signin');
expect(signInNode).toBeDefined();
});
it('does not add an org switcher to the profile node if there is 1 org', () => {
const contextSrv = new ContextSrv();
contextSrv.user.orgCount = 1;
setContextSrv(contextSrv);
const enrichedConfigItems = enrichConfigItems(mockItems, mockLocation, jest.fn());
const profileNode = enrichedConfigItems.find((item) => item.id === 'profile');
expect(profileNode!.children).toBeUndefined();
});
it('adds an org switcher to the profile node if there is more than 1 org', () => {
const contextSrv = new ContextSrv();
contextSrv.user.orgCount = 2;
setContextSrv(contextSrv);
const enrichedConfigItems = enrichConfigItems(mockItems, mockLocation, jest.fn());
const profileNode = enrichedConfigItems.find((item) => item.id === 'profile');
expect(profileNode!.children).toContainEqual(
expect.objectContaining({
text: 'Switch organization',
})
);
});
it('enhances the help node with extra child links', () => {
const contextSrv = new ContextSrv();
setContextSrv(contextSrv);
const enrichedConfigItems = enrichConfigItems(mockItems, mockLocation, jest.fn());
const helpNode = enrichedConfigItems.find((item) => item.id === 'help');
expect(helpNode!.children).toContainEqual(
expect.objectContaining({
text: 'Documentation',
})
);
expect(helpNode!.children).toContainEqual(
expect.objectContaining({
text: 'Support',
})
);
expect(helpNode!.children).toContainEqual(
expect.objectContaining({
text: 'Community',
})
);
expect(helpNode!.children).toContainEqual(
expect.objectContaining({
text: 'Keyboard shortcuts',
})
);
});
});
describe('isLinkActive', () => {
it('returns true if the link url matches the pathname', () => {
const mockPathName = '/test';
const mockLink: NavModelItem = {
text: 'Test',
url: '/test',
};
expect(isLinkActive(mockPathName, mockLink)).toBe(true);
});
it('returns true if the pathname starts with the link url', () => {
const mockPathName = '/test/edit';
const mockLink: NavModelItem = {
text: 'Test',
url: '/test',
};
expect(isLinkActive(mockPathName, mockLink)).toBe(true);
});
it('returns true if a child link url matches the pathname', () => {
const mockPathName = '/testChild2';
const mockLink: NavModelItem = {
text: 'Test',
url: '/test',
children: [
{
text: 'TestChild',
url: '/testChild',
},
{
text: 'TestChild2',
url: '/testChild2',
},
],
};
expect(isLinkActive(mockPathName, mockLink)).toBe(true);
});
it('returns true if the pathname starts with a child link url', () => {
const mockPathName = '/testChild2/edit';
const mockLink: NavModelItem = {
text: 'Test',
url: '/test',
children: [
{
text: 'TestChild',
url: '/testChild',
},
{
text: 'TestChild2',
url: '/testChild2',
},
],
};
expect(isLinkActive(mockPathName, mockLink)).toBe(true);
});
it('returns true for the alerting link if the pathname is an alert notification', () => {
const mockPathName = '/alerting/notification/foo';
const mockLink: NavModelItem = {
text: 'Test',
url: '/alerting/list',
children: [
{
text: 'TestChild',
url: '/testChild',
},
{
text: 'TestChild2',
url: '/testChild2',
},
],
};
expect(isLinkActive(mockPathName, mockLink)).toBe(true);
});
it('returns false if none of the link urls match the pathname', () => {
const mockPathName = '/somethingWeird';
const mockLink: NavModelItem = {
text: 'Test',
url: '/test',
children: [
{
text: 'TestChild',
url: '/testChild',
},
{
text: 'TestChild2',
url: '/testChild2',
},
],
};
expect(isLinkActive(mockPathName, mockLink)).toBe(false);
});
it('returns false for the base route if the pathname is not an exact match', () => {
const mockPathName = '/foo';
const mockLink: NavModelItem = {
text: 'Test',
url: '/',
children: [
{
text: 'TestChild',
url: '/',
},
{
text: 'TestChild2',
url: '/testChild2',
},
],
};
expect(isLinkActive(mockPathName, mockLink)).toBe(false);
});
describe('when the newNavigation feature toggle is disabled', () => {
beforeEach(() => {
updateConfig({
featureToggles: {
...getConfig().featureToggles,
newNavigation: false,
},
});
});
it('returns true for the base route link if the pathname starts with /d/', () => {
const mockPathName = '/d/foo';
const mockLink: NavModelItem = {
text: 'Test',
url: '/',
children: [
{
text: 'TestChild',
url: '/testChild',
},
{
text: 'TestChild2',
url: '/testChild2',
},
],
};
expect(isLinkActive(mockPathName, mockLink)).toBe(true);
});
it('returns false for the dashboards route if the pathname starts with /d/', () => {
const mockPathName = '/d/foo';
const mockLink: NavModelItem = {
text: 'Test',
url: '/dashboards',
children: [
{
text: 'TestChild',
url: '/testChild1',
},
{
text: 'TestChild2',
url: '/testChild2',
},
],
};
expect(isLinkActive(mockPathName, mockLink)).toBe(false);
});
});
describe('when the newNavigation feature toggle is enabled', () => {
beforeEach(() => {
updateConfig({
featureToggles: {
...getConfig().featureToggles,
newNavigation: true,
},
});
});
it('returns false for the base route if the pathname starts with /d/', () => {
const mockPathName = '/d/foo';
const mockLink: NavModelItem = {
text: 'Test',
url: '/',
children: [
{
text: 'TestChild',
url: '/',
},
{
text: 'TestChild2',
url: '/testChild2',
},
],
};
expect(isLinkActive(mockPathName, mockLink)).toBe(false);
});
it('returns true for the dashboards route if the pathname starts with /d/', () => {
const mockPathName = '/d/foo';
const mockLink: NavModelItem = {
text: 'Test',
url: '/dashboards',
children: [
{
text: 'TestChild',
url: '/testChild1',
},
{
text: 'TestChild2',
url: '/testChild2',
},
],
};
expect(isLinkActive(mockPathName, mockLink)).toBe(true);
});
});
});
describe('isSearchActive', () => {
it('returns true if the search query parameter is "open"', () => {
const mockLocation = {
hash: '',
pathname: '/',
search: '?search=open',
state: '',
};
expect(isSearchActive(mockLocation)).toBe(true);
});
it('returns false if the search query parameter is missing', () => {
const mockLocation = {
hash: '',
pathname: '/',
search: '',
state: '',
};
expect(isSearchActive(mockLocation)).toBe(false);
});
});