Skip to content

Commit 51103c5

Browse files
authored
Update Home Page content after cache hit (#2230) (#2453)
* Update Home Page content after cache hit (#2230) * Update Home Page content after cache hit (#2230) - Remove redundant loading flag and null response on no data * PWA-471: Update Home Page content after cache hit (#2230) - Add page loading state to app context - Add loading icon into header - Trigger page loading from CMS root component * PWA-471: Update Home Page content after cache hit (#2230) * PWA-471: Update Home Page content after cache hit (#2230) - Add test coverage for new isPageLoading in talon & header * PWA-471: Update Home Page content after cache hit (#2230) * PWA-471: Update Home Page content after cache hit (#2230) * PWA-471: Update Home Page content after cache hit (#2230)
1 parent e91b345 commit 51103c5

File tree

12 files changed

+234
-9
lines changed

12 files changed

+234
-9
lines changed

packages/peregrine/lib/store/actions/app/actions.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ const actionTypes = [
77
'SET_OFFLINE',
88
'TOGGLE_SEARCH',
99
'EXECUTE_SEARCH',
10-
'MARK_ERROR_HANDLED'
10+
'MARK_ERROR_HANDLED',
11+
'SET_PAGE_LOADING'
1112
];
1213

1314
export default createActions(...actionTypes, { prefix });

packages/peregrine/lib/store/reducers/app.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ const initialState = {
1010
isOnline: navigator.onLine,
1111
overlay: false,
1212
searchOpen: false,
13-
pending: {}
13+
pending: {},
14+
isPageLoading: false
1415
};
1516

1617
const reducerMap = {
@@ -39,6 +40,12 @@ const reducerMap = {
3940
isOnline: false,
4041
hasBeenOffline: true
4142
};
43+
},
44+
[actions.setPageLoading]: (state, { payload }) => {
45+
return {
46+
...state,
47+
isPageLoading: !!payload
48+
};
4249
}
4350
};
4451

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import React, { useEffect } from 'react';
2+
import { useAppContext } from '@magento/peregrine/lib/context/app';
3+
import { useHeader } from '../useHeader';
4+
import { createTestInstance } from '../../../index';
5+
import { act } from 'react-test-renderer';
6+
7+
jest.mock('@magento/peregrine/lib/context/app', () => {
8+
const api = {};
9+
const state = {};
10+
return {
11+
useAppContext: jest.fn(() => [state, api])
12+
};
13+
});
14+
15+
const log = jest.fn();
16+
const Component = () => {
17+
const talonProps = useHeader();
18+
19+
useEffect(() => {
20+
log(talonProps);
21+
}, [talonProps]);
22+
23+
return <div {...talonProps} id={'header'} />;
24+
};
25+
26+
test('useHeader returns correct values from useAppContext', () => {
27+
useAppContext.mockImplementation(() => {
28+
return [
29+
{
30+
hasBeenOffline: false,
31+
isOnline: true,
32+
searchOpen: false,
33+
isPageLoading: false
34+
},
35+
{
36+
toggleSearch: jest.fn()
37+
}
38+
];
39+
});
40+
41+
createTestInstance(<Component />);
42+
43+
expect(log).toHaveBeenCalledWith({
44+
handleSearchTriggerClick: expect.any(Function),
45+
hasBeenOffline: false,
46+
isOnline: true,
47+
isPageLoading: false,
48+
searchOpen: false
49+
});
50+
});
51+
52+
test('handleSearchTriggerClick calls toggleSearch from useAppContext', () => {
53+
const toggleSearchFn = jest.fn();
54+
55+
useAppContext.mockImplementation(() => {
56+
return [
57+
{},
58+
{
59+
toggleSearch: toggleSearchFn
60+
}
61+
];
62+
});
63+
64+
const component = createTestInstance(<Component />);
65+
const talonProps = component.root.findByProps({ id: 'header' }).props;
66+
67+
act(() => {
68+
talonProps.handleSearchTriggerClick();
69+
});
70+
71+
expect(toggleSearchFn).toHaveBeenCalled();
72+
});

packages/peregrine/lib/talons/Header/useHeader.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useAppContext } from '@magento/peregrine/lib/context/app';
33

44
export const useHeader = () => {
55
const [
6-
{ hasBeenOffline, isOnline, searchOpen },
6+
{ hasBeenOffline, isOnline, isPageLoading, searchOpen },
77
{ toggleSearch }
88
] = useAppContext();
99

@@ -15,6 +15,7 @@ export const useHeader = () => {
1515
handleSearchTriggerClick,
1616
hasBeenOffline,
1717
isOnline,
18-
searchOpen
18+
searchOpen,
19+
isPageLoading
1920
};
2021
};

packages/venia-ui/lib/RootComponents/CMS/cms.js

+15-4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Meta, Title } from '../../components/Head';
99
import { mergeClasses } from '../../classify';
1010

1111
import defaultClasses from './cms.css';
12+
import { useAppContext } from '@magento/peregrine/lib/context/app';
1213

1314
const CMSPage = props => {
1415
const { id } = props;
@@ -17,8 +18,15 @@ const CMSPage = props => {
1718
variables: {
1819
id: Number(id),
1920
onServer: false
20-
}
21+
},
22+
fetchPolicy: 'cache-and-network'
2123
});
24+
const [
25+
{ isPageLoading },
26+
{
27+
actions: { setPageLoading }
28+
}
29+
] = useAppContext();
2230

2331
if (error) {
2432
if (process.env.NODE_ENV !== 'production') {
@@ -27,12 +35,15 @@ const CMSPage = props => {
2735
return <div>Page Fetch Error</div>;
2836
}
2937

30-
if (loading) {
38+
if (!data) {
3139
return fullPageLoadingIndicator;
3240
}
3341

34-
if (!data) {
35-
return null;
42+
// Ensure we mark the page as loading while we check the network for updates
43+
if (loading && !isPageLoading) {
44+
setPageLoading(true);
45+
} else if (!loading && isPageLoading) {
46+
setPageLoading(false);
3647
}
3748

3849
const { content_heading, title } = data.cmsPage;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`verify Header can render in default state 1`] = `
4+
<header
5+
className="closed"
6+
>
7+
<div
8+
className="toolbar"
9+
>
10+
<div
11+
className="primaryActions"
12+
>
13+
<NavTrigger />
14+
</div>
15+
<OnlineIndicator
16+
hasBeenOffline={false}
17+
isOnline={true}
18+
/>
19+
<div
20+
className="secondaryActions"
21+
>
22+
<SearchTrigger
23+
active={false}
24+
onClick={[MockFunction]}
25+
/>
26+
<CartTrigger />
27+
</div>
28+
</div>
29+
</header>
30+
`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React from 'react';
2+
import { createTestInstance } from '@magento/peregrine';
3+
import Header from '../header';
4+
import { useHeader } from '@magento/peregrine/lib/talons/Header/useHeader';
5+
6+
jest.mock('../../../classify');
7+
jest.mock('../../Logo', () => 'Logo');
8+
jest.mock('../cartTrigger', () => 'CartTrigger');
9+
jest.mock('../navTrigger', () => 'NavTrigger');
10+
jest.mock('../searchTrigger', () => 'SearchTrigger');
11+
jest.mock('../onlineIndicator', () => 'OnlineIndicator');
12+
jest.mock('../../PageLoadingIndicator', () => () => (
13+
<div id={'pageLoadingIndicator'} />
14+
));
15+
16+
jest.mock('@magento/venia-drivers', () => ({
17+
resourceUrl: jest.fn(url => url),
18+
Link: jest.fn(() => null),
19+
Route: jest.fn(() => null)
20+
}));
21+
22+
jest.mock('@magento/peregrine/lib/talons/Header/useHeader', () => {
23+
const state = {
24+
handleSearchTriggerClick: jest.fn(),
25+
hasBeenOffline: false,
26+
isOnline: true,
27+
searchOpen: false,
28+
isPageLoading: false
29+
};
30+
return {
31+
useHeader: jest.fn(() => state)
32+
};
33+
});
34+
35+
test('verify Header can render in default state', () => {
36+
const component = createTestInstance(<Header />);
37+
38+
expect(component.toJSON()).toMatchSnapshot();
39+
});
40+
41+
test('verify PageLoadingIndicator is displayed when page is loading', () => {
42+
useHeader.mockImplementation(() => {
43+
return {
44+
handleSearchTriggerClick: jest.fn(),
45+
hasBeenOffline: false,
46+
isOnline: true,
47+
searchOpen: false,
48+
isPageLoading: true
49+
};
50+
});
51+
52+
const component = createTestInstance(<Header />);
53+
component.root.findByProps({ id: 'pageLoadingIndicator' });
54+
});

packages/venia-ui/lib/components/Header/header.css

+3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
.primaryActions {
4040
grid-area: primary;
4141
justify-self: start;
42+
display: grid;
43+
grid-auto-flow: column;
44+
align-items: center;
4245
}
4346

4447
.secondaryActions {

packages/venia-ui/lib/components/Header/header.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { useHeader } from '@magento/peregrine/lib/talons/Header/useHeader';
1212

1313
import { mergeClasses } from '../../classify';
1414
import defaultClasses from './header.css';
15+
import PageLoadingIndicator from '../PageLoadingIndicator';
1516

1617
const SearchBar = React.lazy(() => import('../SearchBar'));
1718

@@ -20,7 +21,8 @@ const Header = props => {
2021
handleSearchTriggerClick,
2122
hasBeenOffline,
2223
isOnline,
23-
searchOpen
24+
searchOpen,
25+
isPageLoading
2426
} = useHeader();
2527

2628
const classes = mergeClasses(defaultClasses, props.classes);
@@ -39,12 +41,16 @@ const Header = props => {
3941
</Route>
4042
</Suspense>
4143
) : null;
44+
const pageLoadingIndicator = isPageLoading ? (
45+
<PageLoadingIndicator />
46+
) : null;
4247

4348
return (
4449
<header className={rootClass}>
4550
<div className={classes.toolbar}>
4651
<div className={classes.primaryActions}>
4752
<NavTrigger />
53+
{pageLoadingIndicator}
4854
</div>
4955
<OnlineIndicator
5056
hasBeenOffline={hasBeenOffline}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './indicator';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.root {
2+
justify-self: start;
3+
animation: spin 1920ms linear infinite;
4+
}
5+
6+
.indicator {
7+
--stroke: rgb(var(--venia-text-hint));
8+
}
9+
10+
@keyframes spin {
11+
0% {
12+
transform: rotate(0deg);
13+
}
14+
100% {
15+
transform: rotate(360deg);
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react';
2+
3+
import defaultClasses from './indicator.css';
4+
import { mergeClasses } from '../../classify';
5+
import { RotateCw as LoaderIcon } from 'react-feather';
6+
import Icon from '../Icon';
7+
8+
const PageLoadingIndicator = props => {
9+
const classes = mergeClasses(defaultClasses, props.classes);
10+
11+
return (
12+
<div className={classes.root}>
13+
<Icon
14+
src={LoaderIcon}
15+
size={24}
16+
classes={{ root: classes.indicator }}
17+
/>
18+
</div>
19+
);
20+
};
21+
22+
export default PageLoadingIndicator;

0 commit comments

Comments
 (0)