Skip to content
This repository was archived by the owner on Feb 23, 2024. It is now read-only.

Commit 06356ff

Browse files
authored
Ensure cart changes remain after using back button to get back to the cart (#3960)
* Add cartUpdate middleware * Include timestamp for when cart data was generated * Add getCartFromApi action * Check whether cart can be hydrated or needs to be fetched from API * Add cartDataIsStale action and remove getCartFromApi action * Add isCartDataStale selector * Don't load cart until staleness check is complete * Add comment to ease worry about locaStorage execution * Correct doc block and fix typographical error * Cater for lastCartUpdate or the timestamp being undefined * Include @woocommerce/api and @woocommerce/e2e-utils * Add getNormalPagePermalink test utility This will allow us to get the permalink of a "normal" page, i.e. one that is not using the block editor. * Add getBlockPagePermalink test utility This will get the permalink for a page using the Block editor. * Emit action to update cart staleness in all execution paths * Add visitPostOfType function This will allow us to visit the editor page for a post based on its name and post type. * Add functions to get permalinks from editor pages * Add front-end tests for cart * Update package-lock.json * Create local function to ensure the page permalink is visible * Change functions for getting permalinks They were often failing for unknown reasons, these _somehow_ make them more stable. * Add logging for GitHub actions * Add more logging for tests * Add more logging for tests * Add more logging for tests * Wait for networkidle on back * Remove logging except timestamp * Wait for timestamp to be added to localStorage * Split tests to make runs slightly shorter * Wait for add to cart request before continuing test * Fix formatting in cart.test.js * Rename cartDataStale to setIsCartDataStale * Create constant for localStorage timestamp name * Rename cartDataIsStale to isCartDataStale This is for consistency, the action to change this is called setIsCartDataStale - I think this reads better. * Use cleaner logic when determining if the cart should render or fetch * Change docblock for isCartDataStale selector * Remove boolean cast from SET_IS_CART_DATA_STALE reducer * Set longer timeouts for frontend cart tests * Enclose cart staleness checks in condition/prevent unnecessary execution * Remove unnecessary boolean cast from selector * Use constant for local storage key in tests * Use new localstorage key in tests. cant access constants in page context
1 parent b21656d commit 06356ff

File tree

18 files changed

+487
-2
lines changed

18 files changed

+487
-2
lines changed

assets/js/data/cart/action-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const ACTION_TYPES = {
66
REMOVING_COUPON: 'REMOVING_COUPON',
77
RECEIVE_CART_ITEM: 'RECEIVE_CART_ITEM',
88
ITEM_PENDING_QUANTITY: 'ITEM_PENDING_QUANTITY',
9+
SET_IS_CART_DATA_STALE: 'SET_IS_CART_DATA_STALE',
910
RECEIVE_REMOVED_ITEM: 'RECEIVE_REMOVED_ITEM',
1011
UPDATING_CUSTOMER_DATA: 'UPDATING_CUSTOMER_DATA',
1112
UPDATING_SELECTED_SHIPPING_RATE: 'UPDATING_SELECTED_SHIPPING_RATE',

assets/js/data/cart/actions.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,18 @@ export const itemIsPendingDelete = (
122122
cartItemKey,
123123
isPendingDelete,
124124
} as const );
125+
/**
126+
* Returns an action object to mark the cart data in the store as stale.
127+
*
128+
* @param {boolean} [isCartDataStale=true] Flag to mark cart data as stale; true if
129+
* lastCartUpdate timestamp is newer than the
130+
* one in wcSettings.
131+
*/
132+
export const setIsCartDataStale = ( isCartDataStale = true ) =>
133+
( {
134+
type: types.SET_IS_CART_DATA_STALE,
135+
isCartDataStale,
136+
} as const );
125137

126138
/**
127139
* Returns an action object used to track when customer data is being updated
@@ -435,6 +447,7 @@ export type CartAction = ReturnOrGeneratorYieldUnion<
435447
| typeof itemIsPendingDelete
436448
| typeof updatingCustomerData
437449
| typeof shippingRatesBeingSelected
450+
| typeof cartDataIsStale
438451
| typeof updateCustomerData
439452
| typeof removeItemFromCart
440453
| typeof changeCartItemQuantity

assets/js/data/cart/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ export const CART_API_ERROR = {
1414
status: 500,
1515
},
1616
};
17+
export const LAST_CART_UPDATE_TIMESTAMP_KEY = 'wc-blocks_cart_update_timestamp';

assets/js/data/cart/reducers.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,16 @@ const reducer: Reducer< CartState > = (
149149
updatingSelectedRate: !! action.isResolving,
150150
},
151151
};
152+
break;
153+
case types.SET_IS_CART_DATA_STALE:
154+
state = {
155+
...state,
156+
metaData: {
157+
...state.metaData,
158+
isCartDataStale: action.isCartDataStale,
159+
},
160+
};
161+
break;
152162
}
153163
return state;
154164
};

assets/js/data/cart/selectors.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@ export const isApplyingCoupon = ( state: CartState ): boolean => {
5959
return !! state.metaData.applyingCoupon;
6060
};
6161

62+
/**
63+
* Returns true if cart is stale, false if it is not.
64+
*
65+
* @param {CartState} state The current state.
66+
* @return {boolean} True if the cart data is stale.
67+
*/
68+
export const isCartDataStale = ( state: CartState ): boolean => {
69+
return state.metaData.isCartDataStale;
70+
};
71+
6272
/**
6373
* Retrieves the coupon code currently being applied.
6474
*

assets/js/data/default-states.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export const defaultCartState: CartState = {
8282
updatingSelectedRate: false,
8383
applyingCoupon: '',
8484
removingCoupon: '',
85+
isCartDataStale: false,
8586
},
8687
errors: [],
8788
};

assets/js/hocs/with-store-cart-api-hydration.js

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
import { useRef } from '@wordpress/element';
55
import { getSetting } from '@woocommerce/settings';
66
import { CART_STORE_KEY } from '@woocommerce/block-data';
7-
import { useSelect } from '@wordpress/data';
7+
import { useSelect, useDispatch } from '@wordpress/data';
8+
9+
/**
10+
* Internal dependencies
11+
*/
12+
import { LAST_CART_UPDATE_TIMESTAMP_KEY } from '../data/cart/constants';
813

914
/**
1015
* Hydrate Cart API data.
@@ -13,13 +18,47 @@ import { useSelect } from '@wordpress/data';
1318
*/
1419
const useStoreCartApiHydration = () => {
1520
const cartData = useRef( getSetting( 'cartData' ) );
21+
const { setIsCartDataStale } = useDispatch( CART_STORE_KEY );
1622

1723
useSelect( ( select, registry ) => {
1824
if ( ! cartData.current ) {
1925
return;
2026
}
27+
const { isResolving, hasFinishedResolution, isCartDataStale } = select(
28+
CART_STORE_KEY
29+
);
2130

22-
const { isResolving, hasFinishedResolution } = select( CART_STORE_KEY );
31+
/**
32+
* This should only execute once. When the code further down the file executes
33+
* then the condition directly below this comment should never evaluate to true
34+
* on subsequent executions. Because of this localStorage.getItem won't be
35+
* called multiple times.
36+
*/
37+
if (
38+
! isCartDataStale() &&
39+
! isResolving( 'getCartData' ) &&
40+
! hasFinishedResolution( 'getCartData', [] )
41+
) {
42+
const lastCartUpdateRaw = window.localStorage.getItem(
43+
LAST_CART_UPDATE_TIMESTAMP_KEY
44+
);
45+
46+
if ( lastCartUpdateRaw ) {
47+
const lastCartUpdate = parseFloat( lastCartUpdateRaw );
48+
const cartGeneratedTimestamp = parseFloat(
49+
cartData.current.generated_timestamp
50+
);
51+
52+
const needsUpdateFromAPI =
53+
! isNaN( cartGeneratedTimestamp ) &&
54+
! isNaN( lastCartUpdate ) &&
55+
lastCartUpdate > cartGeneratedTimestamp;
56+
57+
if ( needsUpdateFromAPI ) {
58+
setIsCartDataStale();
59+
}
60+
}
61+
}
2362
const {
2463
receiveCart,
2564
receiveError,
@@ -28,6 +67,7 @@ const useStoreCartApiHydration = () => {
2867
} = registry.dispatch( CART_STORE_KEY );
2968

3069
if (
70+
! isCartDataStale() &&
3171
! isResolving( 'getCartData', [] ) &&
3272
! hasFinishedResolution( 'getCartData', [] )
3373
) {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import apiFetch, { APIFetchOptions } from '@wordpress/api-fetch';
5+
6+
/**
7+
* Internal dependencies
8+
*/
9+
import { LAST_CART_UPDATE_TIMESTAMP_KEY } from '../data/cart/constants';
10+
11+
/**
12+
* Checks if this request is a POST to the wc/store/cart endpoint.
13+
*/
14+
const isCartUpdatePostRequest = ( options: APIFetchOptions ) => {
15+
const url = options.url || options.path;
16+
if ( ! url || ! options.method || options.method !== 'POST' ) {
17+
return false;
18+
}
19+
return /wc\/store\/cart\//.exec( url ) !== null;
20+
};
21+
22+
/**
23+
* Middleware which saves the time that the cart was last modified in
24+
* the browser's Local Storage
25+
*
26+
* @param {Object} options Fetch options.
27+
* @param {Function} next The next middleware or fetchHandler to call.
28+
*
29+
* @return {*} The evaluated result of the remaining middleware chain.
30+
*/
31+
const cartUpdateMiddleware = (
32+
options: APIFetchOptions,
33+
/* eslint-disable @typescript-eslint/no-explicit-any */
34+
next: ( arg0: APIFetchOptions, arg1: any ) => any
35+
) => {
36+
if ( isCartUpdatePostRequest( options ) ) {
37+
window.localStorage.setItem(
38+
LAST_CART_UPDATE_TIMESTAMP_KEY,
39+
( Date.now() / 1000 ).toString()
40+
);
41+
}
42+
return next( options, next );
43+
};
44+
45+
apiFetch.use( cartUpdateMiddleware );

assets/js/middleware/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
* Internal dependencies
33
*/
44
import './store-api-nonce';
5+
import './cart-update';

assets/js/type-defs/cart.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ export interface Cart {
185185
export interface CartMeta {
186186
updatingCustomerData: boolean;
187187
updatingSelectedRate: boolean;
188+
isCartDataStale: boolean;
188189
applyingCoupon: string;
189190
removingCoupon: string;
190191
}

0 commit comments

Comments
 (0)