Skip to content

Commit

Permalink
Merge branch 'develop' into tommy/lighthouse-render-time
Browse files Browse the repository at this point in the history
  • Loading branch information
dpatil-magento authored Jan 27, 2021
2 parents 0438925 + acf7807 commit 8557107
Show file tree
Hide file tree
Showing 57 changed files with 1,150 additions and 241 deletions.
7 changes: 7 additions & 0 deletions packages/peregrine/lib/Apollo/attachClientToStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { extraArgument } from '../store/middleware/thunk';

const attachClientToStore = apolloClient => {
Object.assign(extraArgument, { apolloClient });
};

export default attachClientToStore;
10 changes: 3 additions & 7 deletions packages/peregrine/lib/context/cart.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { createContext, useContext, useEffect, useMemo } from 'react';
import { connect } from 'react-redux';
import { useApolloClient, useMutation } from '@apollo/client';
import { useMutation } from '@apollo/client';
import gql from 'graphql-tag';

import { useAwaitQuery } from '@magento/peregrine/lib/hooks/useAwaitQuery';
Expand Down Expand Up @@ -55,20 +55,16 @@ const CartContextProvider = props => {
derivedCartState
]);

const apolloClient = useApolloClient();
const [fetchCartId] = useMutation(CREATE_CART_MUTATION);
const fetchCartDetails = useAwaitQuery(CART_DETAILS_QUERY);

useEffect(() => {
// cartApi.getCartDetails initializes the cart if there isn't one. Also, we pass
// apolloClient to wipe the store in event of auth token expiry which
// will only happen if the user refreshes.
// cartApi.getCartDetails initializes the cart if there isn't one.
cartApi.getCartDetails({
apolloClient,
fetchCartId,
fetchCartDetails
});
}, [apolloClient, cartApi, fetchCartDetails, fetchCartId]);
}, [cartApi, fetchCartDetails, fetchCartId]);

return (
<CartContext.Provider value={contextValue}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,13 @@ const getState = jest.fn(() => ({
cart: { cartId: 'CART_ID' },
user: { isSignedIn: false }
}));
const thunkArgs = [dispatch, getState];
const thunkArgs = [
dispatch,
getState,
{
apolloClient: {}
}
];

describe('createCart', () => {
test('it returns a thunk', () => {
Expand Down
13 changes: 5 additions & 8 deletions packages/peregrine/lib/store/actions/cart/asyncActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,9 @@ export const removeItemFromCart = payload => {
};

export const getCartDetails = payload => {
const { apolloClient, fetchCartId, fetchCartDetails } = payload;
const { fetchCartId, fetchCartDetails } = payload;

return async function thunk(dispatch, getState) {
return async function thunk(dispatch, getState, { apolloClient }) {
const { cart, user } = getState();
const { cartId } = cart;
const { isSignedIn } = user;
Expand Down Expand Up @@ -367,13 +367,10 @@ export const getCartDetails = payload => {
await dispatch(removeCart());
}

// Clear the cart data from apollo client if we get here and
// have an apolloClient.
if (apolloClient) {
await clearCartDataFromCache(apolloClient);
}
// Clear cart data from Apollo cache
await clearCartDataFromCache(apolloClient);

// Create a new one
// Create a new cart
try {
await dispatch(
createCart({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ const dispatch = jest.fn();
const getState = jest.fn(() => ({
user: { isSignedIn: false }
}));
const thunkArgs = [dispatch, getState];
const thunkArgs = [
dispatch,
getState,
{
apolloClient: {}
}
];

const fetchUserDetails = jest
.fn()
.mockResolvedValue({ data: { customer: {} } });
Expand Down Expand Up @@ -102,7 +109,7 @@ describe('signOut', () => {
});

test('signOut thunk invokes revokeToken and dispatchs actions', async () => {
await signOut({ revokeToken })(dispatch);
await signOut({ revokeToken })(...thunkArgs);

expect(revokeToken).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledTimes(3);
Expand All @@ -112,7 +119,7 @@ describe('signOut', () => {
const consoleSpy = jest.spyOn(console, 'error');
revokeToken.mockRejectedValueOnce(new Error('Revoke Token Error'));

await signOut({ revokeToken })(dispatch);
await signOut({ revokeToken })(...thunkArgs);

expect(revokeToken).toHaveBeenCalledTimes(1);
expect(consoleSpy).toHaveBeenCalledTimes(1);
Expand Down
6 changes: 5 additions & 1 deletion packages/peregrine/lib/store/actions/user/asyncActions.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import BrowserPersistence from '../../../util/simplePersistence';
import { clearCartDataFromCache } from '../../../Apollo/clearCartDataFromCache';
import { clearCustomerDataFromCache } from '../../../Apollo/clearCustomerDataFromCache';
import { removeCart } from '../cart';
import { clearCheckoutDataFromStorage } from '../checkout';

Expand All @@ -7,7 +9,7 @@ import actions from './actions';
const storage = new BrowserPersistence();

export const signOut = (payload = {}) =>
async function thunk(dispatch) {
async function thunk(dispatch, getState, { apolloClient }) {
const { revokeToken } = payload;

if (revokeToken) {
Expand All @@ -23,6 +25,8 @@ export const signOut = (payload = {}) =>
await dispatch(clearToken());
await dispatch(actions.reset());
await clearCheckoutDataFromStorage();
await clearCartDataFromCache(apolloClient);
await clearCustomerDataFromCache(apolloClient);

// Now that we're signed out, forget the old (customer) cart.
// We don't need to create a new cart here because we're going to refresh
Expand Down
5 changes: 3 additions & 2 deletions packages/peregrine/lib/store/enhancers/middleware.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

import auth from '../middleware/auth';
import log from '../middleware/log';
import thunk from '../middleware/thunk';

const middleware = [thunk];
const middleware = [thunk, auth];

if (process.env.NODE_ENV !== 'production') {
middleware.push(log);
Expand Down
82 changes: 82 additions & 0 deletions packages/peregrine/lib/store/middleware/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import BrowserPersistence from '../../util/simplePersistence';
import userActions, { signOut } from '../actions/user';

const timeouts = new Map();
const intervals = new Map();
const storage = new BrowserPersistence();
const SET_TOKEN = userActions.setToken.toString();
const CLEAR_TOKEN = userActions.clearToken.toString();
const GET_DETAILS = userActions.getDetails.request.toString();

const isSigningIn = type => type === SET_TOKEN || type === GET_DETAILS;
const isSigningOut = type => type === CLEAR_TOKEN;

/**
* This function adheres to Redux's middleware pattern.
*
* @param {Store} store The store to augment.
* @returns {Function}
*/
const scheduleSignOut = store => next => action => {
const { dispatch } = store;

if (isSigningIn(action.type)) {
// `BrowserPersistence.getItem()` only returns the value
// but we need the full item with timestamp and ttl
const item = storage.getRawItem('signin_token');

// exit if there's nothing in storage
if (!item) return next(action);

const { timeStored, ttl, value } = JSON.parse(item);
const parsedValue = JSON.parse(value);
const preciseTTL = ttl * 1000;
const elapsed = Date.now() - timeStored;
const expiry = Math.max(preciseTTL - elapsed, 0);

// establish a sign-out routine
const callback = () => {
dispatch(signOut()).then(() => {
timeouts.delete(parsedValue);
intervals.delete(parsedValue);

// refresh the page, important for checkout
history.go(0);
});
};

// set a timeout that runs once when the token expires
if (!timeouts.has(parsedValue)) {
const timeoutId = setTimeout(callback, expiry);

timeouts.set(parsedValue, timeoutId);
}

// then set an interval that runs once per second
// on mobile, the timeout won't fire if the tab is inactive
if (!intervals.has(parsedValue)) {
const intervalId = setInterval(() => {
const hasExpired = Date.now() - timeStored > preciseTTL;

if (hasExpired) callback();
}, 1000);

intervals.set(parsedValue, intervalId);
}
} else if (isSigningOut(action.type)) {
for (const timeoutId of timeouts) {
clearTimeout(timeoutId);
}

for (const intervalId of intervals) {
clearInterval(intervalId);
}

timeouts.clear();
intervals.clear();
}

return next(action);
};

export default scheduleSignOut;
4 changes: 4 additions & 0 deletions packages/peregrine/lib/store/middleware/thunk.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import thunk from 'redux-thunk';

export const extraArgument = {};
export default thunk.withExtraArgument(extraArgument);
9 changes: 2 additions & 7 deletions packages/peregrine/lib/talons/AuthModal/useAuthModal.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { useCallback, useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { useApolloClient, useMutation } from '@apollo/client';
import { useMutation } from '@apollo/client';

import mergeOperations from '../../util/shallowMerge';
import { clearCartDataFromCache } from '../../Apollo/clearCartDataFromCache';
import { clearCustomerDataFromCache } from '../../Apollo/clearCustomerDataFromCache';
import { useUserContext } from '../../context/user';
import DEFAULT_OPERATIONS from './authModal.gql';

Expand Down Expand Up @@ -48,7 +46,6 @@ export const useAuthModal = props => {
const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
const { signOutMutation } = operations;

const apolloClient = useApolloClient();
const [isSigningOut, setIsSigningOut] = useState(false);
const [username, setUsername] = useState('');
const [{ currentUser, isSignedIn }, { signOut }] = useUserContext();
Expand Down Expand Up @@ -89,14 +86,12 @@ export const useAuthModal = props => {

// Delete cart/user data from the redux store.
await signOut({ revokeToken });
await clearCartDataFromCache(apolloClient);
await clearCustomerDataFromCache(apolloClient);

// Refresh the page as a way to say "re-initialize". An alternative
// would be to call apolloClient.resetStore() but that would require
// a large refactor.
history.go(0);
}, [apolloClient, history, revokeToken, signOut]);
}, [history, revokeToken, signOut]);

return {
handleCancel,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`it returns the proper shape 1`] = `
<i
talonProps={
Object {
"errorMessage": "",
"handleEditItem": [Function],
"handleRemoveFromCart": [Function],
"handleToggleFavorites": [Function],
"handleUpdateItemQuantity": [Function],
"isEditable": false,
"isFavorite": false,
"product": Object {
"currency": "USD",
"image": "test.webp",
"name": "unit test",
"options": Array [],
"quantity": 7,
"stockStatus": undefined,
"unitPrice": 99,
"urlKey": undefined,
"urlSuffix": undefined,
},
}
}
/>
`;

exports[`it returns the proper shape when use variant image is configured 1`] = `
<i
talonProps={
Object {
"errorMessage": "",
"handleEditItem": [Function],
"handleRemoveFromCart": [Function],
"handleToggleFavorites": [Function],
"handleUpdateItemQuantity": [Function],
"isEditable": true,
"isFavorite": false,
"product": Object {
"currency": "USD",
"image": "variant1.webp",
"name": "unit test",
"options": Array [
Object {
"id": 22,
"option_label": "Color",
"value_id": 2,
"value_label": "red",
},
],
"quantity": 7,
"stockStatus": undefined,
"unitPrice": 99,
"urlKey": undefined,
"urlSuffix": undefined,
},
}
}
/>
`;
Loading

0 comments on commit 8557107

Please sign in to comment.