diff --git a/CHANGELOG.md b/CHANGELOG.md index 1298ff80b3..85d6f10b8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.11.1] - UNRELEASED + +### Fixed +- add disconnect and sync options for cart/clear - @gibkigonzo (#4062) + ## [1.11.1] - 2020.02.05 ### Added diff --git a/core/modules/cart/store/actions/connectActions.ts b/core/modules/cart/store/actions/connectActions.ts index 37b423a630..4383749c63 100644 --- a/core/modules/cart/store/actions/connectActions.ts +++ b/core/modules/cart/store/actions/connectActions.ts @@ -9,10 +9,21 @@ const connectActions = { toggleMicrocart ({ commit }) { commit(types.CART_TOGGLE_MICROCART) }, - async clear ({ commit, dispatch, getters }) { + /** + * It will always clear cart items on frontend. + * Options: + * sync - if you want to sync it with backend. + * disconnect - if you want to clear cart token. + */ + async clear ({ commit, dispatch }, { disconnect = true, sync = true } = {}) { await commit(types.CART_LOAD_CART, []) - await commit(types.CART_LOAD_CART_SERVER_TOKEN, null) - await commit(types.CART_SET_ITEMS_HASH, null) + if (sync) { + await dispatch('sync', { forceClientState: true }) + } + if (disconnect) { + await commit(types.CART_SET_ITEMS_HASH, null) + await dispatch('disconnect') + } }, async disconnect ({ commit }) { commit(types.CART_LOAD_CART_SERVER_TOKEN, null) diff --git a/core/modules/cart/test/unit/store/connectActions.spec.ts b/core/modules/cart/test/unit/store/connectActions.spec.ts index 7ebd24a6f1..b61184510b 100644 --- a/core/modules/cart/test/unit/store/connectActions.spec.ts +++ b/core/modules/cart/test/unit/store/connectActions.spec.ts @@ -48,22 +48,55 @@ jest.mock('@vue-storefront/core/helpers', () => ({ })); describe('Cart connectActions', () => { - it('clears cart token and server hash', async () => { - const contextMock = createContextMock({ - getters: { - isCartSyncEnabled: true - } - }) - config.orders = { - directBackendSync: false - } + it('clear deletes all cart products and token', async () => { + const contextMock = { + commit: jest.fn(), + dispatch: jest.fn(), + getters: { isCartSyncEnabled: false } + }; + const wrapper = (actions: any) => actions.clear(contextMock); + config.cart = { synchronize: false }; - await (cartActions as any).clear(contextMock) + await wrapper(cartActions); expect(contextMock.commit).toHaveBeenNthCalledWith(1, types.CART_LOAD_CART, []); - expect(contextMock.commit).toHaveBeenNthCalledWith(2, types.CART_LOAD_CART_SERVER_TOKEN, null); - expect(contextMock.commit).toHaveBeenNthCalledWith(3, types.CART_SET_ITEMS_HASH, null); - }) + expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'sync', { forceClientState: true }); + expect(contextMock.commit).toHaveBeenNthCalledWith(2, types.CART_SET_ITEMS_HASH, null); + expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'disconnect'); + }); + + it('clear deletes all cart products but keep token', async () => { + const contextMock = { + commit: jest.fn(), + dispatch: jest.fn(), + getters: { isCartSyncEnabled: false } + }; + const wrapper = (actions: any) => actions.clear(contextMock, { disconnect: false }); + + config.cart = { synchronize: false }; + + await wrapper(cartActions); + + expect(contextMock.commit).toHaveBeenNthCalledWith(1, types.CART_LOAD_CART, []); + expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'sync', { forceClientState: true }); + }); + + it('clear deletes all cart products and token, but not sync with backend', async () => { + const contextMock = { + commit: jest.fn(), + dispatch: jest.fn(), + getters: { isCartSyncEnabled: false } + }; + const wrapper = (actions: any) => actions.clear(contextMock, { sync: false }); + + config.cart = { synchronize: false }; + + await wrapper(cartActions); + + expect(contextMock.commit).toHaveBeenNthCalledWith(1, types.CART_LOAD_CART, []); + expect(contextMock.commit).toHaveBeenNthCalledWith(2, types.CART_SET_ITEMS_HASH, null); + expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'disconnect'); + }); it('disconnects cart', async () => { const contextMock = createContextMock() diff --git a/core/modules/checkout/store/checkout/actions.ts b/core/modules/checkout/store/checkout/actions.ts index bd26d0807d..753ebb67bd 100644 --- a/core/modules/checkout/store/checkout/actions.ts +++ b/core/modules/checkout/store/checkout/actions.ts @@ -11,7 +11,8 @@ const actions: ActionTree = { const result = await dispatch('order/placeOrder', order, { root: true }) if (!result.resultCode || result.resultCode === 200) { await dispatch('updateOrderTimestamp') - await dispatch('cart/clear', null, { root: true }) + // clear cart without sync, because after order cart will be already cleared on backend + await dispatch('cart/clear', { sync: false }, {root: true}) await dispatch('dropPassword') } } catch (e) { diff --git a/core/modules/checkout/test/unit/store/checkout/actions.spec.ts b/core/modules/checkout/test/unit/store/checkout/actions.spec.ts index 273f6bbadc..df3d7b8bb6 100644 --- a/core/modules/checkout/test/unit/store/checkout/actions.spec.ts +++ b/core/modules/checkout/test/unit/store/checkout/actions.spec.ts @@ -43,7 +43,7 @@ describe('Checkout actions', () => { expect(mockContext.dispatch).toHaveBeenCalledTimes(4); expect(mockContext.dispatch).toHaveBeenNthCalledWith(1, 'order/placeOrder', order, { root: true }); expect(mockContext.dispatch).toHaveBeenNthCalledWith(2, 'updateOrderTimestamp'); - expect(mockContext.dispatch).toHaveBeenNthCalledWith(3, 'cart/clear', null, { root: true }); + expect(mockContext.dispatch).toHaveBeenNthCalledWith(3, 'cart/clear', { sync: false }, { root: true }); expect(mockContext.dispatch).toHaveBeenNthCalledWith(4, 'dropPassword'); }); @@ -54,7 +54,7 @@ describe('Checkout actions', () => { expect(mockContext.dispatch).toHaveBeenCalledTimes(4); expect(mockContext.dispatch).toHaveBeenNthCalledWith(1, 'order/placeOrder', order, { root: true }); expect(mockContext.dispatch).toHaveBeenNthCalledWith(2, 'updateOrderTimestamp'); - expect(mockContext.dispatch).toHaveBeenNthCalledWith(3, 'cart/clear', null, { root: true }); + expect(mockContext.dispatch).toHaveBeenNthCalledWith(3, 'cart/clear', { sync: false }, { root: true }); expect(mockContext.dispatch).toHaveBeenNthCalledWith(4, 'dropPassword'); }); @@ -65,7 +65,7 @@ describe('Checkout actions', () => { expect(mockContext.dispatch).toHaveBeenCalledTimes(1); expect(mockContext.dispatch).toHaveBeenNthCalledWith(1, 'order/placeOrder', order, { root: true }); expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(2, 'updateOrderTimestamp'); - expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(3, 'cart/clear', null, { root: true }); + expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(3, 'cart/clear', { sync: false }, { root: true }); expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(4, 'dropPassword'); }); @@ -76,7 +76,7 @@ describe('Checkout actions', () => { expect(mockContext.dispatch).toHaveBeenCalledTimes(1); expect(mockContext.dispatch).toHaveBeenNthCalledWith(1, 'order/placeOrder', order, { root: true }); expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(2, 'updateOrderTimestamp'); - expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(3, 'cart/clear', null, { root: true }); + expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(3, 'cart/clear', { sync: false }, { root: true }); expect(mockContext.dispatch).not.toHaveBeenNthCalledWith(4, 'dropPassword'); expect(Logger.error).toHaveBeenCalled(); }); diff --git a/core/modules/user/store/actions.ts b/core/modules/user/store/actions.ts index d5899743eb..fa2b3607e7 100644 --- a/core/modules/user/store/actions.ts +++ b/core/modules/user/store/actions.ts @@ -226,7 +226,9 @@ const actions: ActionTree = { await dispatch('cart/disconnect', {}, { root: true }) await dispatch('clearCurrentUser') EventBus.$emit('user-after-logout') - await dispatch('cart/clear', null, { root: true }) + // clear cart without sync, because after logout we don't want to clear cart on backend + // user should have items when he comes back + await dispatch('cart/clear', { sync: false }, { root: true }) if (!silent) { await dispatch('notification/spawnNotification', { diff --git a/core/modules/user/test/unit/store/actions.spec.ts b/core/modules/user/test/unit/store/actions.spec.ts index f4cee6dd77..e8f5bf2749 100644 --- a/core/modules/user/test/unit/store/actions.spec.ts +++ b/core/modules/user/test/unit/store/actions.spec.ts @@ -414,7 +414,7 @@ describe('User actions', () => { expect(contextMock.commit).toBeCalledWith(types.USER_END_SESSION) expect(contextMock.dispatch).toHaveBeenNthCalledWith(1, 'cart/disconnect', {}, {root: true}) expect(contextMock.dispatch).toHaveBeenNthCalledWith(2, 'clearCurrentUser') - expect(contextMock.dispatch).toHaveBeenNthCalledWith(3, 'cart/clear', null, {root: true}) + expect(contextMock.dispatch).toHaveBeenNthCalledWith(3, 'cart/clear', { sync: false }, {root: true}) expect(contextMock.dispatch).toHaveBeenNthCalledWith(4, 'notification/spawnNotification', { type: 'success', message: "You're logged out", diff --git a/src/modules/instant-checkout/components/InstantCheckout.vue b/src/modules/instant-checkout/components/InstantCheckout.vue index 9060feaffe..d8765a0705 100644 --- a/src/modules/instant-checkout/components/InstantCheckout.vue +++ b/src/modules/instant-checkout/components/InstantCheckout.vue @@ -149,7 +149,8 @@ export default { this.$store.dispatch('checkout/setThankYouPage', true) this.$store.commit('ui/setMicrocart', false) this.$router.push(this.localizedRoute('/checkout')) - this.$store.dispatch('cart/clear', null, {root: true}) + // clear cart without sync, because after order cart will be already cleared on backend + this.$store.dispatch('cart/clear', { sync: false }, {root: true}) } }) }) diff --git a/src/themes/default/components/core/blocks/Microcart/Microcart.vue b/src/themes/default/components/core/blocks/Microcart/Microcart.vue index 3de4a1aa78..c5ea862b69 100644 --- a/src/themes/default/components/core/blocks/Microcart/Microcart.vue +++ b/src/themes/default/components/core/blocks/Microcart/Microcart.vue @@ -231,8 +231,9 @@ export default { action1: { label: i18n.t('Cancel'), action: 'close' }, action2: { label: i18n.t('OK'), action: async () => { - await this.$store.dispatch('cart/clear') // just clear the items without sync - await this.$store.dispatch('cart/sync', { forceClientState: true }) + // We just need to clear cart on frontend and backend. + // but cart token can be reused + await this.$store.dispatch('cart/clear', { disconnect: false }) } }, hasNoTimeout: true