diff --git a/packages/peregrine/lib/store/actions/user/actions.js b/packages/peregrine/lib/store/actions/user/actions.js index 732545bd51..6fba16f2ad 100755 --- a/packages/peregrine/lib/store/actions/user/actions.js +++ b/packages/peregrine/lib/store/actions/user/actions.js @@ -1,7 +1,12 @@ import { createActions } from 'redux-actions'; const prefix = 'USER'; -const actionTypes = ['RESET', 'SET_TOKEN', 'CLEAR_TOKEN']; +const actionTypes = [ + 'RESET', + 'SET_TOKEN', + 'CLEAR_TOKEN', + 'SET_USER_ON_ORDER_SUCCESS' +]; const actionMap = { SIGN_IN: { diff --git a/packages/peregrine/lib/store/actions/user/asyncActions.js b/packages/peregrine/lib/store/actions/user/asyncActions.js index 353187d66c..1ad1f0832b 100755 --- a/packages/peregrine/lib/store/actions/user/asyncActions.js +++ b/packages/peregrine/lib/store/actions/user/asyncActions.js @@ -89,3 +89,9 @@ export const clearToken = () => // Remove from store dispatch(actions.clearToken()); }; + +export const setUserOnOrderSuccess = successFlag => + async function thunk(dispatch) { + // Dispatch the action to update the state + dispatch(actions.setUserOnOrderSuccess(successFlag)); + }; diff --git a/packages/peregrine/lib/store/reducers/user.js b/packages/peregrine/lib/store/reducers/user.js index 73f49ec154..5eede2d035 100755 --- a/packages/peregrine/lib/store/reducers/user.js +++ b/packages/peregrine/lib/store/reducers/user.js @@ -30,7 +30,8 @@ const initialState = { isResettingPassword: false, isSignedIn: isSignedIn(), resetPasswordError: null, - token: getToken() + token: getToken(), + userOnOrderSuccess: false // Add userOnOrderSuccess state }; const reducerMap = { @@ -48,6 +49,12 @@ const reducerMap = { token: null }; }, + [actions.setUserOnOrderSuccess]: (state, { payload }) => { + return { + ...state, + userOnOrderSuccess: payload // Update the state with the new flag value + }; + }, [actions.getDetails.request]: state => { return { ...state, diff --git a/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/__tests__/useOrderConfirmationPage.spec.js b/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/__tests__/useOrderConfirmationPage.spec.js index de70d4354a..0bad6eb1b2 100644 --- a/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/__tests__/useOrderConfirmationPage.spec.js +++ b/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/__tests__/useOrderConfirmationPage.spec.js @@ -11,6 +11,10 @@ import { useLazyQuery } from '@apollo/client'; import { useUserContext } from '../../../../context/user'; +import { useDispatch } from 'react-redux'; // Import `connect` and `useDispatch` here + +import { setUserOnOrderSuccess } from '../../../../store/actions/user/asyncActions'; // Import `setUserOnOrderSuccess` + jest.mock('../../../../context/user'); useUserContext.mockImplementation(() => { return [ @@ -28,6 +32,21 @@ jest.mock('@apollo/client', () => { }; }); +// Mock `react-redux`'s `useDispatch` and `connect` functions + +jest.mock('react-redux', () => ({ + useDispatch: jest.fn(), + connect: jest.fn().mockReturnValue(Component => Component) // Mock `connect` as an identity function +})); + +jest.mock('../../../../store/actions/user/asyncActions', () => ({ + setUserOnOrderSuccess: jest.fn(value => ({ + type: 'SET_USER_ON_ORDER_SUCCESS', + + payload: value + })) // Mock `setUserOnOrderSuccess` to return an action object +})); + const Component = props => { const talonProps = useOrderConfirmationPage(props); @@ -131,6 +150,11 @@ describe('for guest', () => { } ]; }); + + const mockDispatch = jest.fn(); // Mock dispatch for this test + + useDispatch.mockReturnValue(mockDispatch); + const tree = createTestInstance(); const { root } = tree; @@ -143,6 +167,7 @@ describe('for guest', () => { describe('for authenticated customers', () => { it('returns the correct shape', () => { const mockFetch = jest.fn(); + const mockDispatch = jest.fn(); // Mock dispatch for this test useLazyQuery.mockReturnValueOnce([ mockFetch, @@ -153,8 +178,16 @@ describe('for authenticated customers', () => { } ]); + useDispatch.mockReturnValue(mockDispatch); + const mockOrderNumber = '12345'; + // Create a mock dispatch function + + //const mockDispatch = jest.fn(); + + //useDispatch.mockReturnValue(mockDispatch); // Mock useDispatch to return the mock function + const tree = createTestInstance( ); @@ -164,6 +197,10 @@ describe('for authenticated customers', () => { expect(talonProps).toMatchSnapshot(); + // Check if dispatch was called with the correct action + + expect(mockDispatch).toHaveBeenCalledWith(setUserOnOrderSuccess(true)); + expect(mockFetch).toHaveBeenCalledWith({ variables: { orderNumber: mockOrderNumber } }); diff --git a/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/useCreateAccount.js b/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/useCreateAccount.js index b9176afb3f..b9e378ccb4 100644 --- a/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/useCreateAccount.js +++ b/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/useCreateAccount.js @@ -9,6 +9,7 @@ import { useGoogleReCaptcha } from '../../../hooks/useGoogleReCaptcha'; import DEFAULT_OPERATIONS from './createAccount.gql'; import { useEventingContext } from '../../../context/eventing'; +import { useHistory } from 'react-router-dom'; /** * Returns props necessary to render CreateAccount component. In particular this @@ -95,6 +96,7 @@ export const useCreateAccount = props => { formAction: 'createAccount' }); + const history = useHistory(); const handleSubmit = useCallback( async formValues => { setIsSubmitting(true); @@ -158,6 +160,8 @@ export const useCreateAccount = props => { if (onSubmit) { onSubmit(); } + + history.push('/account-information'); } catch (error) { if (process.env.NODE_ENV !== 'production') { console.error(error); @@ -179,7 +183,8 @@ export const useCreateAccount = props => { removeCart, setToken, signIn, - dispatch + dispatch, + history ] ); diff --git a/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/useOrderConfirmationPage.js b/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/useOrderConfirmationPage.js index 2c7f75e185..4a7259fb10 100644 --- a/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/useOrderConfirmationPage.js +++ b/packages/peregrine/lib/talons/CheckoutPage/OrderConfirmationPage/useOrderConfirmationPage.js @@ -1,9 +1,11 @@ import { useEffect } from 'react'; import { useUserContext } from '../../../context/user'; +import { setUserOnOrderSuccess } from '../../../store/actions/user/asyncActions'; import { useLazyQuery } from '@apollo/client'; import mergeOperations from '../../../util/shallowMerge'; import DEFAULT_OPERATIONS from './orderConfirmationPage.gql'; +import { useDispatch } from 'react-redux'; export const flattenGuestCartData = data => { if (!data) { @@ -34,8 +36,13 @@ export const flattenCustomerOrderData = data => { if (!data) { return; } + const { customer } = data; - const order = customer.orders.items[0]; + const order = customer?.orders?.items?.[0]; + if (!order || !order.shipping_address) { + // Return an empty response if no valid order or shipping address exists + return; + } const { shipping_address: address } = order; return { @@ -65,6 +72,8 @@ export const useOrderConfirmationPage = props => { const flatData = flattenGuestCartData(props.data) || flattenCustomerOrderData(queryData); + const dispatch = useDispatch(); + useEffect(() => { if (props.orderNumber && !props.data) { const orderNumber = props.orderNumber; @@ -74,7 +83,19 @@ export const useOrderConfirmationPage = props => { } }); } - }, [props.orderNumber, props.data, fetchOrderConfirmationDetails]); + + dispatch(setUserOnOrderSuccess(true)); + + return () => { + // Reset the flag when leaving the page + dispatch(setUserOnOrderSuccess(false)); + }; + }, [ + props.orderNumber, + props.data, + fetchOrderConfirmationDetails, + dispatch + ]); return { flatData, diff --git a/packages/peregrine/lib/talons/CreateAccount/__tests__/useCreateAccount.spec.js b/packages/peregrine/lib/talons/CreateAccount/__tests__/useCreateAccount.spec.js index 6c22e62abe..457d71c06b 100644 --- a/packages/peregrine/lib/talons/CreateAccount/__tests__/useCreateAccount.spec.js +++ b/packages/peregrine/lib/talons/CreateAccount/__tests__/useCreateAccount.spec.js @@ -9,6 +9,17 @@ import { retrieveCartId } from '../../../store/actions/cart'; import createTestInstance from '../../../util/createTestInstance'; import { useCreateAccount } from '../useCreateAccount'; import { useEventingContext } from '../../../context/eventing'; +import { useHistory, useLocation } from 'react-router-dom'; // Added import for useHistory and useLocation + +// Mocking useHistory and useLocation + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), // Keep the other functionality intact + + useHistory: jest.fn(), + + useLocation: jest.fn() +})); jest.mock('@apollo/client', () => { const apolloClient = jest.requireActual('@apollo/client'); @@ -186,6 +197,22 @@ beforeAll(() => { }); useApolloClient.mockReturnValue(client); + + // Mock useHistory and useLocation here if needed for specific tests + + useHistory.mockReturnValue({ + push: jest.fn() // You can mock any methods that useHistory would provide + }); + + useLocation.mockReturnValue({ + pathname: '/mock-path', + + search: '', + + hash: '', + + state: null + }); }); test('should return properly', () => { diff --git a/packages/peregrine/lib/talons/CreateAccount/useCreateAccount.js b/packages/peregrine/lib/talons/CreateAccount/useCreateAccount.js index 68922ca694..93d43b30cb 100644 --- a/packages/peregrine/lib/talons/CreateAccount/useCreateAccount.js +++ b/packages/peregrine/lib/talons/CreateAccount/useCreateAccount.js @@ -10,6 +10,12 @@ import { useGoogleReCaptcha } from '../../hooks/useGoogleReCaptcha'; import DEFAULT_OPERATIONS from './createAccount.gql'; import { useEventingContext } from '../../context/eventing'; +import { useHistory, useLocation } from 'react-router-dom'; + +/** + * Routes to redirect from if used to create an account. + */ +const REDIRECT_FOR_ROUTES = ['/checkout', '/order-confirmation']; /** * Returns props necessary to render CreateAccount component. In particular this @@ -47,7 +53,7 @@ export const useCreateAccount = props => { { createCart, removeCart, getCartDetails } ] = useCartContext(); const [ - { isGettingDetails }, + { isGettingDetails, userOnOrderSuccess }, { getUserDetails, setToken } ] = useUserContext(); @@ -112,6 +118,9 @@ export const useCreateAccount = props => { }; }, [handleCancel]); + const history = useHistory(); + const location = useLocation(); + const handleSubmit = useCallback( async formValues => { setIsSubmitting(true); @@ -188,6 +197,13 @@ export const useCreateAccount = props => { if (onSubmit) { onSubmit(); } + + if ( + userOnOrderSuccess && + REDIRECT_FOR_ROUTES.includes(location.pathname) + ) { + history.push('/account-information'); + } } catch (error) { if (process.env.NODE_ENV !== 'production') { console.error(error); @@ -213,7 +229,10 @@ export const useCreateAccount = props => { getCartDetails, fetchCartDetails, onSubmit, - dispatch + dispatch, + history, + location.pathname, + userOnOrderSuccess ] ); diff --git a/packages/peregrine/lib/talons/SignIn/__tests__/useSignIn.spec.js b/packages/peregrine/lib/talons/SignIn/__tests__/useSignIn.spec.js index 42845b790a..db93f04cde 100644 --- a/packages/peregrine/lib/talons/SignIn/__tests__/useSignIn.spec.js +++ b/packages/peregrine/lib/talons/SignIn/__tests__/useSignIn.spec.js @@ -53,6 +53,24 @@ jest.mock('@magento/peregrine/lib/context/eventing', () => ({ useEventingContext: jest.fn().mockReturnValue([{}, { dispatch: jest.fn() }]) })); +jest.mock('react-router-dom', () => ({ + useHistory: jest.fn().mockReturnValue({ + push: jest.fn(), + + replace: jest.fn() + }), + + useLocation: jest.fn().mockReturnValue({ + pathname: '/checkout', + + search: '', + + hash: '', + + state: null + }) +})); + const Component = props => { const talonProps = useSignIn(props); @@ -277,6 +295,12 @@ test('mutation error is returned by talon', async () => { expect(talonProps.errors).toMatchSnapshot(); }); +test('useLocation and useHistory are used correctly', () => { + const { talonProps } = getTalonProps({ ...defaultProps }); + + expect(talonProps).toBeDefined(); // Placeholder assertion. +}); + it('should call handleForgotPassword when Enter key is pressed', () => { const { talonProps } = getTalonProps({ ...defaultProps diff --git a/packages/peregrine/lib/talons/SignIn/useSignIn.js b/packages/peregrine/lib/talons/SignIn/useSignIn.js index 34fb99f043..ef0551eb51 100644 --- a/packages/peregrine/lib/talons/SignIn/useSignIn.js +++ b/packages/peregrine/lib/talons/SignIn/useSignIn.js @@ -10,6 +10,12 @@ import { retrieveCartId } from '../../store/actions/cart'; import DEFAULT_OPERATIONS from './signIn.gql'; import { useEventingContext } from '../../context/eventing'; +import { useHistory, useLocation } from 'react-router-dom'; + +/** + * Routes to redirect from if used to create an account. + */ +const REDIRECT_FOR_ROUTES = ['/checkout', '/order-confirmation']; export const useSignIn = props => { const { @@ -40,7 +46,7 @@ export const useSignIn = props => { const userContext = useUserContext(); const [ - { isGettingDetails, getDetailsError }, + { isGettingDetails, getDetailsError, userOnOrderSuccess }, { getUserDetails, setToken } ] = userContext; @@ -83,6 +89,9 @@ export const useSignIn = props => { const formApiRef = useRef(null); const setFormApi = useCallback(api => (formApiRef.current = api), []); + const history = useHistory(); + const location = useLocation(); + const handleSubmit = useCallback( async ({ email, password }) => { setIsSigningIn(true); @@ -144,6 +153,13 @@ export const useSignIn = props => { }); getCartDetails({ fetchCartId, fetchCartDetails }); + + if ( + userOnOrderSuccess && + REDIRECT_FOR_ROUTES.includes(location.pathname) + ) { + history.push('/order-history'); + } } catch (error) { if (process.env.NODE_ENV !== 'production') { console.error(error); @@ -168,7 +184,10 @@ export const useSignIn = props => { getCartDetails, fetchCartDetails, dispatch, - handleTriggerClick + handleTriggerClick, + history, + location.pathname, + userOnOrderSuccess ] ); diff --git a/packages/venia-ui/lib/components/CreateAccount/__tests__/createAccount.spec.js b/packages/venia-ui/lib/components/CreateAccount/__tests__/createAccount.spec.js index c174a60ae3..f2cd3e6638 100644 --- a/packages/venia-ui/lib/components/CreateAccount/__tests__/createAccount.spec.js +++ b/packages/venia-ui/lib/components/CreateAccount/__tests__/createAccount.spec.js @@ -4,6 +4,8 @@ import { createTestInstance } from '@magento/peregrine'; import CreateAccount from '../createAccount'; +import { useHistory, useLocation } from 'react-router-dom'; + jest.mock('@apollo/client', () => ({ gql: jest.fn(), useApolloClient: jest.fn().mockImplementation(() => {}), @@ -24,6 +26,16 @@ jest.mock('@apollo/client', () => ({ })) })); +// Mocking the react-router hooks + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + + useHistory: jest.fn(), + + useLocation: jest.fn() +})); + jest.mock('../../../util/formValidators'); jest.mock('@magento/peregrine/lib/context/user', () => { const userState = { @@ -58,6 +70,24 @@ jest.mock('@magento/peregrine/lib/hooks/useAwaitQuery', () => { return { useAwaitQuery }; }); +// Mocking useLocation and useHistory for the tests + +beforeEach(() => { + useHistory.mockReturnValue({ + push: jest.fn() // mock any methods you need from useHistory + }); + + useLocation.mockReturnValue({ + pathname: '/mock-path', // mock the location properties + + search: '', + + hash: '', + + state: null + }); +}); + jest.mock('@magento/peregrine/lib/context/eventing', () => ({ useEventingContext: jest.fn().mockReturnValue([{}, { dispatch: jest.fn() }]) })); diff --git a/packages/venia-ui/lib/components/SignIn/__tests__/signIn.spec.js b/packages/venia-ui/lib/components/SignIn/__tests__/signIn.spec.js index f8249d57c5..feb4eb699f 100755 --- a/packages/venia-ui/lib/components/SignIn/__tests__/signIn.spec.js +++ b/packages/venia-ui/lib/components/SignIn/__tests__/signIn.spec.js @@ -8,6 +8,8 @@ import SignIn from '../signIn'; import { useUserContext } from '@magento/peregrine/lib/context/user'; import { useMutation } from '@apollo/client'; +import { useHistory, useLocation } from 'react-router-dom'; + jest.mock('@apollo/client', () => ({ gql: jest.fn(), useApolloClient: jest.fn().mockImplementation(() => {}), @@ -23,6 +25,17 @@ jest.mock('@apollo/client', () => ({ loading: false })) })); + +// Mocking the react-router hooks + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + + useHistory: jest.fn(), + + useLocation: jest.fn() +})); + jest.mock('../../../classify'); jest.mock('../../Button', () => () => ); jest.mock('../../FormError/formError', () => 'FormError'); @@ -67,6 +80,24 @@ jest.mock('@magento/peregrine/lib/context/eventing', () => ({ useEventingContext: jest.fn().mockReturnValue([{}, { dispatch: jest.fn() }]) })); +// Mocking useLocation and useHistory for the tests + +beforeEach(() => { + useHistory.mockReturnValue({ + push: jest.fn() // mock any methods you need from useHistory + }); + + useLocation.mockReturnValue({ + pathname: '/mock-path', // mock the location properties + + search: '', + + hash: '', + + state: null + }); +}); + const props = { setDefaultUsername: jest.fn(), showCreateAccount: jest.fn(),