<%= yield(:content) %>
diff --git a/test/integration/notifications_drawer_test.rb b/test/integration/notifications_drawer_test.rb
index 1dcc6e4e91d1..3fbd84cc199e 100644
--- a/test/integration/notifications_drawer_test.rb
+++ b/test/integration/notifications_drawer_test.rb
@@ -47,7 +47,7 @@ def notifications_open_and_close_flow
def navigate_somewhere_with_turbolinks
# check the outside click with turbolinks
- page.find('a.navbar-brand').click
+ page.find('a.pf-c-masthead__brand').click
# wait for loader to dissapear
page.has_no_selector?('div.spinner')
end
diff --git a/test/integration_test_helper.rb b/test/integration_test_helper.rb
index 1850daa1b7ad..512785516b34 100644
--- a/test/integration_test_helper.rb
+++ b/test/integration_test_helper.rb
@@ -60,7 +60,7 @@ def assert_breadcrumb_text(text)
def assert_new_button(index_path, new_link_text, new_path)
visit index_path
- click_link(new_link_text)
+ click_link(new_link_text, :class => /^((?!pf-c-nav__link).)*$/)
assert_current_path new_path
end
@@ -207,8 +207,8 @@ def refute_available_organization(organization)
def refute_available_organization_menu(organization)
within('.location-menu') do
- first('a:first-of-type').hover
- within('.location-menu>div>ul', visible: :all) do
+ first('button').click
+ within('.location-menu section ul ul', visible: :all) do
assert page.has_no_link?(organization)
end
end
@@ -254,8 +254,8 @@ def select_organization_dropdown(organization)
def select_organization_menu(organization)
within('.organization-menu') do
- first('a:first-of-type').hover
- find("span.list-group-item-value", text: organization).click
+ first('button').click
+ find("li.pf-c-nav__item", text: organization).click
end
end
diff --git a/webpack/assets/javascripts/react_app/common/colors.scss b/webpack/assets/javascripts/react_app/common/colors.scss
index ad9ceecf48e7..5f7b68861c88 100644
--- a/webpack/assets/javascripts/react_app/common/colors.scss
+++ b/webpack/assets/javascripts/react_app/common/colors.scss
@@ -10,9 +10,6 @@ $pf-blue-400: #0088ce;
$pf-color-white: #fff;
$pf-border-gray: #d1d1d1;
-$nav-pf-vertical-active-icon-color: white;
$nav-pf-vertical-active-bg-color: #026991;
$nav-pf-vertical-secondary-active-bg-color: #024d6c;
-$nav-pf-vertical-icon-color: #c7c7c7;
-$nav-pf-vertical-secondary-indicator-color: #d1d1d1;
$nav-pf-vertical-bg-color: #024d6c;
diff --git a/webpack/assets/javascripts/react_app/common/testHelpers.js b/webpack/assets/javascripts/react_app/common/testHelpers.js
index 672e1fd1a2bb..dbbbe84a872e 100644
--- a/webpack/assets/javascripts/react_app/common/testHelpers.js
+++ b/webpack/assets/javascripts/react_app/common/testHelpers.js
@@ -148,3 +148,127 @@ export const testSelectorsSnapshotWithFixtures = fixtures =>
Object.entries(fixtures).forEach(([description, selectorRunner]) =>
it(description, () => expect(selectorRunner()).toMatchSnapshot())
);
+
+export const initMockStore = {
+ bookmarksPF4: {},
+ hosts: {
+ storage: {
+ vmware: {
+ controllers: [],
+ volumes: [],
+ },
+ },
+ },
+ notifications: {
+ isDrawerOpen: null,
+ expandedGroup: null,
+ hasUnreadMessages: false,
+ },
+ toasts: {},
+ passwordStrength: {
+ password: '',
+ passwordConfirmation: '',
+ },
+ breadcrumbBar: {
+ resourceSwitcherItems: [],
+ isLoadingResources: false,
+ isSwitcherOpen: false,
+ resourceUrl: null,
+ requestError: null,
+ currentPage: null,
+ searchQuery: '',
+ pages: null,
+ titleReplacement: null,
+ },
+ layout: {
+ items: [],
+ isLoading: false,
+ isCollapsed: false,
+ },
+ diffModal: {
+ isOpen: false,
+ diff: '',
+ title: '',
+ diffViewType: 'split',
+ },
+ editor: {
+ hosts: [],
+ filteredHosts: [],
+ diffViewType: 'split',
+ editorName: 'editor',
+ errorText: '',
+ isFetchingHosts: false,
+ isLoading: false,
+ isMasked: false,
+ isMaximized: false,
+ isRendering: false,
+ isSearchingHosts: false,
+ isSelectOpen: false,
+ keyBinding: 'Default',
+ mode: 'Ruby',
+ previewResult: '',
+ renderedEditorValue: '',
+ readOnly: false,
+ searchQuery: '',
+ selectedHost: {
+ id: '',
+ name: '',
+ },
+ selectedView: 'input',
+ showError: false,
+ templateClass: '',
+ theme: 'Monokai',
+ autocompletion: true,
+ liveAutocompletion: false,
+ value: '',
+ kind: '',
+ },
+ templates: {
+ scheduleInProgress: false,
+ polling: false,
+ dataUrl: null,
+ },
+ factChart: {
+ modalToDisplay: {},
+ },
+ typeAheadSelect: {},
+ settingRecords: {
+ settings: {},
+ editing: null,
+ },
+ personalAccessTokens: {
+ tokens: [],
+ },
+ confirmModal: {
+ isOpen: false,
+ },
+ router: {
+ location: {
+ pathname: '/users/login',
+ search: '',
+ hash: '',
+ query: {},
+ },
+ action: 'POP',
+ },
+ extendable: {},
+ auditsPage: {
+ data: {
+ isLoading: true,
+ hasError: false,
+ hasData: false,
+ message: {
+ type: 'empty',
+ text: '',
+ },
+ },
+ query: {
+ page: 1,
+ searchQuery: '',
+ itemCount: 0,
+ },
+ },
+ foremanModals: {},
+ intervals: {},
+ API: {},
+};
diff --git a/webpack/assets/javascripts/react_app/components/BreadcrumbBar/BreadcrumbBar.scss b/webpack/assets/javascripts/react_app/components/BreadcrumbBar/BreadcrumbBar.scss
index 4da9237f35fc..8c2ce3127c2b 100644
--- a/webpack/assets/javascripts/react_app/components/BreadcrumbBar/BreadcrumbBar.scss
+++ b/webpack/assets/javascripts/react_app/components/BreadcrumbBar/BreadcrumbBar.scss
@@ -1,6 +1,4 @@
.breadcrumb-bar {
- padding-top: 20px;
-
.breadcrumb-line {
margin-right: -60px;
margin-left: -60px;
@@ -12,8 +10,6 @@
}
.breadcrumb-bar-pf4 {
- margin: 0 0 16px;
-
.breadcrumbs-list {
display: block;
}
diff --git a/webpack/assets/javascripts/react_app/components/BreadcrumbBar/components/Breadcrumb.js b/webpack/assets/javascripts/react_app/components/BreadcrumbBar/components/Breadcrumb.js
index ee499bc3f038..a88b2091bd49 100644
--- a/webpack/assets/javascripts/react_app/components/BreadcrumbBar/components/Breadcrumb.js
+++ b/webpack/assets/javascripts/react_app/components/BreadcrumbBar/components/Breadcrumb.js
@@ -4,6 +4,8 @@ import classNames from 'classnames';
import {
Breadcrumb as PfBreadcrumb,
BreadcrumbItem,
+ TextContent,
+ Text,
} from '@patternfly/react-core';
import EllipsisWithTooltip from 'react-ellipsis-with-tooltip';
import './Breadcrumbs.scss';
@@ -17,9 +19,9 @@ const Breadcrumb = ({
}) => {
if (isTitle) {
return (
-
);
}
diff --git a/webpack/assets/javascripts/react_app/components/HostDetails/HostDetails.scss b/webpack/assets/javascripts/react_app/components/HostDetails/HostDetails.scss
index 760eedd3ee0b..6def35f77f69 100644
--- a/webpack/assets/javascripts/react_app/components/HostDetails/HostDetails.scss
+++ b/webpack/assets/javascripts/react_app/components/HostDetails/HostDetails.scss
@@ -2,67 +2,48 @@
background: var(--pf-global--BackgroundColor--200);
}
-.host-details-header-section {
- margin-left: -20px;
- margin-right: -20px;
- background: var(--pf-global--BackgroundColor--100);
-
- .header-top {
- margin: 0px 24px 16px;
- padding-top: 16px;
-
- .host-details-breadcrumb {
- margin: 0 0 24px;
- }
-
- .hostname-skeleton-rapper {
- margin-bottom: 8px;
-
- .hostname-wrapper {
- display: inline-flex;
- max-width: 60%;
- margin-right: 8px;
-
- .hostname-truncate {
- text-overflow: ellipsis;
- max-width: 100%;
- white-space: nowrap;
- overflow: hidden;
- display: inline-block;
- }
- }
- }
- }
-
- .pf-c-alert {
- margin: 16px 24px;
- }
-
- .host-details-tabs {
- margin: 0 24px;
- }
-
-
+.pf-c-page__main-section.pf-m-light.host-details-tabs-section {
+ padding-left: 24px;
.tab-width-138 {
- max-width: calc(100vw - 138px); //These values may need to be increased for other browsers
+ max-width: calc(
+ 100vw - 138px
+ ); //These values may need to be increased for other browsers
@media screen and (max-width: 767px) {
max-width: calc(100vw - 64px);
}
}
-
.tab-width-263 {
- max-width: calc(100vw - 263px); //These values may need to be increased for other browsers
+ max-width: calc(
+ 100vw - 263px
+ ); //These values may need to be increased for other browsers
@media screen and (max-width: 767px) {
max-width: calc(100vw - 64px);
}
}
+}
- .host-details-tab-item {
- padding: 16px 24px;
- background: var(--pf-global--BackgroundColor--200);
+.host-details-header-section {
+ .hostname-skeleton-rapper {
+ .hostname-wrapper {
+ display: inline-flex;
+ max-width: 60%;
+ margin-right: 8px;
+
+ .hostname-truncate {
+ text-overflow: ellipsis;
+ max-width: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ display: inline-block;
+ }
+ }
+ }
+}
+.host-details-cards-section {
+ .host-details-tab-item {
.pf-c-card {
height: 100%;
}
diff --git a/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/Details/index.js b/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/Details/index.js
index 540ff4055e6f..9efcb6a7a42a 100644
--- a/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/Details/index.js
+++ b/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/Details/index.js
@@ -1,21 +1,16 @@
import PropTypes from 'prop-types';
import React, { useEffect, useContext } from 'react';
-import { Flex, FlexItem, Button } from '@patternfly/react-core';
+import { Flex, FlexItem, Button, PageSection } from '@patternfly/react-core';
import { registerCoreCards } from './CardRegistry';
import Slot from '../../../common/Slot';
import { STATUS } from '../../../../constants';
-import '../Overview/styles.css';
import './DetailsCard.scss';
import { translate as __ } from '../../../../common/I18n';
import { CardExpansionContext } from '../../CardExpansionContext';
const DetailsTab = ({ response, status, hostName }) => {
useEffect(() => {
- // This is a workaround for adding a gray background inspired by PF4 design
- // TODO: delete it when PF4 layout (Page component) is implemented in Foreman
- document.body.classList.add('pf-gray-background');
registerCoreCards();
- return () => document.body.classList.remove('pf-gray-background');
}, []);
const { cardExpandStates, dispatch } = useContext(CardExpansionContext);
const areAllCardsExpanded = Object.values(cardExpandStates).every(
@@ -31,28 +26,30 @@ const DetailsTab = ({ response, status, hostName }) => {
: __('Expand all cards');
return (
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
+
);
};
diff --git a/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/Overview/index.js b/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/Overview/index.js
index 0283ba3c9678..ef675be7615d 100644
--- a/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/Overview/index.js
+++ b/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/Overview/index.js
@@ -1,32 +1,29 @@
import PropTypes from 'prop-types';
import React, { useEffect } from 'react';
-import { Grid } from '@patternfly/react-core';
+import { PageSection, Grid } from '@patternfly/react-core';
import { registerCoreCards } from './CardsRegistry';
import Slot from '../../../common/Slot';
import { STATUS } from '../../../../constants';
-import './styles.css';
const OverviewTab = ({ response, status, hostName }) => {
useEffect(() => {
- // This is a workaround for adding gray background inspiring pf4 desgin
- // TODO: delete it when pf4 layout (Page copmponent) is implemented in foreman
- document.body.classList.add('pf-gray-background');
registerCoreCards();
- return () => document.body.classList.remove('pf-gray-background');
}, []);
return (
-
-
-
-
-
+
+
+
+
+
+
+
);
};
diff --git a/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/Overview/styles.css b/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/Overview/styles.css
deleted file mode 100644
index 7a4459fcdedc..000000000000
--- a/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/Overview/styles.css
+++ /dev/null
@@ -1,7 +0,0 @@
-.details-tab {
- padding-top: 20px;
-}
-
-.details-cards {
- padding-top: 20px;
-}
diff --git a/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/TabRouter/index.js b/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/TabRouter/index.js
index e6952bbc9108..ff8b129f7620 100644
--- a/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/TabRouter/index.js
+++ b/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/TabRouter/index.js
@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React from 'react';
+import { PageSection } from '@patternfly/react-core';
import { HashRouter, Route, Redirect, Switch } from 'react-router-dom';
import { STATUS } from '../../../../constants';
import Slot from '../../../common/Slot';
@@ -8,31 +9,35 @@ import TabsWithHashHistory from './Tabs';
const TabRouter = ({ children, tabs, hostName, response, status, router }) => (
- <>
+
-
-
-
-
- {tabs.map(tab => (
- (
-
- )}
- />
- ))}
-
- >
+
+
+
+
+
+ {tabs.map(tab => (
+ (
+
+ )}
+ />
+ ))}
+
);
diff --git a/webpack/assets/javascripts/react_app/components/HostDetails/index.js b/webpack/assets/javascripts/react_app/components/HostDetails/index.js
index 8cb8a8d341d1..53189bcc1b99 100644
--- a/webpack/assets/javascripts/react_app/components/HostDetails/index.js
+++ b/webpack/assets/javascripts/react_app/components/HostDetails/index.js
@@ -15,6 +15,7 @@ import {
Text,
TextVariants,
PageSection,
+ PageSectionVariants,
Split,
SplitItem,
} from '@patternfly/react-core';
@@ -91,175 +92,175 @@ const HostDetails = ({
{id}
+
+
+
+ {response.name && (
+ {
+ e.preventDefault();
+ history.push(href);
+ }}
+ resource={{
+ nameField: 'name',
+ resourceUrl: '/api/v2/hosts?thin=true',
+ switcherItemUrl: '/new/hosts/:name',
+ }}
+ breadcrumbItems={[
+ { caption: __('Hosts'), url: foremanUrl('/hosts') },
+ { caption: response.name },
+ ]}
+ />
+ )}
+
+
-
-
- {response.name && (
- {
- e.preventDefault();
- history.push(href);
- }}
- resource={{
- nameField: 'name',
- resourceUrl: '/api/v2/hosts?thin=true',
- switcherItemUrl: '/new/hosts/:name',
- }}
- breadcrumbItems={[
- { caption: __('Hosts'), url: foremanUrl('/hosts') },
- { caption: response.name },
- ]}
- />
- )}
-
-
-
-
- {response && (
- <>
-
-
- {response && (
-
+
+
+ {response && (
+ <>
+
+
+ {response && (
+
+ {response.name}
+
+ )}
+
+
+
+
+
+
+
+
+ {content}
+
)}
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- )}
-
-
-
-
-
-
-
-
-
-
-
+ {response?.operatingsystem_name}
+
+
+
+
+
+
+ >
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ {response && (
+
+
+ {date => sprintf(__('Created %s'), date)}
+ {' '}
+ {response.creator
+ ? `${sprintf(__('by %s'), response.creator)}`
+ : ''}{' '}
+
+ {date => sprintf(__('(updated %s)'), date)}
+
+
+ )}
+
+
+ {tabs && (
+
+
- {response && (
-
-
- {date => sprintf(__('Created %s'), date)}
- {' '}
- {response.creator
- ? `${sprintf(__('by %s'), response.creator)}`
- : ''}{' '}
-
- {date => sprintf(__('(updated %s)'), date)}
-
-
- )}
-
-
- {tabs && (
-
-
-
- {filteredTabs.map(tab => {
- const tabID = `${tab.toLowerCase()}-tab`;
- return (
-
- );
- })}
-
-
-
- )}
-
+ {filteredTabs.map(tab => {
+ const tabID = `${tab.toLowerCase()}-tab`;
+ return (
+
+ );
+ })}
+
+
+
+ )}
>
);
};
diff --git a/webpack/assets/javascripts/react_app/components/Layout/Layout.fixtures.js b/webpack/assets/javascripts/react_app/components/Layout/Layout.fixtures.js
index 9ca3c85da7ce..8c0a00c57378 100644
--- a/webpack/assets/javascripts/react_app/components/Layout/Layout.fixtures.js
+++ b/webpack/assets/javascripts/react_app/components/Layout/Layout.fixtures.js
@@ -1,32 +1,19 @@
const mockOnClick = jest.fn();
-const PFitems = [
- {
- title: 'Monitor',
- initialActive: true,
- iconClass: 'fa fa-tachometer',
- subItems: subItemsA,
- href: '/a',
- },
- {
- title: 'Hosts',
- initialActive: false,
- iconClass: 'fa fa-server',
- subItems: subItemsB,
- href: '/b',
- },
-];
-
const subItemsA = [
{
title: 'Aa',
isDivider: false,
onClick: mockOnClick,
+ href: '/a',
+ id: 'menu_item_aa',
},
{
title: 'Cc',
isDivider: false,
onClick: mockOnClick,
+ href: '/c',
+ id: 'menu_item_cc',
},
];
const subItemsB = [
@@ -34,9 +21,25 @@ const subItemsB = [
title: 'Dd',
isDivider: false,
onClick: mockOnClick,
+ href: '/d',
+ id: 'menu_item_dd',
},
];
+const PFitems = [
+ {
+ title: 'Monitor',
+ initialActive: true,
+ iconClass: 'fa fa-tachometer',
+ subItems: subItemsA,
+ },
+ {
+ title: 'Hosts',
+ initialActive: false,
+ iconClass: 'fa fa-server',
+ subItems: subItemsB,
+ },
+];
// Server Hash Data
const monitorChildren = [
{
@@ -118,23 +121,6 @@ const user = {
],
};
-const organizations = {
- current_org: 'org1',
- available_organizations: [
- { id: 1, title: 'org1', href: '/organizations/1-org1/select' },
- { id: 2, title: 'org2', href: '/organizations/2-org2/select' },
- ],
-};
-
-const locations = {
- current_location: 'london',
- available_locations: [
- { id: 1, title: 'yaml', href: '/locations/1-yaml/select' },
- { id: 2, title: 'london', href: '/locations/2-london/select' },
- { id: 3, title: 'norway', href: '/locations/3-norway/select' },
- ],
-};
-
const serverUser = {
current_user: {
firstname: 'G',
@@ -158,20 +144,258 @@ const serverUser = {
],
};
+export const userDropdownProps = {
+ user: serverUser,
+ notification_url: '/',
+ isOpen: true,
+};
+
+export const fullLayoutStore = {
+ layout: {
+ items: [
+ {
+ type: 'sub_menu',
+ name: 'Monitor',
+ icon: 'fa fa-tachometer',
+ children: [
+ {
+ type: 'item',
+ exact: true,
+ html_options: {},
+ name: 'Dashboard',
+ url: '/',
+ title: 'Dashboard',
+ },
+ {
+ type: 'item',
+ exact: false,
+ html_options: {},
+ name: 'Facts',
+ url: '/fact_values',
+ title: 'Facts',
+ },
+ {
+ type: 'item',
+ exact: false,
+ html_options: {},
+ name: 'Audits',
+ url: '/audits',
+ title: 'Audits',
+ },
+ {
+ type: 'divider',
+ name: 'Reports',
+ title: 'Reports',
+ },
+ {
+ type: 'item',
+ exact: false,
+ html_options: {},
+ name: 'Config Management',
+ url: '/config_reports?search=eventful+%3D+true',
+ title: 'Config Management',
+ },
+ {
+ type: 'item',
+ exact: false,
+ html_options: {},
+ name: 'Report Templates',
+ url: '/templates/report_templates',
+ title: 'Report Templates',
+ },
+ ],
+ className: '',
+ },
+ {
+ type: 'sub_menu',
+ name: 'Hosts',
+ icon: 'fa fa-server',
+ children: [
+ {
+ type: 'item',
+ exact: false,
+ html_options: {},
+ name: 'All Hosts',
+ url: '/hosts',
+ title: 'All Hosts',
+ },
+ {
+ type: 'item',
+ exact: false,
+ html_options: {},
+ name: 'Create Host',
+ url: '/hosts/new',
+ title: 'Create Host',
+ },
+ {
+ type: 'divider',
+ name: 'Provisioning Setup',
+ title: 'Provisioning Setup',
+ },
+ {
+ type: 'item',
+ exact: false,
+ html_options: {},
+ name: 'Architectures',
+ url: '/architectures',
+ title: 'Architectures',
+ },
+ ],
+ className: '',
+ },
+ ],
+ isLoading: false,
+ isCollapsed: false,
+ currentOrganization: 'Default Organization',
+ currentLocation: 'Default Location',
+ },
+};
+
+export const layoutData = {
+ menu: [
+ {
+ type: 'sub_menu',
+ name: 'Monitor',
+ icon: 'fa fa-tachometer',
+ children: [
+ {
+ type: 'item',
+ exact: true,
+ html_options: {},
+ name: 'Dashboard',
+ url: '/',
+ },
+ {
+ type: 'item',
+ exact: false,
+ html_options: {},
+ name: 'Facts',
+ url: '/fact_values',
+ },
+ {
+ type: 'item',
+ exact: false,
+ html_options: {},
+ name: 'Audits',
+ url: '/audits',
+ },
+ {
+ type: 'divider',
+ name: 'Reports',
+ },
+ {
+ type: 'item',
+ exact: false,
+ html_options: {},
+ name: 'Config Management',
+ url: '/config_reports?search=eventful+%3D+true',
+ },
+ {
+ type: 'item',
+ exact: false,
+ html_options: {},
+ name: 'Report Templates',
+ url: '/templates/report_templates',
+ },
+ ],
+ },
+ {
+ type: 'sub_menu',
+ name: 'Hosts',
+ icon: 'fa fa-server',
+ children: [
+ {
+ type: 'item',
+ exact: false,
+ html_options: {},
+ name: 'All Hosts',
+ url: '/hosts',
+ },
+ {
+ type: 'item',
+ exact: false,
+ html_options: {},
+ name: 'Create Host',
+ url: '/hosts/new',
+ },
+ {
+ type: 'divider',
+ name: 'Provisioning Setup',
+ },
+ {
+ type: 'item',
+ exact: false,
+ html_options: {},
+ name: 'Architectures',
+ url: '/architectures',
+ },
+ ],
+ },
+ {
+ type: 'sub_menu',
+ name: 'Configure',
+ icon: 'fa fa-wrench',
+ children: [
+ {
+ type: 'item',
+ exact: false,
+ html_options: {},
+ name: 'Host Groups',
+ url: '/hostgroups',
+ },
+ {
+ type: 'item',
+ exact: false,
+ html_options: {},
+ name: 'Global Parameters',
+ url: '/common_parameters',
+ },
+ ],
+ },
+ ],
+ root: '/',
+ logo,
+ notification_url: '/notification_recipients',
+ user,
+ stop_impersonation_url: '/users/stop_impersonation',
+ instance_title: 'Production',
+ brand: 'foreman',
+ locations: {
+ current_location: 'london',
+ available_locations: [
+ {
+ id: 2,
+ title: 'london',
+ href: '/locations/2-london/select',
+ },
+ {
+ id: 3,
+ title: 'norway',
+ href: '/locations/3-norway/select',
+ },
+ ],
+ },
+ orgs: {
+ current_org: 'org1',
+ available_organizations: [
+ {
+ id: 1,
+ title: 'org1',
+ href: '/organizations/1-org1/select',
+ },
+ {
+ id: 2,
+ title: 'org2',
+ href: '/organizations/2-org2/select',
+ },
+ ],
+ },
+};
+
export const layoutMock = {
items: PFitems,
- data: {
- menu: [...hashItemsA, ...hashItemNameless],
- locations,
- orgs: organizations,
- root: '/',
- logo,
- notification_url: '/notification_recipients',
- user,
- stop_impersonation_url: '/users/stop_impersonation',
- instance_title: 'Production',
- },
- setFlyoutActiveItem: jest.fn(),
+ data: layoutData,
+ setNavigationActiveItem: jest.fn(),
};
export const noItemsMock = {
@@ -184,9 +408,3 @@ export const hasTaxonomiesMock = {
currentLocation: 'london',
currentOrganization: 'org1',
};
-
-export const userDropdownProps = {
- user: serverUser,
- notification_url: '/',
- isOpen: true,
-};
diff --git a/webpack/assets/javascripts/react_app/components/Layout/Layout.js b/webpack/assets/javascripts/react_app/components/Layout/Layout.js
index 30d66f7118c3..cfa0c73d7691 100644
--- a/webpack/assets/javascripts/react_app/components/Layout/Layout.js
+++ b/webpack/assets/javascripts/react_app/components/Layout/Layout.js
@@ -1,15 +1,9 @@
import React from 'react';
-import { VerticalNav } from 'patternfly-react';
-import { translate as __ } from '../../common/I18n';
-
-import {
- handleMenuClick,
- layoutPropTypes,
- layoutDefaultProps,
-} from './LayoutHelper';
-import LayoutContainer from './components/LayoutContainer';
-import HeaderToolbar from './components/Toolbar/HeaderToolbar';
+import { Page, PageSidebar } from '@patternfly/react-core';
+import { layoutPropTypes, layoutDefaultProps } from './LayoutHelper';
+import Header from './components/Toolbar/Header';
+import Navigation from './Navigation';
import './layout.scss';
const Layout = ({
@@ -20,40 +14,46 @@ const Layout = ({
navigate,
expandLayoutMenus,
collapseLayoutMenus,
- changeActiveMenu,
- activeMenu,
children,
-}) => (
-
-
- handleMenuClick(primary, activeMenu, changeActiveMenu)
- }
- onNavigate={({ href }) => navigate(href)}
- activePath={`/${__(activeMenu || 'active')}/`}
- onCollapse={collapseLayoutMenus}
- onExpand={expandLayoutMenus}
- >
-
-
-
-
- {children}
-
-);
+ }
+ >
+ {children}
+
+ >
+ );
+};
Layout.propTypes = layoutPropTypes;
Layout.defaultProps = layoutDefaultProps;
diff --git a/webpack/assets/javascripts/react_app/components/Layout/Navigation.js b/webpack/assets/javascripts/react_app/components/Layout/Navigation.js
new file mode 100644
index 000000000000..776459189f5f
--- /dev/null
+++ b/webpack/assets/javascripts/react_app/components/Layout/Navigation.js
@@ -0,0 +1,195 @@
+import React, { useEffect, useRef, useMemo } from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import {
+ Nav,
+ NavList,
+ NavItem,
+ NavExpandable,
+ NavGroup,
+ NavItemSeparator,
+} from '@patternfly/react-core';
+import { getCurrentPath } from './LayoutHelper';
+
+const titleWithIcon = (title, iconClass) => (
+
+
+ {title}
+
+);
+
+const Navigation = ({
+ items,
+ navigationActiveItem,
+ setNavigationActiveItem,
+}) => {
+ const clearTimerRef = useRef();
+ useEffect(
+ () => () => {
+ if (clearTimerRef.current) clearTimeout(clearTimerRef.current);
+ },
+ []
+ );
+
+ const onMouseOver = index => {
+ clearTimeout(clearTimerRef.current);
+ if (navigationActiveItem !== index) {
+ setNavigationActiveItem(index);
+ }
+ };
+
+ const pathFragment = path => path.split('?')[0];
+ const subItemToItemMap = {};
+
+ items.forEach(item => {
+ item.subItems.forEach(subItem => {
+ if (!subItem.isDivider) {
+ // don't keep the query parameters for the key
+ subItemToItemMap[pathFragment(subItem.href)] = item.title;
+ }
+ });
+ });
+
+ const getGroupedItems = useMemo(
+ () =>
+ items.map(({ subItems, ...rest }) => {
+ const { pathname } = window.location;
+ const groups = [];
+ let currIndex = 0;
+ if (subItems.length) {
+ if (subItems[0].isDivider) {
+ groups.push({ title: subItems[0].title, groupItems: [] });
+ } else {
+ groups.push({ title: '', groupItems: [] });
+ }
+ subItems.forEach(sub => {
+ if (sub.isDivider) {
+ groups.push({ title: sub.title, groupItems: [] });
+ currIndex++;
+ } else {
+ groups[currIndex].groupItems.push({
+ ...sub,
+ isActive: pathname === sub.href.split('?')[0],
+ });
+ }
+ });
+ }
+ return { ...rest, groups };
+ }),
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [items.length]
+ );
+
+ const groupedItems = getGroupedItems;
+ return (
+
+ );
+};
+
+Navigation.propTypes = {
+ items: PropTypes.array.isRequired,
+ navigationActiveItem: PropTypes.number,
+ setNavigationActiveItem: PropTypes.func.isRequired,
+};
+Navigation.defaultProps = {
+ navigationActiveItem: null,
+};
+
+export default Navigation;
diff --git a/webpack/assets/javascripts/react_app/components/Layout/__tests__/Layout.test.js b/webpack/assets/javascripts/react_app/components/Layout/__tests__/Layout.test.js
index 3fc0d9ab1651..d89b48a8bfb3 100644
--- a/webpack/assets/javascripts/react_app/components/Layout/__tests__/Layout.test.js
+++ b/webpack/assets/javascripts/react_app/components/Layout/__tests__/Layout.test.js
@@ -1,12 +1,34 @@
-import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
-import Layout from '../Layout';
-import { layoutMock } from '../Layout.fixtures';
+import React from 'react';
+import { Provider } from 'react-redux';
+import configureMockStore from 'redux-mock-store';
+import { BrowserRouter as Router } from 'react-router-dom';
+import { fireEvent, screen, render, act } from '@testing-library/react';
+import "@testing-library/jest-dom/extend-expect"; // for toBeVisable
+import Layout from '../index';
+import { layoutData, fullLayoutStore } from '../Layout.fixtures';
+import { initMockStore } from '../../../common/testHelpers';
+import { reducers } from '../index';
-const fixtures = {
- 'renders layout': layoutMock,
-};
+const mockStore = configureMockStore(reducers);
+const store = mockStore({ ...initMockStore, ...fullLayoutStore });
+jest.useFakeTimers();
describe('Layout', () => {
- describe('rendering', () =>
- testComponentSnapshotsWithFixtures(Layout, fixtures));
+ it('Layout', async () => {
+ render(
+
+
+
+
+
+ );
+ expect(screen.getByText('Monitor')).toBeVisible();
+ expect(screen.getByText('Dashboard')).toBeVisible();
+ expect(screen.getByText('All Hosts')).not.toBeVisible();
+ await act(async () => {
+ await fireEvent.click(screen.getByText('Hosts'));
+ });
+ expect(screen.getByText('All Hosts')).toBeVisible();
+ expect(screen.getByText('Dashboard')).toBeVisible();
+ });
});
diff --git a/webpack/assets/javascripts/react_app/components/Layout/__tests__/LayoutActions.test.js b/webpack/assets/javascripts/react_app/components/Layout/__tests__/LayoutActions.test.js
index ecccb3cc18f7..e11ebada7892 100644
--- a/webpack/assets/javascripts/react_app/components/Layout/__tests__/LayoutActions.test.js
+++ b/webpack/assets/javascripts/react_app/components/Layout/__tests__/LayoutActions.test.js
@@ -3,7 +3,6 @@ import {
initializeLayout,
showLoading,
hideLoading,
- changeActiveMenu,
collapseLayoutMenus,
expandLayoutMenus,
} from '../LayoutActions';
@@ -12,7 +11,6 @@ const fixtures = {
'should initialize the layout': () =>
initializeLayout({
items: 'some items',
- activeMenu: 'some active menu',
isCollapsed: false,
organization: 'org1',
location: 'loc2',
@@ -22,9 +20,6 @@ const fixtures = {
'should hideLoading': () => hideLoading(),
- 'should changeActiveMenu to Monitor': () =>
- changeActiveMenu({ title: 'Monitor' }),
-
'should expandLayoutMenus': () => expandLayoutMenus(),
'should collapseLayoutMenus': () => collapseLayoutMenus(),
diff --git a/webpack/assets/javascripts/react_app/components/Layout/__tests__/LayoutReducer.test.js b/webpack/assets/javascripts/react_app/components/Layout/__tests__/LayoutReducer.test.js
index c0249f393d18..49b1c4aab26e 100644
--- a/webpack/assets/javascripts/react_app/components/Layout/__tests__/LayoutReducer.test.js
+++ b/webpack/assets/javascripts/react_app/components/Layout/__tests__/LayoutReducer.test.js
@@ -2,7 +2,6 @@ import {
LAYOUT_INITIALIZE,
LAYOUT_SHOW_LOADING,
LAYOUT_HIDE_LOADING,
- LAYOUT_CHANGE_ACTIVE,
LAYOUT_COLLAPSE,
LAYOUT_EXPAND,
} from '../LayoutConstants';
@@ -18,7 +17,6 @@ const fixtures = {
type: LAYOUT_INITIALIZE,
payload: {
items: 'some-items',
- activeMenu: 'some-menu',
isCollapsed: true,
organization: 'some organization',
location: 'some location',
@@ -35,14 +33,6 @@ const fixtures = {
type: LAYOUT_HIDE_LOADING,
},
},
- 'should handle LAYOUT_CHANGE_ACTIVE': {
- action: {
- type: LAYOUT_CHANGE_ACTIVE,
- payload: {
- activeMenu: 'Monitor',
- },
- },
- },
'should handle LAYOUT_COLLAPSE': {
action: {
type: LAYOUT_COLLAPSE,
diff --git a/webpack/assets/javascripts/react_app/components/Layout/__tests__/LayoutSelectors.test.js b/webpack/assets/javascripts/react_app/components/Layout/__tests__/LayoutSelectors.test.js
index b5eca2648960..89ec2d5ad1dc 100644
--- a/webpack/assets/javascripts/react_app/components/Layout/__tests__/LayoutSelectors.test.js
+++ b/webpack/assets/javascripts/react_app/components/Layout/__tests__/LayoutSelectors.test.js
@@ -1,6 +1,5 @@
import { testSelectorsSnapshotWithFixtures } from '../../../common/testHelpers';
import {
- selectActiveMenu,
patternflyMenuItemsSelector,
selectIsLoading,
selectLayout,
@@ -13,7 +12,6 @@ import { layoutMock, hashItemNameless } from '../Layout.fixtures';
const state = {
layout: {
items: layoutMock.data.menu,
- activeMenu: 'Hosts',
currentOrganization: 'org1',
currentLocation: 'loc1',
isLoading: true,
@@ -30,7 +28,6 @@ const emptyState = {
const namelessState = {
layout: {
items: hashItemNameless,
- activeMenu: 'Empty',
currentOrganization: 'org1',
currentLocation: 'loc1',
isLoading: true,
@@ -40,7 +37,6 @@ const namelessState = {
const fixtures = {
'should return Layout': () => selectLayout(state),
- 'should return activeMenu': () => selectActiveMenu(state),
'should return PF-React Compatible items': () =>
patternflyMenuItemsSelector(state),
'should return empty array of items': () =>
diff --git a/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/Layout.test.js.snap b/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/Layout.test.js.snap
deleted file mode 100644
index 3b6943146fb3..000000000000
--- a/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/Layout.test.js.snap
+++ /dev/null
@@ -1,198 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Layout rendering renders layout 1`] = `
-
-
-
-
-
-
-
-
-`;
diff --git a/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/LayoutActions.test.js.snap b/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/LayoutActions.test.js.snap
index 6b2e9a0e29a6..f99426f72157 100644
--- a/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/LayoutActions.test.js.snap
+++ b/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/LayoutActions.test.js.snap
@@ -1,14 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`Layout actions should changeActiveMenu to Monitor 1`] = `
-Object {
- "payload": Object {
- "activeMenu": "Monitor",
- },
- "type": "LAYOUT_CHANGE_ACTIVE",
-}
-`;
-
exports[`Layout actions should collapseLayoutMenus 1`] = `
Object {
"type": "LAYOUT_COLLAPSE",
@@ -30,7 +21,6 @@ Object {
exports[`Layout actions should initialize the layout 1`] = `
Object {
"payload": Object {
- "activeMenu": "some active menu",
"isCollapsed": false,
"items": "some items",
"location": "loc2",
diff --git a/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/LayoutHelper.test.js.snap b/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/LayoutHelper.test.js.snap
index 3e0fc459a64b..78e5c6010b40 100644
--- a/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/LayoutHelper.test.js.snap
+++ b/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/LayoutHelper.test.js.snap
@@ -6,17 +6,49 @@ Array [
"children": Array [
Object {
"exact": true,
+ "html_options": Object {},
"name": "Dashboard",
"title": "Dashboard",
"type": "item",
"url": "/",
},
Object {
+ "exact": false,
+ "html_options": Object {},
"name": "Facts",
"title": "Facts",
"type": "item",
"url": "/fact_values",
},
+ Object {
+ "exact": false,
+ "html_options": Object {},
+ "name": "Audits",
+ "title": "Audits",
+ "type": "item",
+ "url": "/audits",
+ },
+ Object {
+ "name": "Reports",
+ "title": "Reports",
+ "type": "divider",
+ },
+ Object {
+ "exact": false,
+ "html_options": Object {},
+ "name": "Config Management",
+ "title": "Config Management",
+ "type": "item",
+ "url": "/config_reports?search=eventful+%3D+true",
+ },
+ Object {
+ "exact": false,
+ "html_options": Object {},
+ "name": "Report Templates",
+ "title": "Report Templates",
+ "type": "item",
+ "url": "/templates/report_templates",
+ },
],
"className": "",
"icon": "fa fa-tachometer",
@@ -26,11 +58,34 @@ Array [
Object {
"children": Array [
Object {
+ "exact": false,
+ "html_options": Object {},
"name": "All Hosts",
"title": "All Hosts",
"type": "item",
+ "url": "/hosts",
+ },
+ Object {
+ "exact": false,
+ "html_options": Object {},
+ "name": "Create Host",
+ "title": "Create Host",
+ "type": "item",
"url": "/hosts/new",
},
+ Object {
+ "name": "Provisioning Setup",
+ "title": "Provisioning Setup",
+ "type": "divider",
+ },
+ Object {
+ "exact": false,
+ "html_options": Object {},
+ "name": "Architectures",
+ "title": "Architectures",
+ "type": "item",
+ "url": "/architectures",
+ },
],
"className": "",
"icon": "fa fa-server",
@@ -40,18 +95,25 @@ Array [
Object {
"children": Array [
Object {
- "title": undefined,
+ "exact": false,
+ "html_options": Object {},
+ "name": "Host Groups",
+ "title": "Host Groups",
"type": "item",
- "url": "/nameless",
+ "url": "/hostgroups",
},
Object {
- "title": undefined,
- "type": "divider",
+ "exact": false,
+ "html_options": Object {},
+ "name": "Global Parameters",
+ "title": "Global Parameters",
+ "type": "item",
+ "url": "/common_parameters",
},
],
"className": "",
- "icon": "pficon pficon-unplugged",
- "name": "Empty",
+ "icon": "fa fa-wrench",
+ "name": "Configure",
"type": "sub_menu",
},
Object {
@@ -86,12 +148,6 @@ Array [
"onClick": [Function],
"title": "Any Location",
},
- Object {
- "name": "yaml",
- "onClick": [Function],
- "title": "yaml",
- "type": undefined,
- },
Object {
"name": "london",
"onClick": [Function],
diff --git a/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/LayoutReducer.test.js.snap b/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/LayoutReducer.test.js.snap
index 830e948fda36..9cb5baf11b50 100644
--- a/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/LayoutReducer.test.js.snap
+++ b/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/LayoutReducer.test.js.snap
@@ -1,17 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`Layout reducer should handle LAYOUT_CHANGE_ACTIVE 1`] = `
-Object {
- "activeMenu": "Monitor",
- "isCollapsed": false,
- "isLoading": false,
- "items": Array [],
-}
-`;
-
exports[`Layout reducer should handle LAYOUT_COLLAPSE 1`] = `
Object {
- "activeMenu": "initialActive",
"isCollapsed": true,
"isLoading": false,
"items": Array [],
@@ -20,7 +10,6 @@ Object {
exports[`Layout reducer should handle LAYOUT_EXPAND 1`] = `
Object {
- "activeMenu": "initialActive",
"isCollapsed": false,
"isLoading": false,
"items": Array [],
@@ -29,7 +18,6 @@ Object {
exports[`Layout reducer should handle LAYOUT_HIDE_LOADING 1`] = `
Object {
- "activeMenu": "initialActive",
"isCollapsed": false,
"isLoading": false,
"items": Array [],
@@ -38,7 +26,6 @@ Object {
exports[`Layout reducer should handle LAYOUT_INITIALIZE 1`] = `
Object {
- "activeMenu": "some-menu",
"currentLocation": "some location",
"currentOrganization": "some organization",
"isCollapsed": true,
@@ -49,7 +36,6 @@ Object {
exports[`Layout reducer should handle LAYOUT_SHOW_LOADING 1`] = `
Object {
- "activeMenu": "initialActive",
"isCollapsed": false,
"isLoading": true,
"items": Array [],
@@ -58,7 +44,6 @@ Object {
exports[`Layout reducer should return the initial state 1`] = `
Object {
- "activeMenu": "initialActive",
"isCollapsed": false,
"isLoading": false,
"items": Array [],
diff --git a/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/LayoutSelectors.test.js.snap b/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/LayoutSelectors.test.js.snap
index a90d24968c9e..3c61b2e5bc3f 100644
--- a/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/LayoutSelectors.test.js.snap
+++ b/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/LayoutSelectors.test.js.snap
@@ -13,7 +13,6 @@ Array [
exports[`Layout selectors should return Layout 1`] = `
Object {
- "activeMenu": "Hosts",
"currentLocation": "loc1",
"currentOrganization": "org1",
"isCollapsed": false,
@@ -23,17 +22,43 @@ Object {
"children": Array [
Object {
"exact": true,
+ "html_options": Object {},
"name": "Dashboard",
- "title": "Dashboard",
"type": "item",
"url": "/",
},
Object {
+ "exact": false,
+ "html_options": Object {},
"name": "Facts",
- "title": "Facts",
"type": "item",
"url": "/fact_values",
},
+ Object {
+ "exact": false,
+ "html_options": Object {},
+ "name": "Audits",
+ "type": "item",
+ "url": "/audits",
+ },
+ Object {
+ "name": "Reports",
+ "type": "divider",
+ },
+ Object {
+ "exact": false,
+ "html_options": Object {},
+ "name": "Config Management",
+ "type": "item",
+ "url": "/config_reports?search=eventful+%3D+true",
+ },
+ Object {
+ "exact": false,
+ "html_options": Object {},
+ "name": "Report Templates",
+ "type": "item",
+ "url": "/templates/report_templates",
+ },
],
"icon": "fa fa-tachometer",
"name": "Monitor",
@@ -42,11 +67,30 @@ Object {
Object {
"children": Array [
Object {
+ "exact": false,
+ "html_options": Object {},
"name": "All Hosts",
- "title": "All Hosts",
+ "type": "item",
+ "url": "/hosts",
+ },
+ Object {
+ "exact": false,
+ "html_options": Object {},
+ "name": "Create Host",
"type": "item",
"url": "/hosts/new",
},
+ Object {
+ "name": "Provisioning Setup",
+ "type": "divider",
+ },
+ Object {
+ "exact": false,
+ "html_options": Object {},
+ "name": "Architectures",
+ "type": "item",
+ "url": "/architectures",
+ },
],
"icon": "fa fa-server",
"name": "Hosts",
@@ -55,15 +99,22 @@ Object {
Object {
"children": Array [
Object {
+ "exact": false,
+ "html_options": Object {},
+ "name": "Host Groups",
"type": "item",
- "url": "/nameless",
+ "url": "/hostgroups",
},
Object {
- "type": "divider",
+ "exact": false,
+ "html_options": Object {},
+ "name": "Global Parameters",
+ "type": "item",
+ "url": "/common_parameters",
},
],
- "icon": "pficon pficon-unplugged",
- "name": "Empty",
+ "icon": "fa fa-wrench",
+ "name": "Configure",
"type": "sub_menu",
},
],
@@ -77,22 +128,58 @@ Array [
"iconClass": "fa fa-tachometer",
"subItems": Array [
Object {
- "className": "",
+ "className": "mobile-active",
"href": "/",
"id": "menu_item_dashboard",
"isDivider": false,
"onClick": [Function],
"preventHref": true,
- "title": "Dashboard",
+ "title": undefined,
},
Object {
- "className": "",
+ "className": "mobile-active",
"href": "/fact_values",
"id": "menu_item_facts",
"isDivider": false,
"onClick": [Function],
"preventHref": true,
- "title": "Facts",
+ "title": undefined,
+ },
+ Object {
+ "className": "mobile-active",
+ "href": "/audits",
+ "id": "menu_item_audits",
+ "isDivider": false,
+ "onClick": [Function],
+ "preventHref": true,
+ "title": undefined,
+ },
+ Object {
+ "className": "mobile-active",
+ "href": "#",
+ "id": "menu_item_reports",
+ "isDivider": true,
+ "onClick": [Function],
+ "preventHref": true,
+ "title": undefined,
+ },
+ Object {
+ "className": "mobile-active",
+ "href": "/config_reports?search=eventful+%3D+true",
+ "id": "menu_item_config_management",
+ "isDivider": false,
+ "onClick": [Function],
+ "preventHref": true,
+ "title": undefined,
+ },
+ Object {
+ "className": "mobile-active",
+ "href": "/templates/report_templates",
+ "id": "menu_item_report_templates",
+ "isDivider": false,
+ "onClick": [Function],
+ "preventHref": true,
+ "title": undefined,
},
],
"title": "Monitor",
@@ -102,28 +189,72 @@ Array [
"iconClass": "fa fa-server",
"subItems": Array [
Object {
- "className": "",
- "href": "/hosts/new",
+ "className": "mobile-active",
+ "href": "/hosts",
"id": "menu_item_all_hosts",
"isDivider": false,
"onClick": [Function],
"preventHref": true,
- "title": "All Hosts",
+ "title": undefined,
+ },
+ Object {
+ "className": "mobile-active",
+ "href": "/hosts/new",
+ "id": "menu_item_create_host",
+ "isDivider": false,
+ "onClick": [Function],
+ "preventHref": true,
+ "title": undefined,
+ },
+ Object {
+ "className": "mobile-active",
+ "href": "#",
+ "id": "menu_item_provisioning_setup",
+ "isDivider": true,
+ "onClick": [Function],
+ "preventHref": true,
+ "title": undefined,
+ },
+ Object {
+ "className": "mobile-active",
+ "href": "/architectures",
+ "id": "menu_item_architectures",
+ "isDivider": false,
+ "onClick": [Function],
+ "preventHref": true,
+ "title": undefined,
},
],
"title": "Hosts",
},
Object {
"className": undefined,
- "iconClass": "pficon pficon-unplugged",
- "subItems": Array [],
- "title": "Empty",
+ "iconClass": "fa fa-wrench",
+ "subItems": Array [
+ Object {
+ "className": "mobile-active",
+ "href": "/hostgroups",
+ "id": "menu_item_host_groups",
+ "isDivider": false,
+ "onClick": [Function],
+ "preventHref": true,
+ "title": undefined,
+ },
+ Object {
+ "className": "mobile-active",
+ "href": "/common_parameters",
+ "id": "menu_item_global_parameters",
+ "isDivider": false,
+ "onClick": [Function],
+ "preventHref": true,
+ "title": undefined,
+ },
+ ],
+ "title": "Configure",
},
]
`;
-exports[`Layout selectors should return activeMenu 1`] = `"Hosts"`;
-
exports[`Layout selectors should return empty array of items 1`] = `Array []`;
exports[`Layout selectors should return isCollapsed from selector 1`] = `false`;
diff --git a/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/integration.test.js.snap b/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/integration.test.js.snap
deleted file mode 100644
index 0148e7b07e75..000000000000
--- a/webpack/assets/javascripts/react_app/components/Layout/__tests__/__snapshots__/integration.test.js.snap
+++ /dev/null
@@ -1,257 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Layout integration test should flow: Changed ActiveMenu to Hosts 1`] = `
-Object {
- "action": Array [
- Array [
- Object {
- "payload": Object {
- "activeMenu": "Hosts",
- },
- "type": "LAYOUT_CHANGE_ACTIVE",
- },
- ],
- ],
- "state": Object {
- "layout": Object {
- "activeMenu": "Hosts",
- "currentLocation": "london",
- "currentOrganization": "org1",
- "isCollapsed": false,
- "isLoading": false,
- "items": Array [
- Object {
- "children": Array [
- Object {
- "exact": true,
- "name": "Dashboard",
- "title": "Dashboard",
- "type": "item",
- "url": "/",
- },
- Object {
- "name": "Facts",
- "title": "Facts",
- "type": "item",
- "url": "/fact_values",
- },
- ],
- "className": "",
- "icon": "fa fa-tachometer",
- "name": "Monitor",
- "type": "sub_menu",
- },
- Object {
- "children": Array [
- Object {
- "name": "All Hosts",
- "title": "All Hosts",
- "type": "item",
- "url": "/hosts/new",
- },
- ],
- "className": "",
- "icon": "fa fa-server",
- "name": "Hosts",
- "type": "sub_menu",
- },
- Object {
- "children": Array [
- Object {
- "title": undefined,
- "type": "item",
- "url": "/nameless",
- },
- Object {
- "title": undefined,
- "type": "divider",
- },
- ],
- "className": "",
- "icon": "pficon pficon-unplugged",
- "name": "Empty",
- "type": "sub_menu",
- },
- Object {
- "children": Array [
- Object {
- "name": "Any Organization",
- "onClick": [Function],
- "title": "Any Organization",
- },
- Object {
- "name": "org1",
- "onClick": [Function],
- "title": "org1",
- "type": undefined,
- },
- Object {
- "name": "org2",
- "onClick": [Function],
- "title": "org2",
- "type": undefined,
- },
- ],
- "className": "organization-menu hidden-nav-lg",
- "icon": "fa fa-building",
- "name": "Organizations",
- "type": "sub_menu",
- },
- Object {
- "children": Array [
- Object {
- "name": "Any Location",
- "onClick": [Function],
- "title": "Any Location",
- },
- Object {
- "name": "yaml",
- "onClick": [Function],
- "title": "yaml",
- "type": undefined,
- },
- Object {
- "name": "london",
- "onClick": [Function],
- "title": "london",
- "type": undefined,
- },
- Object {
- "name": "norway",
- "onClick": [Function],
- "title": "norway",
- "type": undefined,
- },
- ],
- "className": "location-menu hidden-nav-lg",
- "icon": "fa fa-globe",
- "name": "Locations",
- "type": "sub_menu",
- },
- ],
- },
- },
-}
-`;
-
-exports[`Layout integration test should flow: initial state 1`] = `
-Object {
- "layout": Object {
- "activeMenu": "Monitor",
- "currentLocation": "london",
- "currentOrganization": "org1",
- "isCollapsed": false,
- "isLoading": false,
- "items": Array [
- Object {
- "children": Array [
- Object {
- "exact": true,
- "name": "Dashboard",
- "title": "Dashboard",
- "type": "item",
- "url": "/",
- },
- Object {
- "name": "Facts",
- "title": "Facts",
- "type": "item",
- "url": "/fact_values",
- },
- ],
- "className": "",
- "icon": "fa fa-tachometer",
- "name": "Monitor",
- "type": "sub_menu",
- },
- Object {
- "children": Array [
- Object {
- "name": "All Hosts",
- "title": "All Hosts",
- "type": "item",
- "url": "/hosts/new",
- },
- ],
- "className": "",
- "icon": "fa fa-server",
- "name": "Hosts",
- "type": "sub_menu",
- },
- Object {
- "children": Array [
- Object {
- "title": undefined,
- "type": "item",
- "url": "/nameless",
- },
- Object {
- "title": undefined,
- "type": "divider",
- },
- ],
- "className": "",
- "icon": "pficon pficon-unplugged",
- "name": "Empty",
- "type": "sub_menu",
- },
- Object {
- "children": Array [
- Object {
- "name": "Any Organization",
- "onClick": [Function],
- "title": "Any Organization",
- },
- Object {
- "name": "org1",
- "onClick": [Function],
- "title": "org1",
- "type": undefined,
- },
- Object {
- "name": "org2",
- "onClick": [Function],
- "title": "org2",
- "type": undefined,
- },
- ],
- "className": "organization-menu hidden-nav-lg",
- "icon": "fa fa-building",
- "name": "Organizations",
- "type": "sub_menu",
- },
- Object {
- "children": Array [
- Object {
- "name": "Any Location",
- "onClick": [Function],
- "title": "Any Location",
- },
- Object {
- "name": "yaml",
- "onClick": [Function],
- "title": "yaml",
- "type": undefined,
- },
- Object {
- "name": "london",
- "onClick": [Function],
- "title": "london",
- "type": undefined,
- },
- Object {
- "name": "norway",
- "onClick": [Function],
- "title": "norway",
- "type": undefined,
- },
- ],
- "className": "location-menu hidden-nav-lg",
- "icon": "fa fa-globe",
- "name": "Locations",
- "type": "sub_menu",
- },
- ],
- },
-}
-`;
diff --git a/webpack/assets/javascripts/react_app/components/Layout/__tests__/integration.test.js b/webpack/assets/javascripts/react_app/components/Layout/__tests__/integration.test.js
deleted file mode 100644
index 19536edc86ac..000000000000
--- a/webpack/assets/javascripts/react_app/components/Layout/__tests__/integration.test.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import React from 'react';
-import { BrowserRouter as Router } from 'react-router-dom';
-
-import { act } from 'react-dom/test-utils';
-import { IntegrationTestHelper } from '@theforeman/test';
-
-import { hasTaxonomiesMock } from '../Layout.fixtures';
-import Layout, { reducers } from '../index';
-import ForemanContext from '../../../Root/Context/ForemanContext';
-
-jest.mock('../../notifications', () => 'span');
-
-// mock the taxonomy context to match the test until we implement context setter
-
-jest
- .spyOn(ForemanContext, 'useForemanLocation')
- .mockReturnValue({ title: 'london' });
-jest
- .spyOn(ForemanContext, 'useForemanOrganization')
- .mockReturnValue({ title: 'org1' });
-
-afterEach(() => {
- jest.clearAllMocks();
-});
-describe('Layout integration test', () => {
- it('should flow', async () => {
- const integrationTestHelper = new IntegrationTestHelper(reducers);
- Object.defineProperty(window, 'location', {
- writable: true,
- value: { assign: jest.fn(), pathname: '/' },
- });
- const component = integrationTestHelper.mount(
-
-
-
- );
- await IntegrationTestHelper.flushAllPromises();
- component.update();
-
- integrationTestHelper.takeStoreSnapshot('initial state');
- expect(
- component
- .find('#location-dropdown .pf-c-context-selector__toggle-text')
- .text()
- ).toBe('london');
- expect(
- component
- .find('#organization-dropdown .pf-c-context-selector__toggle-text')
- .text()
- ).toBe('org1');
-
- const hostsMenuItem = component.find(
- '.pf-c-nav__item.pf-m-flyout > div > div'
- );
- await act(async () => {
- await hostsMenuItem.at(1).simulate('mouseover');
- });
- component.update();
- expect(hostsMenuItem.at(1).text()).toBe('Hosts');
- expect(component.find('.pf-c-menu.pf-m-flyout a').text()).toBe('All Hosts');
- });
-});
diff --git a/webpack/assets/javascripts/react_app/components/Layout/components/ImpersonateIcon/ImpersonateIcon.scss b/webpack/assets/javascripts/react_app/components/Layout/components/ImpersonateIcon/ImpersonateIcon.scss
index 3fe0312018a6..fee44434dbca 100644
--- a/webpack/assets/javascripts/react_app/components/Layout/components/ImpersonateIcon/ImpersonateIcon.scss
+++ b/webpack/assets/javascripts/react_app/components/Layout/components/ImpersonateIcon/ImpersonateIcon.scss
@@ -21,7 +21,7 @@
animation: blink normal 1s infinite ease-in-out; /* Opera and prob css3 final iteration */
}
-.navbar-pf-vertical {
+.pf-c-masthead {
.nav-item-iconic .blink-image {
color: $color-pf-gold-200;
}
diff --git a/webpack/assets/javascripts/react_app/components/Layout/components/LayoutContainer.js b/webpack/assets/javascripts/react_app/components/Layout/components/LayoutContainer.js
deleted file mode 100644
index 94a470e72694..000000000000
--- a/webpack/assets/javascripts/react_app/components/Layout/components/LayoutContainer.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import React, { useEffect } from 'react';
-import PropTypes from 'prop-types';
-
-const LayoutContainer = ({ isCollapsed, children }) => {
- const classes = 'react-container container-fluid nav-pf-persistent-secondary';
-
- useEffect(() => {
- if (isCollapsed) document.body.classList.add('collapsed-nav');
- else document.body.classList.remove('collapsed-nav');
- }, [isCollapsed]);
- return
{children}
;
-};
-
-LayoutContainer.propTypes = {
- isCollapsed: PropTypes.bool.isRequired,
- children: PropTypes.node,
-};
-
-LayoutContainer.defaultProps = {
- children: null,
-};
-
-export default LayoutContainer;
diff --git a/webpack/assets/javascripts/react_app/components/Layout/components/LayoutContainer.test.js b/webpack/assets/javascripts/react_app/components/Layout/components/LayoutContainer.test.js
deleted file mode 100644
index 2413beee6b4d..000000000000
--- a/webpack/assets/javascripts/react_app/components/Layout/components/LayoutContainer.test.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from 'react';
-import { mount, testComponentSnapshotsWithFixtures } from '@theforeman/test';
-
-import LayoutContainer from './LayoutContainer';
-
-const children = [
TEST];
-
-const fixtures = {
- 'render LayoutContainer': {
- isCollapsed: false,
- children,
- },
-};
-
-const removeClass = jest.fn();
-const addClass = jest.fn();
-global.document.body.classList.remove = removeClass;
-global.document.body.classList.add = addClass;
-
-describe('LayoutContainer', () => {
- describe('rendering', () =>
- testComponentSnapshotsWithFixtures(LayoutContainer, fixtures));
-
- it('LayoutContainer Collapsed', () => {
- mount(
{children});
- expect(addClass).toBeCalledWith('collapsed-nav');
- });
- it('LayoutContainer Not Collapsed', () => {
- mount(
{children});
- expect(removeClass).toBeCalledWith('collapsed-nav');
- });
-});
diff --git a/webpack/assets/javascripts/react_app/components/Layout/components/TaxonomySwitcher/TaxonomyDropdown.scss b/webpack/assets/javascripts/react_app/components/Layout/components/TaxonomySwitcher/TaxonomyDropdown.scss
index 9d106855d167..5f7e814d201c 100644
--- a/webpack/assets/javascripts/react_app/components/Layout/components/TaxonomySwitcher/TaxonomyDropdown.scss
+++ b/webpack/assets/javascripts/react_app/components/Layout/components/TaxonomySwitcher/TaxonomyDropdown.scss
@@ -1,6 +1,6 @@
@import '~@theforeman/vendor/scss/variables';
-.pf-c-page__header-tools-group {
+.pf-c-masthead .pf-c-toolbar {
.pf-c-context-selector__menu-search {
border-bottom: 0;
}
diff --git a/webpack/assets/javascripts/react_app/components/Layout/components/TaxonomySwitcher/TaxonomyDropdown.test.js b/webpack/assets/javascripts/react_app/components/Layout/components/TaxonomySwitcher/TaxonomyDropdown.test.js
deleted file mode 100644
index 8e645a99f0d2..000000000000
--- a/webpack/assets/javascripts/react_app/components/Layout/components/TaxonomySwitcher/TaxonomyDropdown.test.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import React from 'react';
-import { testComponentSnapshotsWithFixtures, shallow } from '@theforeman/test';
-import TaxonomyDropdown from './TaxonomyDropdown';
-import { hasTaxonomiesMock } from '../../Layout.fixtures';
-
-const props = {
- taxonomyType: 'organization',
- id: 'organization-dropdown',
- currentTaxonomy: hasTaxonomiesMock.currentOrganization,
- taxonomies: hasTaxonomiesMock.data.orgs.available_organizations,
- anyTaxonomyText: 'Any Organization',
- manageTaxonomyText: 'Manage Organizations',
- anyTaxonomyURL: '/organizations/clear',
- manageTaxonomyURL: '/organizations',
- isOpen: true,
-};
-
-const fixtures = {
- rendering: { ...props },
-};
-
-describe('TaxonomyDropdown', () => {
- describe('rendering', () =>
- testComponentSnapshotsWithFixtures(TaxonomyDropdown, fixtures));
-
- it('Search items', () => {
- const wrapper = shallow(
);
- const child = () => wrapper.children();
- expect(child()).toMatchSnapshot();
- wrapper.props().onSearchInputChange('', { target: { value: 'org1' } });
- wrapper.props().onSearchButtonClick();
- wrapper.update();
- expect(child()).toMatchSnapshot();
- });
-});
diff --git a/webpack/assets/javascripts/react_app/components/Layout/components/TaxonomySwitcher/TaxonomySwitcher.test.js b/webpack/assets/javascripts/react_app/components/Layout/components/TaxonomySwitcher/TaxonomySwitcher.test.js
index d7d5a9adb70e..07da396edd1d 100644
--- a/webpack/assets/javascripts/react_app/components/Layout/components/TaxonomySwitcher/TaxonomySwitcher.test.js
+++ b/webpack/assets/javascripts/react_app/components/Layout/components/TaxonomySwitcher/TaxonomySwitcher.test.js
@@ -1,18 +1,52 @@
-import { testComponentSnapshotsWithFixtures } from '../../../../common/testHelpers';
import TaxonomySwitcher from './TaxonomySwitcher';
-import { layoutMock } from '../../Layout.fixtures';
+import { layoutData } from '../../Layout.fixtures';
+import ForemanContext from '../../../../Root/Context/ForemanContext';
+import React from 'react';
+import { fireEvent, screen, render, act } from '@testing-library/react';
const props = {
- organizations: layoutMock.data.orgs.available_organizations,
- locations: layoutMock.data.locations.available_locations,
+ organizations: layoutData.orgs.available_organizations,
+ locations: layoutData.locations.available_locations,
isLoading: true,
};
-const fixtures = {
- 'render TaxonomySwitcher': { ...props },
-};
-
+jest
+ .spyOn(ForemanContext, 'useForemanLocation')
+ .mockReturnValue({ title: 'london' });
+jest
+ .spyOn(ForemanContext, 'useForemanOrganization')
+ .mockReturnValue({ title: 'org1' });
+const assign = jest.fn();
+Object.defineProperty(window, 'location', {
+ value: {
+ assign,
+ },
+ writable: true,
+});
describe('TaxonomySwitcher', () => {
- describe('rendering', () =>
- testComponentSnapshotsWithFixtures(TaxonomySwitcher, fixtures));
+ it('should switch orgs and locations', async () => {
+ render(
);
+ expect(screen.getAllByText('london')).toHaveLength(1);
+ expect(screen.getAllByText('org1')).toHaveLength(1);
+ await act(async () => {
+ fireEvent.click(screen.getByText('london'));
+ });
+ expect(screen.getAllByText('london')).toHaveLength(2);
+ expect(screen.getAllByText('norway')).toHaveLength(1);
+ await act(async () => {
+ fireEvent.click(screen.getByText('norway'));
+ });
+ expect(assign).toHaveBeenLastCalledWith('/locations/3-norway/select');
+
+ await act(async () => {
+ fireEvent.click(screen.getByText('org1'));
+ });
+ expect(screen.getAllByText('org1')).toHaveLength(2);
+ expect(screen.getAllByText('org2')).toHaveLength(1);
+
+ await act(async () => {
+ fireEvent.click(screen.getByText('org2'));
+ });
+ expect(assign).toHaveBeenLastCalledWith('/organizations/2-org2/select');
+ });
});
diff --git a/webpack/assets/javascripts/react_app/components/Layout/components/TaxonomySwitcher/__snapshots__/TaxonomyDropdown.test.js.snap b/webpack/assets/javascripts/react_app/components/Layout/components/TaxonomySwitcher/__snapshots__/TaxonomyDropdown.test.js.snap
deleted file mode 100644
index 3c4cce1a42de..000000000000
--- a/webpack/assets/javascripts/react_app/components/Layout/components/TaxonomySwitcher/__snapshots__/TaxonomyDropdown.test.js.snap
+++ /dev/null
@@ -1,329 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`TaxonomyDropdown Search items 1`] = `
-Array [
-
-
-
-
-
-
- Any organization
-
-
-
- ,
-
-
-
- org1
-
-
-
-
-
- ,
-
-
-
- org2
-
-
-
- ,
-]
-`;
-
-exports[`TaxonomyDropdown Search items 2`] = `
-Array [
-
-
-
-
-
-
- Any organization
-
-
-
- ,
-
-
-
- org1
-
-
-
-
-
- ,
-]
-`;
-
-exports[`TaxonomyDropdown rendering rendering 1`] = `
-
-
-
- }
- id="organization-dropdown"
- isFlipEnabled={true}
- isOpen={false}
- isPlain={false}
- isText={false}
- menuAppendTo="inline"
- onSearchButtonClick={[Function]}
- onSearchInputChange={[Function]}
- onSelect={[Function]}
- onToggle={[Function]}
- ouiaSafe={true}
- removeFindDomNode={false}
- screenReaderLabel="Selected Taxonomy:"
- searchButtonAriaLabel="Search menu items"
- searchInputPlaceholder="Search"
- searchInputValue=""
- toggleText="org1"
- zIndex={9999}
->
-
-
-
-
-
-
- Any organization
-
-
-
-
-
-
-
- org1
-
-
-
-
-
-
-
-
-
- org2
-
-
-
-
-
-`;
diff --git a/webpack/assets/javascripts/react_app/components/Layout/components/TaxonomySwitcher/__snapshots__/TaxonomySwitcher.test.js.snap b/webpack/assets/javascripts/react_app/components/Layout/components/TaxonomySwitcher/__snapshots__/TaxonomySwitcher.test.js.snap
deleted file mode 100644
index 42865c40f0af..000000000000
--- a/webpack/assets/javascripts/react_app/components/Layout/components/TaxonomySwitcher/__snapshots__/TaxonomySwitcher.test.js.snap
+++ /dev/null
@@ -1,54 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`TaxonomySwitcher rendering render TaxonomySwitcher 1`] = `
-
-
-
-
-
-
-
-
-
-`;
diff --git a/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/Header.js b/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/Header.js
new file mode 100644
index 000000000000..68454a6ab16c
--- /dev/null
+++ b/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/Header.js
@@ -0,0 +1,57 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {
+ Brand,
+ Masthead,
+ MastheadToggle,
+ MastheadMain,
+ MastheadBrand,
+ MastheadContent,
+ Button,
+} from '@patternfly/react-core';
+import { BarsIcon } from '@patternfly/react-icons';
+
+import {
+ layoutPropTypes,
+ layoutDefaultProps,
+ dataPropType,
+} from '../../LayoutHelper';
+import HeaderToolbar from './HeaderToolbar';
+
+const Header = ({
+ data: { logo, brand, root, ...props },
+ onNavToggle,
+ isLoading,
+}) => (
+
+
+
+
+
+
+
+
+
+ {brand}
+
+
+
+
+
+
+
+
+);
+
+Header.propTypes = {
+ data: PropTypes.shape(dataPropType).isRequired,
+ isLoading: layoutPropTypes.isLoading,
+ onNavToggle: PropTypes.func.isRequired,
+};
+
+Header.defaultProps = {
+ isLoading: layoutDefaultProps.isLoading,
+};
+export default Header;
diff --git a/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/HeaderToolbar.js b/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/HeaderToolbar.js
index 90840097eb2e..08ab75d80229 100644
--- a/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/HeaderToolbar.js
+++ b/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/HeaderToolbar.js
@@ -1,9 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
- PageHeaderTools,
- PageHeaderToolsGroup,
- PageHeaderToolsItem,
+ Toolbar,
+ ToolbarContent,
+ ToolbarGroup,
+ ToolbarItem,
} from '@patternfly/react-core';
import TaxonomySwitcher from '../TaxonomySwitcher/TaxonomySwitcher';
import UserDropdowns from './UserDropdowns';
@@ -28,32 +29,34 @@ const HeaderToolbar = ({
instance_title: instanceTitle,
isLoading,
}) => (
-
-
-
-
-
-
-
-
-
-
-
- {user.impersonated_by && (
-
-
-
- )}
+
+
+
+
+
+
+
+
+
+
+
+
+ {user.impersonated_by && (
+
+
+
+ )}
-
-
-
-
-
+
+
+
+
+
+
);
HeaderToolbar.propTypes = {
stop_impersonation_url: PropTypes.string.isRequired,
diff --git a/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/HeaderToolbar.scss b/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/HeaderToolbar.scss
index ed1b4ad94171..a474003e9ff3 100644
--- a/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/HeaderToolbar.scss
+++ b/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/HeaderToolbar.scss
@@ -1,19 +1,7 @@
@import '../../../../common/variables.scss';
#data-toolbar {
- grid-column: 2 / 3;
- margin-left: 0;
- display: flex;
- justify-content: space-between;
- flex-wrap: wrap;
-
- .pf-c-page__header-tools-item {
- margin-left: 10px;
- }
-
- .pf-c-spinner {
- margin-left: 10px;
- }
+ background-color: unset;
@media (max-width: $header-max-width) {
justify-content: flex-end;
@@ -23,12 +11,22 @@
}
}
-@media (max-width: 768px) {
- .navbar-pf-vertical {
- display: flex;
+.pf-c-masthead__brand {
+ padding-right: 15px;
- .pf-c-page__header {
- grid-template-columns: 1fr 3fr;
- }
+ &:hover {
+ text-decoration: none;
+ }
+
+ .pf-c-brand {
+ height: 34px;
+ }
+
+ .navbar-brand-txt span {
+ color: white;
+ text-transform: uppercase;
+ font-weight: bold;
+ font-size: 20px;
+ font-family: 'Open Sans', Helvetica, Arial, sans-serif;
}
}
diff --git a/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/HeaderToolbar.test.js b/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/HeaderToolbar.test.js
index f0da4ca3e03d..6c029c077d8d 100644
--- a/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/HeaderToolbar.test.js
+++ b/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/HeaderToolbar.test.js
@@ -10,7 +10,6 @@ const fixtures = {
currentLocation: hasTaxonomiesMock.currentLocation,
currentOrganization: hasTaxonomiesMock.currentOrganization,
isLoading: false,
- changeActiveMenu: noop,
},
};
diff --git a/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/__snapshots__/HeaderToolbar.test.js.snap b/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/__snapshots__/HeaderToolbar.test.js.snap
index 5b46635d4fff..fc338091ed96 100644
--- a/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/__snapshots__/HeaderToolbar.test.js.snap
+++ b/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/__snapshots__/HeaderToolbar.test.js.snap
@@ -1,114 +1,125 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`HeaderToolbar rendering render HeaderToolbar 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
+
+
+
+
+
+
+
+
+
-
-
-
+ />
+
+
+
+
`;
diff --git a/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/__snapshots__/UserDropdowns.test.js.snap b/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/__snapshots__/UserDropdowns.test.js.snap
index 7bd5ff6d77a8..2be8c6b188ec 100644
--- a/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/__snapshots__/UserDropdowns.test.js.snap
+++ b/webpack/assets/javascripts/react_app/components/Layout/components/Toolbar/__snapshots__/UserDropdowns.test.js.snap
@@ -8,7 +8,6 @@ exports[`UserDropdown render 1`] = `
My Account
,
diff --git a/webpack/assets/javascripts/react_app/components/Layout/components/__snapshots__/LayoutContainer.test.js.snap b/webpack/assets/javascripts/react_app/components/Layout/components/__snapshots__/LayoutContainer.test.js.snap
deleted file mode 100644
index 374b66c5defd..000000000000
--- a/webpack/assets/javascripts/react_app/components/Layout/components/__snapshots__/LayoutContainer.test.js.snap
+++ /dev/null
@@ -1,13 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`LayoutContainer rendering render LayoutContainer 1`] = `
-
-
- TEST
-
-
-`;
diff --git a/webpack/assets/javascripts/react_app/components/Layout/index.js b/webpack/assets/javascripts/react_app/components/Layout/index.js
index ba4f58726166..5bd6bf49c5f2 100644
--- a/webpack/assets/javascripts/react_app/components/Layout/index.js
+++ b/webpack/assets/javascripts/react_app/components/Layout/index.js
@@ -1,21 +1,19 @@
-import React, { useEffect } from 'react';
+import React, { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';
import {
initializeLayout,
- changeActiveMenu,
collapseLayoutMenus,
expandLayoutMenus,
} from './LayoutActions';
import reducer from './LayoutReducer';
import {
patternflyMenuItemsSelector,
- selectActiveMenu,
selectIsLoading,
selectIsCollapsed,
} from './LayoutSelectors';
-import { combineMenuItems, getActiveMenuItem } from './LayoutHelper';
+import { combineMenuItems } from './LayoutHelper';
import { getIsNavbarCollapsed } from './LayoutSessionStorage';
import {
useForemanOrganization,
@@ -33,7 +31,6 @@ const ConnectedLayout = ({ children, data }) => {
dispatch(
initializeLayout({
items: combineMenuItems(data),
- activeMenu: getActiveMenuItem(data.menu).title,
isCollapsed: getIsNavbarCollapsed(),
organization: data.orgs.current_org,
location: data.locations.current_location,
@@ -41,13 +38,23 @@ const ConnectedLayout = ({ children, data }) => {
);
}, [data, dispatch]);
+ const isNavCollapsed = useSelector(state => selectIsCollapsed(state));
+ useEffect(() => {
+ // toggles a class in the body tag, so that the main #rails-app-content container can have the appropriate width
+ if (isNavCollapsed) {
+ document.body.classList.remove('pf-m-expanded');
+ } else {
+ document.body.classList.add('pf-m-expanded');
+ }
+ }, [isNavCollapsed]);
const { push: navigate } = useHistory();
const items = useSelector(state =>
patternflyMenuItemsSelector(state, currentLocation, currentOrganization)
);
const isLoading = useSelector(state => selectIsLoading(state));
const isCollapsed = useSelector(state => selectIsCollapsed(state));
- const activeMenu = useSelector(state => selectActiveMenu(state));
+
+ const [navigationActiveItem, setNavigationActiveItem] = useState(null);
return (
{
items={items}
isLoading={isLoading}
isCollapsed={isCollapsed}
- activeMenu={activeMenu}
- changeActiveMenu={menu => dispatch(changeActiveMenu(menu))}
- collapseLayoutMenus={() => dispatch(collapseLayoutMenus())}
+ collapseLayoutMenus={() => {
+ setNavigationActiveItem(null);
+ dispatch(collapseLayoutMenus());
+ }}
expandLayoutMenus={() => dispatch(expandLayoutMenus())}
+ navigationActiveItem={navigationActiveItem}
+ setNavigationActiveItem={setNavigationActiveItem}
>
{children}
diff --git a/webpack/assets/javascripts/react_app/components/Layout/layout.scss b/webpack/assets/javascripts/react_app/components/Layout/layout.scss
index 35358d1048ca..9731d06422df 100644
--- a/webpack/assets/javascripts/react_app/components/Layout/layout.scss
+++ b/webpack/assets/javascripts/react_app/components/Layout/layout.scss
@@ -1,91 +1,26 @@
-@import '~@theforeman/vendor/scss/variables';
@import '../../common/colors.scss';
@import '../../common/variables.scss';
-.secondary-nav-item-pf > .nav-pf-secondary-nav {
- background-color: $nav-pf-vertical-active-bg-color;
+.react-page #foreman-main-container {
+ height: 100%;
+}
+#foreman-main-container {
+ height: fit-content;
+}
- .mobile-active {
- // Mark active Location/Org in Mobile view
- a {
- .list-group-item-value {
- background-color: $nav-pf-vertical-secondary-active-bg-color;
- }
+.collapsed-nav {
+ #react-app-root {
+ main.pf-c-page__main {
+ padding-left: 0;
}
}
-
- .list-group-item {
- a {
- background-color: $nav-pf-vertical-active-bg-color;
-
- &:hover {
- .list-group-item-value {
- background-color: $nav-pf-vertical-secondary-active-bg-color;
- }
- }
- }
-
- span {
- color: $nav-pf-vertical-active-icon-color;
- }
+ #rails-app-content {
+ margin-left: 0;
}
}
-#react-app-root {
- .react-container {
- margin-right: auto;
- margin-left: 200px;
- padding-left: 20px;
- padding-right: 20px;
- }
-
- .nav-pf-vertical {
- background-color: $nav-pf-vertical-secondary-active-bg-color;
-
- .list-group {
- .list-group-item {
- a {
- span {
- color: $nav-pf-vertical-icon-color;
- }
-
- &:hover {
- background-color: $nav-pf-vertical-active-bg-color;
-
- span {
- color: $nav-pf-vertical-active-icon-color;
- }
- }
-
- &::after {
- color: $nav-pf-vertical-icon-color;
- }
- }
-
- &:hover {
- a {
- background-color: $nav-pf-vertical-active-bg-color;
-
- span {
- color: $nav-pf-vertical-active-icon-color;
- }
- }
- }
-
- &.active {
- a {
- background-color: $nav-pf-vertical-active-bg-color;
-
- span {
- color: $nav-pf-vertical-active-icon-color;
- }
- }
- }
- }
- }
- }
-
- .secondary-nav-item-pf.hidden-nav-lg {
+#page-sidebar {
+ .pf-c-nav__item.hidden-nav-lg {
display: none;
@media (max-width: $header-max-width) {
@@ -94,53 +29,59 @@
}
}
-/*
- patternfly-sass hides the vertical-nav on smaller screens, see: https://github.com/patternfly/patternfly-sass/blob/5e7cce3445d5b1af1e16500695b9f33edc6a4f6c/assets/javascripts/patternfly-functions.js#L260
- the react-container needs to be shifted when it happens.
-*/
-@media only screen and (max-width: $pf-global--breakpoint--md) {
- #react-app-root .react-container,
- #rails-app-content {
- margin-left: 0;
+.pf-c-nav {
+ .nav-title-icon {
+ padding-right: 10px;
+ }
+ .nav-title {
+ pointer-events: none;
+ }
+ .pf-c-nav__item .pf-c-nav__item.pf-m-expandable .pf-c-nav__list::before {
+ border: 0;
+ }
+ .pf-c-nav__item.pf-m-expandable::before {
+ z-index: var(--pf-c-page__sidebar--ZIndex);
+ }
+ .pf-c-nav__item .pf-c-nav__item.pf-m-expandable::before {
+ // in small screens, the expandable items have css to add a second border
+ border-bottom: none;
}
}
-#account_menu {
- outline: none;
-}
-
-#vertical-spinner {
- margin-top: 15px;
- margin-left: 5px;
-}
+.pf-c-nav {
+ .nav-title-icon {
+ pointer-events: none;
+ padding-right: 10px;
+ }
-.navbar-header {
- width: 42px;
+ .nav-title {
+ pointer-events: none;
+ }
}
-#navbar-header {
- left: 42px;
+.pf-c-page {
+ --pf-c-page--BackgroundColor: var(--pf-global--BackgroundColor--100);
}
-#data-toolbar {
- padding: 0;
- background-color: transparent;
-}
+.pf-c-page .pf-c-masthead {
+ background-repeat: no-repeat;
+ background-size: cover;
-.pf-c-page {
- background-color: rgba(0, 0, 0, 0);
-}
+ .pf-c-toolbar__item {
+ .pf-c-context-selector__menu {
+ top: auto;
+ }
-.collapsed-nav {
- #react-app-root .react-container,
- #rails-app-content {
- margin-left: 75px;
- }
+ .pf-c-context-selector {
+ &.pf-m-expanded,
+ &:hover {
+ background: rgba(255, 255, 255, 0.24);
+ }
+ }
- @media only screen and (max-width: $pf-global--breakpoint--md) {
- #react-app-root .react-container,
- #rails-app-content {
- margin-left: 0;
+ .pf-c-context-selector__toggle::before {
+ border-width: 0;
}
}
+
}
diff --git a/webpack/assets/javascripts/react_app/components/PF4/TableIndexPage/TableIndexPage.js b/webpack/assets/javascripts/react_app/components/PF4/TableIndexPage/TableIndexPage.js
index 1455b7933208..2aa8920cf6c5 100644
--- a/webpack/assets/javascripts/react_app/components/PF4/TableIndexPage/TableIndexPage.js
+++ b/webpack/assets/javascripts/react_app/components/PF4/TableIndexPage/TableIndexPage.js
@@ -107,7 +107,7 @@ const TableIndexPage = ({
].filter(item => item);
return (
-
+
{header}
diff --git a/webpack/assets/javascripts/react_app/components/PF4/TableIndexPage/TableIndexPage.scss b/webpack/assets/javascripts/react_app/components/PF4/TableIndexPage/TableIndexPage.scss
index 61abdd93e69d..6b07052dcbdc 100644
--- a/webpack/assets/javascripts/react_app/components/PF4/TableIndexPage/TableIndexPage.scss
+++ b/webpack/assets/javascripts/react_app/components/PF4/TableIndexPage/TableIndexPage.scss
@@ -1,7 +1,4 @@
-.pf-c-page.foreman-page {
- display: block;
- margin-right: -20px; // Overriding .container-fluid padding
- margin-left: -20px; // Overriding .container-fluid padding
+.foreman-page {
.table-toolbar {
padding: 0;
.pf-c-toolbar__content{
diff --git a/webpack/assets/javascripts/react_app/routes/Audits/AuditsPage/__tests__/__snapshots__/AuditsPage.test.js.snap b/webpack/assets/javascripts/react_app/routes/Audits/AuditsPage/__tests__/__snapshots__/AuditsPage.test.js.snap
index 0d0d5495e109..321daf64a7d2 100644
--- a/webpack/assets/javascripts/react_app/routes/Audits/AuditsPage/__tests__/__snapshots__/AuditsPage.test.js.snap
+++ b/webpack/assets/javascripts/react_app/routes/Audits/AuditsPage/__tests__/__snapshots__/AuditsPage.test.js.snap
@@ -8,6 +8,7 @@ exports[`AuditsPage rendering render audits page 1`] = `
header="Audits"
isLoading={false}
onSearch={[Function]}
+ pageSectionType="default"
searchProps={
Object {
"autocomplete": Object {
@@ -80,6 +81,7 @@ exports[`AuditsPage rendering render audits page w/empty audits 1`] = `
header="Audits"
isLoading={false}
onSearch={[Function]}
+ pageSectionType="default"
searchProps={
Object {
"autocomplete": Object {
@@ -154,6 +156,7 @@ exports[`AuditsPage rendering render audits page w/error 1`] = `
header="Audits"
isLoading={false}
onSearch={[Function]}
+ pageSectionType="default"
searchProps={
Object {
"autocomplete": Object {
@@ -228,6 +231,7 @@ exports[`AuditsPage rendering render loading audits page 1`] = `
header="Audits"
isLoading={false}
onSearch={[Function]}
+ pageSectionType="default"
searchProps={
Object {
"autocomplete": Object {
diff --git a/webpack/assets/javascripts/react_app/routes/FiltersForm/FilterForm.scss b/webpack/assets/javascripts/react_app/routes/FiltersForm/FilterForm.scss
index 260ceffb68fc..b58b9814a77f 100644
--- a/webpack/assets/javascripts/react_app/routes/FiltersForm/FilterForm.scss
+++ b/webpack/assets/javascripts/react_app/routes/FiltersForm/FilterForm.scss
@@ -1,7 +1,3 @@
.filter-form {
max-width: 880px;
}
-
-#react-content {
- padding-bottom: 50px;
-}
diff --git a/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/index.js b/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/index.js
index e2b94c94d7df..dcc94bdd4df4 100644
--- a/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/index.js
+++ b/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/index.js
@@ -21,7 +21,7 @@ import {
useForemanVersion,
} from '../../../Root/Context/ForemanContext';
import { STATUS } from '../../../constants';
-import Head from '../../../components/Head';
+import PageLayout from '../../common/PageLayout/PageLayout';
import Slot from '../../../components/common/Slot';
import {
@@ -172,10 +172,7 @@ const RegistrationCommandsPage = () => {
}, [dispatch, hostGroupId, operatingSystemId]);
return (
- <>
-
-
{__('Register Host')}
-
+
- >
+
);
};
diff --git a/webpack/assets/javascripts/react_app/routes/RoutingService.js b/webpack/assets/javascripts/react_app/routes/RoutingService.js
index 6ca1cb51b1b0..9f463dcc070a 100644
--- a/webpack/assets/javascripts/react_app/routes/RoutingService.js
+++ b/webpack/assets/javascripts/react_app/routes/RoutingService.js
@@ -55,4 +55,5 @@ const updatePath = newPath => {
const removeRailsContent = () => {
const railsContainer = document.getElementById('rails-app-content');
if (railsContainer) railsContainer.remove();
+ document.body.classList.add('react-page');
};
diff --git a/webpack/assets/javascripts/react_app/routes/common/PageLayout/PageLayout.js b/webpack/assets/javascripts/react_app/routes/common/PageLayout/PageLayout.js
index a791b9f0092f..10f74ab51ff3 100644
--- a/webpack/assets/javascripts/react_app/routes/common/PageLayout/PageLayout.js
+++ b/webpack/assets/javascripts/react_app/routes/common/PageLayout/PageLayout.js
@@ -1,8 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { Row, Col, Spinner } from 'patternfly-react';
+import { Col, Spinner } from 'patternfly-react';
+import {
+ PageSection,
+ PageSectionVariants,
+ TextContent,
+ Text,
+} from '@patternfly/react-core';
import { changeQuery } from '../../../common/urlHelpers';
-
import BreadcrumbBar from '../../../components/BreadcrumbBar';
import SearchBar from '../../../components/SearchBar';
import Head from '../../../components/Head';
@@ -18,50 +23,58 @@ const PageLayout = ({
header,
beforeToolbarComponent,
isLoading,
+ pageSectionType,
children,
}) => (
-
-
-
-
{header}
-
+ <>
+
+
{header}
+
+
{!breadcrumbOptions && (
-
-
{header}
-
+
+ {header}
+
)}
{customBreadcrumbs ||
(breadcrumbOptions &&
)}
- {beforeToolbarComponent}
-
-
- {searchable && (
-
- )}
-
-
-
-
- {isLoading && (
-
-
-
+
+
+ {(searchable || beforeToolbarComponent || isLoading || toolbarButtons) && (
+
+ {beforeToolbarComponent}
+
+
+ {searchable && (
+
)}
- {toolbarButtons}
-
-
-
+
+
+
+
+ {isLoading && (
+
+
+
+ )}
+ {toolbarButtons}
+
+
+
+
+ )}
+
{children}
-
-
+
+ >
);
PageLayout.propTypes = {
@@ -111,6 +124,7 @@ PageLayout.propTypes = {
searchQuery: PropTypes.string,
beforeToolbarComponent: PropTypes.node,
isLoading: PropTypes.bool,
+ pageSectionType: PropTypes.string,
};
PageLayout.defaultProps = {
@@ -123,6 +137,7 @@ PageLayout.defaultProps = {
isLoading: false,
onSearch: searchQuery => changeQuery({ search: searchQuery.trim(), page: 1 }),
beforeToolbarComponent: null,
+ pageSectionType: 'default',
};
export default PageLayout;
diff --git a/webpack/assets/javascripts/react_app/routes/common/PageLayout/PageLayout.test.js b/webpack/assets/javascripts/react_app/routes/common/PageLayout/PageLayout.test.js
index 5d79214a3d2b..63ac196debff 100644
--- a/webpack/assets/javascripts/react_app/routes/common/PageLayout/PageLayout.test.js
+++ b/webpack/assets/javascripts/react_app/routes/common/PageLayout/PageLayout.test.js
@@ -1,29 +1,110 @@
import React from 'react';
-import { testComponentSnapshotsWithFixtures } from '../../../common/testHelpers';
+import { Provider } from 'react-redux';
+import configureMockStore from 'redux-mock-store';
+import { BrowserRouter as Router } from 'react-router-dom';
+import { render, screen } from '@testing-library/react';
+
+import thunk from 'redux-thunk';
import PageLayout from './PageLayout';
+import '@testing-library/jest-dom';
import { pageLayoutMock } from './PageLayout.fixtures';
+import { initMockStore } from '../../../common/testHelpers';
+
+const mockStore = configureMockStore([thunk]);
+
+const store = mockStore({ ...initMockStore });
jest.unmock('react-helmet');
+describe('PageLayout', () => {
+ it('should render header text', () => {
+ const header = 'My Header';
+ const { getByText } = render(
+
+
+
+ Content
+
+
+
+ );
+ const headerElement = getByText(header);
+ expect(headerElement).toBeInTheDocument();
+ expect(screen.queryAllByLabelText('Search')).toHaveLength(0);
+ });
+
+ it('should have Search', () => {
+ const onSearchMock = jest.fn();
+ const { getByLabelText } = render(
+
+
+
+ Content
+
+
+
+ );
+ expect(getByLabelText('Search input')).toBeInTheDocument();
+ expect(getByLabelText('Search')).toBeInTheDocument();
+ });
+
+ it('should render custom breadcrumbs', () => {
+ const customBreadcrumbs =
test Breadcrumbs
;
+ const { getByText } = render(
+
+
+
+ Content
+
+
+
+ );
+ const breadcrumbsElement = getByText('test Breadcrumbs');
+ expect(breadcrumbsElement).toBeInTheDocument();
+ });
+
+ it('should render toolbar buttons', () => {
+ const toolbarButtons =
;
+ const { getByText } = render(
+
+
+
+ Content
+
+
+
+ );
+ const buttonElement = getByText('test Button');
+ expect(buttonElement).toBeInTheDocument();
+ });
-const pageLayoutFixtures = {
- 'render pageLayout w/search': pageLayoutMock,
- 'render pageLayout without search': { ...pageLayoutMock, searchable: false },
- 'render pageLayout with custom breadcrumbs': {
- ...pageLayoutMock,
- customBreadcrumbs:
customBreadcrumbs
,
- },
- 'render pageLayout without breadcrumbs': {
- ...pageLayoutMock,
- breadcrumbOptions: null,
- },
- 'render pageLayout w/toolBar': {
- ...pageLayoutMock,
- toolbarButtons:
,
- },
- 'render pageLayout w/beforeToolbarComponent': {
- ...pageLayoutMock,
- beforeToolbarComponent:
beforeToolbarComponent
,
- },
-};
-
-testComponentSnapshotsWithFixtures(PageLayout, pageLayoutFixtures);
+ it('should render content', () => {
+ const { getByText } = render(
+
+
+
+ Content
+
+
+
+ );
+ const contentElement = getByText('Content');
+ expect(contentElement).toBeInTheDocument();
+ });
+});
diff --git a/webpack/assets/javascripts/react_app/routes/common/PageLayout/__snapshots__/PageLayout.test.js.snap b/webpack/assets/javascripts/react_app/routes/common/PageLayout/__snapshots__/PageLayout.test.js.snap
deleted file mode 100644
index 846b214609b5..000000000000
--- a/webpack/assets/javascripts/react_app/routes/common/PageLayout/__snapshots__/PageLayout.test.js.snap
+++ /dev/null
@@ -1,490 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`render pageLayout w/beforeToolbarComponent 1`] = `
-
-
-
-
-
-
-
-
-
- beforeToolbarComponent
-
-
-
-
-
-
-
-
-
-
- body
-
-
-`;
-
-exports[`render pageLayout w/search 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- body
-
-
-`;
-
-exports[`render pageLayout w/toolBar 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- body
-
-
-`;
-
-exports[`render pageLayout with custom breadcrumbs 1`] = `
-
-
-
-
-
-
-
- customBreadcrumbs
-
-
-
-
-
-
-
-
-
-
-
- body
-
-
-`;
-
-exports[`render pageLayout without breadcrumbs 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- body
-
-
-`;
-
-exports[`render pageLayout without search 1`] = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- body
-
-
-`;