diff --git a/packages/peregrine/lib/store/actions/app/actions.js b/packages/peregrine/lib/store/actions/app/actions.js
index 7595d60fad..d062e3db6e 100644
--- a/packages/peregrine/lib/store/actions/app/actions.js
+++ b/packages/peregrine/lib/store/actions/app/actions.js
@@ -7,7 +7,8 @@ const actionTypes = [
'SET_OFFLINE',
'TOGGLE_SEARCH',
'EXECUTE_SEARCH',
- 'MARK_ERROR_HANDLED'
+ 'MARK_ERROR_HANDLED',
+ 'SET_PAGE_LOADING'
];
export default createActions(...actionTypes, { prefix });
diff --git a/packages/peregrine/lib/store/reducers/app.js b/packages/peregrine/lib/store/reducers/app.js
index 1970bcced4..199b32a703 100644
--- a/packages/peregrine/lib/store/reducers/app.js
+++ b/packages/peregrine/lib/store/reducers/app.js
@@ -10,7 +10,8 @@ const initialState = {
isOnline: navigator.onLine,
overlay: false,
searchOpen: false,
- pending: {}
+ pending: {},
+ isPageLoading: false
};
const reducerMap = {
@@ -39,6 +40,12 @@ const reducerMap = {
isOnline: false,
hasBeenOffline: true
};
+ },
+ [actions.setPageLoading]: (state, { payload }) => {
+ return {
+ ...state,
+ isPageLoading: !!payload
+ };
}
};
diff --git a/packages/peregrine/lib/talons/Header/__tests__/useHeader.spec.js b/packages/peregrine/lib/talons/Header/__tests__/useHeader.spec.js
new file mode 100644
index 0000000000..d2ba370674
--- /dev/null
+++ b/packages/peregrine/lib/talons/Header/__tests__/useHeader.spec.js
@@ -0,0 +1,72 @@
+import React, { useEffect } from 'react';
+import { useAppContext } from '@magento/peregrine/lib/context/app';
+import { useHeader } from '../useHeader';
+import { createTestInstance } from '../../../index';
+import { act } from 'react-test-renderer';
+
+jest.mock('@magento/peregrine/lib/context/app', () => {
+ const api = {};
+ const state = {};
+ return {
+ useAppContext: jest.fn(() => [state, api])
+ };
+});
+
+const log = jest.fn();
+const Component = () => {
+ const talonProps = useHeader();
+
+ useEffect(() => {
+ log(talonProps);
+ }, [talonProps]);
+
+ return
;
+};
+
+test('useHeader returns correct values from useAppContext', () => {
+ useAppContext.mockImplementation(() => {
+ return [
+ {
+ hasBeenOffline: false,
+ isOnline: true,
+ searchOpen: false,
+ isPageLoading: false
+ },
+ {
+ toggleSearch: jest.fn()
+ }
+ ];
+ });
+
+ createTestInstance();
+
+ expect(log).toHaveBeenCalledWith({
+ handleSearchTriggerClick: expect.any(Function),
+ hasBeenOffline: false,
+ isOnline: true,
+ isPageLoading: false,
+ searchOpen: false
+ });
+});
+
+test('handleSearchTriggerClick calls toggleSearch from useAppContext', () => {
+ const toggleSearchFn = jest.fn();
+
+ useAppContext.mockImplementation(() => {
+ return [
+ {},
+ {
+ toggleSearch: toggleSearchFn
+ }
+ ];
+ });
+
+ const component = createTestInstance();
+ const talonProps = component.root.findByProps({ id: 'header' }).props;
+
+ act(() => {
+ talonProps.handleSearchTriggerClick();
+ });
+
+ expect(toggleSearchFn).toHaveBeenCalled();
+});
diff --git a/packages/peregrine/lib/talons/Header/useHeader.js b/packages/peregrine/lib/talons/Header/useHeader.js
index 50d3c528c2..a640740185 100644
--- a/packages/peregrine/lib/talons/Header/useHeader.js
+++ b/packages/peregrine/lib/talons/Header/useHeader.js
@@ -3,7 +3,7 @@ import { useAppContext } from '@magento/peregrine/lib/context/app';
export const useHeader = () => {
const [
- { hasBeenOffline, isOnline, searchOpen },
+ { hasBeenOffline, isOnline, isPageLoading, searchOpen },
{ toggleSearch }
] = useAppContext();
@@ -15,6 +15,7 @@ export const useHeader = () => {
handleSearchTriggerClick,
hasBeenOffline,
isOnline,
- searchOpen
+ searchOpen,
+ isPageLoading
};
};
diff --git a/packages/venia-ui/lib/RootComponents/CMS/cms.js b/packages/venia-ui/lib/RootComponents/CMS/cms.js
index 6545af0241..e9e377fdfa 100644
--- a/packages/venia-ui/lib/RootComponents/CMS/cms.js
+++ b/packages/venia-ui/lib/RootComponents/CMS/cms.js
@@ -9,6 +9,7 @@ import { Meta, Title } from '../../components/Head';
import { mergeClasses } from '../../classify';
import defaultClasses from './cms.css';
+import { useAppContext } from '@magento/peregrine/lib/context/app';
const CMSPage = props => {
const { id } = props;
@@ -17,8 +18,15 @@ const CMSPage = props => {
variables: {
id: Number(id),
onServer: false
- }
+ },
+ fetchPolicy: 'cache-and-network'
});
+ const [
+ { isPageLoading },
+ {
+ actions: { setPageLoading }
+ }
+ ] = useAppContext();
if (error) {
if (process.env.NODE_ENV !== 'production') {
@@ -27,12 +35,15 @@ const CMSPage = props => {
return Page Fetch Error
;
}
- if (loading) {
+ if (!data) {
return fullPageLoadingIndicator;
}
- if (!data) {
- return null;
+ // Ensure we mark the page as loading while we check the network for updates
+ if (loading && !isPageLoading) {
+ setPageLoading(true);
+ } else if (!loading && isPageLoading) {
+ setPageLoading(false);
}
const { content_heading, title } = data.cmsPage;
diff --git a/packages/venia-ui/lib/components/Header/__tests__/__snapshots__/header.spec.js.snap b/packages/venia-ui/lib/components/Header/__tests__/__snapshots__/header.spec.js.snap
new file mode 100644
index 0000000000..799935f6e1
--- /dev/null
+++ b/packages/venia-ui/lib/components/Header/__tests__/__snapshots__/header.spec.js.snap
@@ -0,0 +1,30 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`verify Header can render in default state 1`] = `
+
+`;
diff --git a/packages/venia-ui/lib/components/Header/__tests__/header.spec.js b/packages/venia-ui/lib/components/Header/__tests__/header.spec.js
new file mode 100644
index 0000000000..a24b65e1b0
--- /dev/null
+++ b/packages/venia-ui/lib/components/Header/__tests__/header.spec.js
@@ -0,0 +1,54 @@
+import React from 'react';
+import { createTestInstance } from '@magento/peregrine';
+import Header from '../header';
+import { useHeader } from '@magento/peregrine/lib/talons/Header/useHeader';
+
+jest.mock('../../../classify');
+jest.mock('../../Logo', () => 'Logo');
+jest.mock('../cartTrigger', () => 'CartTrigger');
+jest.mock('../navTrigger', () => 'NavTrigger');
+jest.mock('../searchTrigger', () => 'SearchTrigger');
+jest.mock('../onlineIndicator', () => 'OnlineIndicator');
+jest.mock('../../PageLoadingIndicator', () => () => (
+
+));
+
+jest.mock('@magento/venia-drivers', () => ({
+ resourceUrl: jest.fn(url => url),
+ Link: jest.fn(() => null),
+ Route: jest.fn(() => null)
+}));
+
+jest.mock('@magento/peregrine/lib/talons/Header/useHeader', () => {
+ const state = {
+ handleSearchTriggerClick: jest.fn(),
+ hasBeenOffline: false,
+ isOnline: true,
+ searchOpen: false,
+ isPageLoading: false
+ };
+ return {
+ useHeader: jest.fn(() => state)
+ };
+});
+
+test('verify Header can render in default state', () => {
+ const component = createTestInstance();
+
+ expect(component.toJSON()).toMatchSnapshot();
+});
+
+test('verify PageLoadingIndicator is displayed when page is loading', () => {
+ useHeader.mockImplementation(() => {
+ return {
+ handleSearchTriggerClick: jest.fn(),
+ hasBeenOffline: false,
+ isOnline: true,
+ searchOpen: false,
+ isPageLoading: true
+ };
+ });
+
+ const component = createTestInstance();
+ component.root.findByProps({ id: 'pageLoadingIndicator' });
+});
diff --git a/packages/venia-ui/lib/components/Header/header.css b/packages/venia-ui/lib/components/Header/header.css
index 5230924ba6..06e3af5ac1 100755
--- a/packages/venia-ui/lib/components/Header/header.css
+++ b/packages/venia-ui/lib/components/Header/header.css
@@ -39,6 +39,9 @@
.primaryActions {
grid-area: primary;
justify-self: start;
+ display: grid;
+ grid-auto-flow: column;
+ align-items: center;
}
.secondaryActions {
diff --git a/packages/venia-ui/lib/components/Header/header.js b/packages/venia-ui/lib/components/Header/header.js
index ee49397b4e..d624f227cc 100644
--- a/packages/venia-ui/lib/components/Header/header.js
+++ b/packages/venia-ui/lib/components/Header/header.js
@@ -12,6 +12,7 @@ import { useHeader } from '@magento/peregrine/lib/talons/Header/useHeader';
import { mergeClasses } from '../../classify';
import defaultClasses from './header.css';
+import PageLoadingIndicator from '../PageLoadingIndicator';
const SearchBar = React.lazy(() => import('../SearchBar'));
@@ -20,7 +21,8 @@ const Header = props => {
handleSearchTriggerClick,
hasBeenOffline,
isOnline,
- searchOpen
+ searchOpen,
+ isPageLoading
} = useHeader();
const classes = mergeClasses(defaultClasses, props.classes);
@@ -39,12 +41,16 @@ const Header = props => {
) : null;
+ const pageLoadingIndicator = isPageLoading ? (
+
+ ) : null;
return (
+ {pageLoadingIndicator}
{
+ const classes = mergeClasses(defaultClasses, props.classes);
+
+ return (
+
+
+
+ );
+};
+
+export default PageLoadingIndicator;