This repository has been archived by the owner on Feb 23, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 219
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
CartEventContext
and dispatch events when pressing proceed to c…
…heckout button (#7809) * Add CartEventsContext with onProceedToCheckout event * Wrap Cart in CartEventsProvider * Dispatch onProceedToCheckout event when button is pressed * Update type of children on CartEventsProvider * Add test for ProceedToCheckout block * Add tests for CartEventProvider * Remove superfluous div * Fix incorrect nesting after rebase * Wrap mini cart in CartEventsProvider * Dispatch onProceedToCheckout event when clicking button in mini cart * Add tests for mini cart onProceedToCheckout emitter * Make observer fail so navigation isn't attempted * Prevent console error on navigation * Try preventing navigation in unit tests * Try preventing navigation in unit tests * Try preventing navigation in unit tests * Try preventing navigation in unit tests * Try preventing navigation in unit tests * Try preventing navigation in unit tests * Try preventing navigation in unit tests
- Loading branch information
Showing
11 changed files
with
305 additions
and
8 deletions.
There are no files selected for viewing
51 changes: 51 additions & 0 deletions
51
assets/js/base/context/providers/cart-checkout/cart-events/event-emit.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { useMemo } from '@wordpress/element'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { | ||
emitterCallback, | ||
reducer, | ||
emitEvent, | ||
emitEventWithAbort, | ||
ActionType, | ||
} from '../../../event-emit'; | ||
|
||
// These events are emitted when the Cart status is BEFORE_PROCESSING and AFTER_PROCESSING | ||
// to enable third parties to hook into the cart process | ||
const EVENTS = { | ||
PROCEED_TO_CHECKOUT: 'cart_proceed_to_checkout', | ||
}; | ||
|
||
type EventEmittersType = Record< string, ReturnType< typeof emitterCallback > >; | ||
|
||
/** | ||
* Receives a reducer dispatcher and returns an object with the | ||
* various event emitters for the payment processing events. | ||
* | ||
* Calling the event registration function with the callback will register it | ||
* for the event emitter and will return a dispatcher for removing the | ||
* registered callback (useful for implementation in `useEffect`). | ||
* | ||
* @param {Function} observerDispatch The emitter reducer dispatcher. | ||
* @return {Object} An object with the various payment event emitter registration functions | ||
*/ | ||
const useEventEmitters = ( | ||
observerDispatch: React.Dispatch< ActionType > | ||
): EventEmittersType => { | ||
const eventEmitters = useMemo( | ||
() => ( { | ||
onProceedToCheckout: emitterCallback( | ||
EVENTS.PROCEED_TO_CHECKOUT, | ||
observerDispatch | ||
), | ||
} ), | ||
[ observerDispatch ] | ||
); | ||
return eventEmitters; | ||
}; | ||
|
||
export { EVENTS, useEventEmitters, reducer, emitEvent, emitEventWithAbort }; |
78 changes: 78 additions & 0 deletions
78
assets/js/base/context/providers/cart-checkout/cart-events/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
|
||
import { | ||
createContext, | ||
useContext, | ||
useReducer, | ||
useRef, | ||
useEffect, | ||
} from '@wordpress/element'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { | ||
useEventEmitters, | ||
reducer as emitReducer, | ||
emitEventWithAbort, | ||
EVENTS, | ||
} from './event-emit'; | ||
import type { emitterCallback } from '../../../event-emit'; | ||
|
||
type CartEventsContextType = { | ||
// Used to register a callback that will fire when the cart has been processed and has an error. | ||
onProceedToCheckout: ReturnType< typeof emitterCallback >; | ||
// Used to register a callback that will fire when the cart has been processed and has an error. | ||
dispatchOnProceedToCheckout: () => Promise< unknown[] >; | ||
}; | ||
|
||
const CartEventsContext = createContext< CartEventsContextType >( { | ||
onProceedToCheckout: () => () => void null, | ||
dispatchOnProceedToCheckout: () => new Promise( () => void null ), | ||
} ); | ||
|
||
export const useCartEventsContext = () => { | ||
return useContext( CartEventsContext ); | ||
}; | ||
|
||
/** | ||
* Checkout Events provider | ||
* Emit Checkout events and provide access to Checkout event handlers | ||
* | ||
* @param {Object} props Incoming props for the provider. | ||
* @param {Object} props.children The children being wrapped. | ||
*/ | ||
export const CartEventsProvider = ( { | ||
children, | ||
}: { | ||
children: React.ReactNode; | ||
} ): JSX.Element => { | ||
const [ observers, observerDispatch ] = useReducer( emitReducer, {} ); | ||
const currentObservers = useRef( observers ); | ||
const { onProceedToCheckout } = useEventEmitters( observerDispatch ); | ||
|
||
// set observers on ref so it's always current. | ||
useEffect( () => { | ||
currentObservers.current = observers; | ||
}, [ observers ] ); | ||
|
||
const dispatchOnProceedToCheckout = async () => { | ||
return await emitEventWithAbort( | ||
currentObservers.current, | ||
EVENTS.PROCEED_TO_CHECKOUT, | ||
null | ||
); | ||
}; | ||
|
||
const cartEvents = { | ||
onProceedToCheckout, | ||
dispatchOnProceedToCheckout, | ||
}; | ||
return ( | ||
<CartEventsContext.Provider value={ cartEvents }> | ||
{ children } | ||
</CartEventsContext.Provider> | ||
); | ||
}; |
49 changes: 49 additions & 0 deletions
49
assets/js/base/context/providers/cart-checkout/cart-events/test/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { useCartEventsContext } from '@woocommerce/base-context'; | ||
import { useEffect } from '@wordpress/element'; | ||
import { render, screen, waitFor } from '@testing-library/react'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { CartEventsProvider } from '../index'; | ||
import Block from '../../../../../../blocks/cart/inner-blocks/proceed-to-checkout-block/block'; | ||
|
||
describe( 'CartEventsProvider', () => { | ||
it( 'allows observers to unsubscribe', async () => { | ||
const mockObserver = jest.fn().mockReturnValue( { type: 'error' } ); | ||
const MockObserverComponent = () => { | ||
const { onProceedToCheckout } = useCartEventsContext(); | ||
useEffect( () => { | ||
const unsubscribe = onProceedToCheckout( () => { | ||
mockObserver(); | ||
unsubscribe(); | ||
} ); | ||
}, [ onProceedToCheckout ] ); | ||
return <div>Mock observer</div>; | ||
}; | ||
|
||
render( | ||
<CartEventsProvider> | ||
<div> | ||
<MockObserverComponent /> | ||
<Block checkoutPageId={ 0 } className="test-block" /> | ||
</div> | ||
</CartEventsProvider> | ||
); | ||
expect( screen.getByText( 'Mock observer' ) ).toBeInTheDocument(); | ||
const button = screen.getByText( 'Proceed to Checkout' ); | ||
|
||
// Forcibly set the button URL to # to prevent JSDOM error: `["Error: Not implemented: navigation (except hash changes)` | ||
button.parentElement?.removeAttribute( 'href' ); | ||
|
||
// Click twice. The observer should unsubscribe after the first click. | ||
button.click(); | ||
button.click(); | ||
await waitFor( () => { | ||
expect( mockObserver ).toHaveBeenCalledTimes( 1 ); | ||
} ); | ||
} ); | ||
} ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
export * from './payment-events'; | ||
export * from './shipping'; | ||
export * from './checkout-events'; | ||
export * from './cart-events'; | ||
export * from './cart'; | ||
export * from './checkout-processor'; | ||
export * from './checkout-provider'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.