From 31acf5d032cb99d367a164f5e790fd1f7e57aa57 Mon Sep 17 00:00:00 2001 From: Intershop Date: Thu, 11 Jun 2020 19:24:50 +0200 Subject: [PATCH] refactor: perform action creator migration BREAKING CHANGE: NgRx code artifacts are transformed to a new pattern, follow the instructions in the migration guide. --- .../core/configurations/configuration.meta.ts | 4 +- src/app/core/facades/account.facade.ts | 70 +-- src/app/core/facades/app.facade.ts | 8 +- src/app/core/facades/checkout.facade.ts | 60 +- src/app/core/facades/cms.facade.ts | 4 +- src/app/core/facades/message.facade.ts | 16 +- src/app/core/facades/shopping.facade.ts | 42 +- src/app/core/guards/auth.guard.spec.ts | 4 +- src/app/core/guards/logout.guard.spec.ts | 4 +- src/app/core/guards/logout.guard.ts | 4 +- src/app/core/interceptors/auth.interceptor.ts | 8 +- .../api/api.service.errorhandler.spec.ts | 6 +- .../services/api/api.service.errorhandler.ts | 6 +- src/app/core/services/api/api.service.spec.ts | 16 +- .../content/includes/includes.actions.ts | 33 +- .../content/includes/includes.effects.spec.ts | 10 +- .../content/includes/includes.effects.ts | 31 +- .../content/includes/includes.reducer.spec.ts | 12 +- .../content/includes/includes.reducer.ts | 48 +- .../includes/includes.selectors.spec.ts | 6 +- .../pagelets/pagelets.integration.spec.ts | 4 +- .../content/pagelets/pagelets.reducer.ts | 26 +- .../core/store/content/pages/pages.actions.ts | 30 +- .../store/content/pages/pages.effects.spec.ts | 8 +- .../core/store/content/pages/pages.effects.ts | 31 +- .../core/store/content/pages/pages.reducer.ts | 48 +- .../content/pages/pages.selectors.spec.ts | 4 +- .../configuration/configuration.actions.ts | 24 +- .../configuration.effects.spec.ts | 6 +- .../configuration/configuration.effects.ts | 130 ++-- .../configuration.integration.spec.ts | 8 +- .../configuration/configuration.reducer.ts | 26 +- .../configuration.selectors.spec.ts | 8 +- .../core/store/core/error/error.actions.ts | 30 +- .../store/core/error/error.effects.spec.ts | 4 +- .../core/store/core/error/error.effects.ts | 17 +- .../store/core/error/error.reducer.spec.ts | 17 +- .../core/store/core/error/error.reducer.ts | 22 +- .../store/core/error/error.selectors.spec.ts | 4 +- .../store/core/messages/messages.actions.ts | 31 +- .../core/messages/messages.effects.spec.ts | 4 +- .../store/core/messages/messages.effects.ts | 79 +-- .../store/core/viewconf/viewconf.actions.ts | 23 +- .../store/core/viewconf/viewconf.effects.ts | 32 +- .../store/core/viewconf/viewconf.reducer.ts | 31 +- .../customer/addresses/addresses.actions.ts | 100 +-- .../addresses/addresses.effects.spec.ts | 52 +- .../customer/addresses/addresses.effects.ts | 97 +-- .../addresses/addresses.reducer.spec.ts | 64 +- .../customer/addresses/addresses.reducer.ts | 144 +++-- .../addresses/addresses.selectors.spec.ts | 8 +- .../basket/basket-addresses.effects.spec.ts | 106 ++-- .../basket/basket-addresses.effects.ts | 195 +++--- .../basket/basket-items.effects.spec.ts | 108 ++-- .../customer/basket/basket-items.effects.ts | 243 ++++---- .../basket/basket-payment.effects.spec.ts | 132 ++-- .../customer/basket/basket-payment.effects.ts | 200 +++--- .../basket-promotion-code.effects.spec.ts | 48 +- .../basket/basket-promotion-code.effects.ts | 67 +- .../basket/basket-validation.effects.spec.ts | 62 +- .../basket/basket-validation.effects.ts | 208 ++++--- .../store/customer/basket/basket.actions.ts | 574 ++++++------------ .../customer/basket/basket.effects.spec.ts | 118 ++-- .../store/customer/basket/basket.effects.ts | 194 +++--- .../customer/basket/basket.reducer.spec.ts | 200 +++--- .../store/customer/basket/basket.reducer.ts | 348 ++++++----- .../customer/basket/basket.selectors.spec.ts | 68 +-- .../store/customer/customer-store.spec.ts | 14 +- .../store/customer/orders/orders.actions.ts | 108 +--- .../customer/orders/orders.effects.spec.ts | 112 ++-- .../store/customer/orders/orders.effects.ts | 312 +++++----- .../customer/orders/orders.reducer.spec.ts | 59 +- .../store/customer/orders/orders.reducer.ts | 97 ++- .../customer/orders/orders.selectors.spec.ts | 36 +- .../customer/restore/restore.effects.spec.ts | 52 +- .../store/customer/restore/restore.effects.ts | 184 +++--- .../core/store/customer/user/user.actions.ts | 366 ++++------- .../store/customer/user/user.effects.spec.ts | 194 +++--- .../core/store/customer/user/user.effects.ts | 485 ++++++++------- .../store/customer/user/user.reducer.spec.ts | 210 +++++-- .../core/store/customer/user/user.reducer.ts | 370 ++++++----- .../customer/user/user.selectors.spec.ts | 58 +- .../store/general/contact/contact.actions.ts | 52 +- .../general/contact/contact.effects.spec.ts | 12 +- .../store/general/contact/contact.effects.ts | 45 +- .../store/general/contact/contact.reducer.ts | 98 ++- .../general/contact/contact.selectors.spec.ts | 24 +- .../general/countries/countries.actions.ts | 26 +- .../countries/countries.effects.spec.ts | 10 +- .../general/countries/countries.effects.ts | 23 +- .../countries/countries.reducer.spec.ts | 10 +- .../general/countries/countries.reducer.ts | 48 +- .../countries/countries.selectors.spec.ts | 8 +- .../store/general/regions/regions.actions.ts | 27 +- .../general/regions/regions.effects.spec.ts | 12 +- .../store/general/regions/regions.effects.ts | 25 +- .../general/regions/regions.reducer.spec.ts | 10 +- .../store/general/regions/regions.reducer.ts | 48 +- .../general/regions/regions.selectors.spec.ts | 8 +- .../server-config/server-config.actions.ts | 29 +- .../server-config.effects.spec.ts | 14 +- .../server-config/server-config.effects.ts | 37 +- .../server-config/server-config.reducer.ts | 21 +- .../server-config.selectors.spec.ts | 4 +- src/app/core/store/hybrid/hybrid.effects.ts | 17 +- .../shopping/categories/categories.actions.ts | 60 +- .../categories/categories.effects.spec.ts | 44 +- .../shopping/categories/categories.effects.ts | 134 ++-- .../categories/categories.reducer.spec.ts | 38 +- .../shopping/categories/categories.reducer.ts | 56 +- .../categories/categories.selectors.spec.ts | 22 +- .../store/shopping/compare/compare.actions.ts | 25 +- .../shopping/compare/compare.effects.spec.ts | 12 +- .../store/shopping/compare/compare.effects.ts | 32 +- .../shopping/compare/compare.reducer.spec.ts | 8 +- .../store/shopping/compare/compare.reducer.ts | 34 +- .../store/shopping/filter/filter.actions.ts | 77 +-- .../shopping/filter/filter.effects.spec.ts | 52 +- .../store/shopping/filter/filter.effects.ts | 105 ++-- .../shopping/filter/filter.reducer.spec.ts | 32 +- .../store/shopping/filter/filter.reducer.ts | 58 +- .../shopping/filter/filter.selectors.spec.ts | 6 +- .../product-listing.actions.ts | 77 +-- .../product-listing.effects.spec.ts | 10 +- .../product-listing.effects.ts | 285 ++++----- .../product-listing.reducer.ts | 68 +-- .../product-listing.selectors.spec.ts | 20 +- .../shopping/products/products.actions.ts | 131 ++-- .../products/products.effects.spec.ts | 122 ++-- .../shopping/products/products.effects.ts | 396 ++++++------ .../products/products.reducer.spec.ts | 52 +- .../shopping/products/products.reducer.ts | 110 ++-- .../products/products.selectors.spec.ts | 52 +- .../shopping/promotions/promotions.actions.ts | 30 +- .../promotions/promotions.effects.spec.ts | 12 +- .../shopping/promotions/promotions.effects.ts | 31 +- .../promotions/promotions.reducer.spec.ts | 14 +- .../shopping/promotions/promotions.reducer.ts | 20 +- .../promotions/promotions.selectors.spec.ts | 12 +- .../shopping/recently/recently.actions.ts | 21 +- .../recently/recently.effects.spec.ts | 4 +- .../shopping/recently/recently.effects.ts | 35 +- .../shopping/recently/recently.reducer.ts | 30 +- .../recently/recently.selectors.spec.ts | 14 +- .../store/shopping/search/search.actions.ts | 42 +- .../shopping/search/search.effects.spec.ts | 36 +- .../store/shopping/search/search.effects.ts | 116 ++-- .../store/shopping/search/search.reducer.ts | 16 +- .../store/shopping/shopping-store.spec.ts | 4 +- src/app/core/utils/meta-reducers.spec.ts | 6 +- src/app/core/utils/meta-reducers.ts | 4 +- src/app/core/utils/ngrx-creators.ts | 38 ++ src/app/core/utils/operators.spec.ts | 10 +- src/app/core/utils/operators.ts | 4 +- .../facades/order-templates.facade.ts | 34 +- .../order-template/order-template.actions.ts | 292 ++++----- .../order-template.effects.spec.ts | 163 +++-- .../order-template/order-template.effects.ts | 408 +++++++------ .../order-template/order-template.reducer.ts | 120 ++-- .../order-template.selectors.spec.ts | 60 +- .../quoting/facades/quoting.facade.ts | 68 +-- .../quote-request.service.spec.ts | 18 +- .../services/quote/quote.service.spec.ts | 6 +- .../quote-request/quote-request.actions.ts | 372 ++++-------- .../quote-request.effects.spec.ts | 340 +++++------ .../quote-request/quote-request.effects.ts | 528 ++++++++-------- .../quote-request.reducer.spec.ts | 136 ++--- .../quote-request/quote-request.reducer.ts | 196 +++--- .../quote-request.selectors.spec.ts | 42 +- .../quoting/store/quote/quote.actions.ts | 168 ++--- .../quoting/store/quote/quote.effects.spec.ts | 130 ++-- .../quoting/store/quote/quote.effects.ts | 247 ++++---- .../quoting/store/quote/quote.reducer.spec.ts | 64 +- .../quoting/store/quote/quote.reducer.ts | 118 ++-- .../store/quote/quote.selectors.spec.ts | 16 +- .../quoting/store/quoting-store.spec.ts | 42 +- .../sentry-config/sentry-config.actions.ts | 13 +- .../sentry-config/sentry-config.effects.ts | 154 ++--- .../sentry-config/sentry-config.reducer.ts | 23 +- src/app/extensions/seo/guards/meta.guard.ts | 4 +- .../extensions/seo/store/seo/seo.actions.ts | 13 +- .../seo/store/seo/seo.effects.spec.ts | 4 +- .../extensions/seo/store/seo/seo.effects.ts | 218 ++++--- .../wishlists/facades/wishlists.facade.ts | 30 +- .../store/wishlist/wishlist.actions.ts | 234 +++---- .../store/wishlist/wishlist.effects.spec.ts | 171 +++--- .../store/wishlist/wishlist.effects.ts | 318 +++++----- .../store/wishlist/wishlist.reducer.ts | 128 ++-- .../store/wishlist/wishlist.selectors.spec.ts | 62 +- .../cms/sfe-adapter/sfe-adapter.service.ts | 4 +- 190 files changed, 7180 insertions(+), 8021 deletions(-) create mode 100644 src/app/core/utils/ngrx-creators.ts diff --git a/src/app/core/configurations/configuration.meta.ts b/src/app/core/configurations/configuration.meta.ts index ef60f9f3e4..e58d90561f 100644 --- a/src/app/core/configurations/configuration.meta.ts +++ b/src/app/core/configurations/configuration.meta.ts @@ -2,7 +2,7 @@ import { Params } from '@angular/router'; import { RouterNavigationPayload, routerNavigationAction } from '@ngrx/router-store'; import { ActionReducer } from '@ngrx/store'; -import { ApplyConfiguration } from 'ish-core/store/core/configuration'; +import { applyConfiguration } from 'ish-core/store/core/configuration'; import { ConfigurationState, configurationReducer } from 'ish-core/store/core/configuration/configuration.reducer'; import { CoreState } from 'ish-core/store/core/core-store'; import { RouterState } from 'ish-core/store/core/router/router.reducer'; @@ -42,7 +42,7 @@ function extractConfigurationParameters(state: ConfigurationState, paramMap: Sim } if (Object.keys(properties).length) { - return configurationReducer(state, new ApplyConfiguration(properties)); + return configurationReducer(state, applyConfiguration(properties)); } return state; } diff --git a/src/app/core/facades/account.facade.ts b/src/app/core/facades/account.facade.ts index c48763a873..d046524e2d 100644 --- a/src/app/core/facades/account.facade.ts +++ b/src/app/core/facades/account.facade.ts @@ -10,25 +10,17 @@ import { PasswordReminderUpdate } from 'ish-core/models/password-reminder-update import { PasswordReminder } from 'ish-core/models/password-reminder/password-reminder.model'; import { User } from 'ish-core/models/user/user.model'; import { - CreateCustomerAddress, - DeleteCustomerAddress, - LoadAddresses, + createCustomerAddress, + deleteCustomerAddress, getAddressesError, getAddressesLoading, getAllAddresses, + loadAddresses, } from 'ish-core/store/customer/addresses'; -import { LoadOrders, getOrders, getOrdersLoading, getSelectedOrder } from 'ish-core/store/customer/orders'; +import { getOrders, getOrdersLoading, getSelectedOrder, loadOrders } from 'ish-core/store/customer/orders'; import { - CreateUser, - DeleteUserPaymentInstrument, - LoadUserPaymentMethods, - LoginUser, - RequestPasswordReminder, - ResetPasswordReminder, - UpdateCustomer, - UpdateUser, - UpdateUserPassword, - UpdateUserPasswordByPasswordReminder, + createUser, + deleteUserPaymentInstrument, getLoggedInCustomer, getLoggedInUser, getPasswordReminderError, @@ -39,13 +31,21 @@ import { getUserLoading, getUserPaymentMethods, isBusinessCustomer, + loadUserPaymentMethods, + loginUser, + requestPasswordReminder, + resetPasswordReminder, + updateCustomer, + updateUser, + updateUserPassword, + updateUserPasswordByPasswordReminder, } from 'ish-core/store/customer/user'; import { - CreateContact, - LoadContact, + createContact, getContactLoading, getContactSubjects, getContactSuccess, + loadContact, } from 'ish-core/store/general/contact'; import { whenTruthy } from 'ish-core/utils/operators'; @@ -62,20 +62,20 @@ export class AccountFacade { isLoggedIn$ = this.store.pipe(select(getUserAuthorized)); loginUser(credentials: Credentials) { - this.store.dispatch(new LoginUser({ credentials })); + this.store.dispatch(loginUser({ credentials })); } createUser(body: CustomerRegistrationType) { - this.store.dispatch(new CreateUser(body)); + this.store.dispatch(createUser(body)); } updateUser(user: User, successMessage?: string, successRouterLink?: string) { - this.store.dispatch(new UpdateUser({ user, successMessage, successRouterLink })); + this.store.dispatch(updateUser({ user, successMessage, successRouterLink })); } updateUserEmail(user: User) { this.store.dispatch( - new UpdateUser({ + updateUser({ user, successMessage: 'account.profile.update_email.message', successRouterLink: '/account/profile', @@ -84,12 +84,12 @@ export class AccountFacade { } updateUserPassword(data: { password: string; currentPassword: string }) { - this.store.dispatch(new UpdateUserPassword(data)); + this.store.dispatch(updateUserPassword(data)); } updateUserProfile(user: User) { this.store.dispatch( - new UpdateUser({ + updateUser({ user, successMessage: 'account.profile.update_profile.message', successRouterLink: '/account/profile', @@ -105,7 +105,7 @@ export class AccountFacade { updateCustomerProfile(customer: Customer, message?: string) { this.store.dispatch( - new UpdateCustomer({ + updateCustomer({ customer, successMessage: message ? message : 'account.profile.update_profile.message', successRouterLink: '/account/profile', @@ -119,21 +119,21 @@ export class AccountFacade { passwordReminderError$ = this.store.pipe(select(getPasswordReminderError)); resetPasswordReminder() { - this.store.dispatch(new ResetPasswordReminder()); + this.store.dispatch(resetPasswordReminder()); } requestPasswordReminder(data: PasswordReminder) { - this.store.dispatch(new RequestPasswordReminder({ data })); + this.store.dispatch(requestPasswordReminder({ data })); } requestPasswordReminderUpdate(data: PasswordReminderUpdate) { - this.store.dispatch(new UpdateUserPasswordByPasswordReminder(data)); + this.store.dispatch(updateUserPasswordByPasswordReminder(data)); } // ORDERS orders$() { - this.store.dispatch(new LoadOrders()); + this.store.dispatch(loadOrders()); return this.store.pipe(select(getOrders)); } @@ -145,12 +145,12 @@ export class AccountFacade { private eligiblePaymentMethods$ = this.store.pipe(select(getUserPaymentMethods)); paymentMethods$() { - this.store.dispatch(new LoadUserPaymentMethods()); + this.store.dispatch(loadUserPaymentMethods()); return this.eligiblePaymentMethods$; } deletePaymentInstrument(paymentInstrumentId: string) { - this.store.dispatch(new DeleteUserPaymentInstrument({ id: paymentInstrumentId })); + this.store.dispatch(deleteUserPaymentInstrument({ id: paymentInstrumentId })); } // ADDRESSES @@ -159,7 +159,7 @@ export class AccountFacade { return this.user$.pipe( whenTruthy(), take(1), - tap(() => this.store.dispatch(new LoadAddresses())), + tap(() => this.store.dispatch(loadAddresses())), switchMap(() => this.store.pipe(select(getAllAddresses))) ); } @@ -167,26 +167,26 @@ export class AccountFacade { addressesError$ = this.store.pipe(select(getAddressesError)); createCustomerAddress(address: Address) { - this.store.dispatch(new CreateCustomerAddress({ address })); + this.store.dispatch(createCustomerAddress({ address })); } deleteCustomerAddress(addressId: string) { - this.store.dispatch(new DeleteCustomerAddress({ addressId })); + this.store.dispatch(deleteCustomerAddress({ addressId })); } // CONTACT US contactSubjects$() { - this.store.dispatch(new LoadContact()); + this.store.dispatch(loadContact()); return this.store.pipe(select(getContactSubjects)); } contactLoading$ = this.store.pipe(select(getContactLoading)); contactSuccess$ = this.store.pipe(select(getContactSuccess)); resetContactState() { - this.store.dispatch(new LoadContact()); + this.store.dispatch(loadContact()); } createContact(contact: Contact) { - this.store.dispatch(new CreateContact({ contact })); + this.store.dispatch(createContact({ contact })); } } diff --git a/src/app/core/facades/app.facade.ts b/src/app/core/facades/app.facade.ts index 5daacb44e0..2e3b0656b2 100644 --- a/src/app/core/facades/app.facade.ts +++ b/src/app/core/facades/app.facade.ts @@ -7,8 +7,8 @@ import { filter, map, mapTo, shareReplay, startWith } from 'rxjs/operators'; import { getAvailableLocales, getCurrentLocale, getDeviceType, getICMBaseURL } from 'ish-core/store/core/configuration'; import { getGeneralError, getGeneralErrorType } from 'ish-core/store/core/error'; import { getBreadcrumbData, getHeaderType, getWrapperClass, isStickyHeader } from 'ish-core/store/core/viewconf'; -import { LoadCountries, getAllCountries, getCountriesLoading } from 'ish-core/store/general/countries'; -import { LoadRegions, getRegionsByCountryCode } from 'ish-core/store/general/regions'; +import { getAllCountries, getCountriesLoading, loadCountries } from 'ish-core/store/general/countries'; +import { getRegionsByCountryCode, loadRegions } from 'ish-core/store/general/regions'; @Injectable({ providedIn: 'root' }) export class AppFacade { @@ -64,12 +64,12 @@ export class AppFacade { ).pipe(startWith(true), shareReplay(1)); countries$() { - this.store.dispatch(new LoadCountries()); + this.store.dispatch(loadCountries()); return this.store.pipe(select(getAllCountries)); } regions$(countryCode: string) { - this.store.dispatch(new LoadRegions({ countryCode })); + this.store.dispatch(loadRegions({ countryCode })); return this.store.pipe(select(getRegionsByCountryCode, { countryCode })); } } diff --git a/src/app/core/facades/checkout.facade.ts b/src/app/core/facades/checkout.facade.ts index 3db21781d5..b5effb84ef 100644 --- a/src/app/core/facades/checkout.facade.ts +++ b/src/app/core/facades/checkout.facade.ts @@ -9,21 +9,14 @@ import { PaymentInstrument } from 'ish-core/models/payment-instrument/payment-in import { selectRouteData } from 'ish-core/store/core/router'; import { getAllAddresses } from 'ish-core/store/customer/addresses'; import { - AddPromotionCodeToBasket, - AssignBasketAddress, - ContinueCheckout, - CreateBasketAddress, - CreateBasketPayment, - DeleteBasketItem, - DeleteBasketPayment, - DeleteBasketShippingAddress, - LoadBasketEligiblePaymentMethods, - LoadBasketEligibleShippingMethods, - RemovePromotionCodeFromBasket, - SetBasketPayment, - UpdateBasketAddress, - UpdateBasketItems, - UpdateBasketShippingMethod, + addPromotionCodeToBasket, + assignBasketAddress, + continueCheckout, + createBasketAddress, + createBasketPayment, + deleteBasketItem, + deleteBasketPayment, + deleteBasketShippingAddress, getBasketEligiblePaymentMethods, getBasketEligibleShippingMethods, getBasketError, @@ -36,6 +29,13 @@ import { getBasketValidationResults, getCurrentBasket, isBasketInvoiceAndShippingAddressEqual, + loadBasketEligiblePaymentMethods, + loadBasketEligibleShippingMethods, + removePromotionCodeFromBasket, + setBasketPayment, + updateBasketAddress, + updateBasketItems, + updateBasketShippingMethod, } from 'ish-core/store/customer/basket'; import { getOrdersError, getSelectedOrder } from 'ish-core/store/customer/orders'; import { getLoggedInUser } from 'ish-core/store/customer/user'; @@ -50,7 +50,7 @@ export class CheckoutFacade { checkoutStep$ = this.store.pipe(select(selectRouteData('checkoutStep'))); continue(targetStep: number) { - this.store.dispatch(new ContinueCheckout({ targetStep })); + this.store.dispatch(continueCheckout({ targetStep })); } // BASKET @@ -68,15 +68,15 @@ export class CheckoutFacade { ); deleteBasketItem(itemId: string) { - this.store.dispatch(new DeleteBasketItem({ itemId })); + this.store.dispatch(deleteBasketItem({ itemId })); } updateBasketItem(update: LineItemUpdate) { - this.store.dispatch(new UpdateBasketItems({ lineItemUpdates: [update] })); + this.store.dispatch(updateBasketItems({ lineItemUpdates: [update] })); } updateBasketShippingMethod(shippingId: string) { - this.store.dispatch(new UpdateBasketShippingMethod({ shippingId })); + this.store.dispatch(updateBasketShippingMethod({ shippingId })); } // ORDERS @@ -91,7 +91,7 @@ export class CheckoutFacade { return this.basket$.pipe( whenTruthy(), take(1), - tap(() => this.store.dispatch(new LoadBasketEligibleShippingMethods())), + tap(() => this.store.dispatch(loadBasketEligibleShippingMethods())), switchMap(() => this.store.pipe(select(getBasketEligibleShippingMethods))) ); } @@ -102,22 +102,22 @@ export class CheckoutFacade { return this.basket$.pipe( whenTruthy(), take(1), - tap(() => this.store.dispatch(new LoadBasketEligiblePaymentMethods())), + tap(() => this.store.dispatch(loadBasketEligiblePaymentMethods())), switchMap(() => this.store.pipe(select(getBasketEligiblePaymentMethods))) ); } priceType$ = this.store.pipe(select(getServerConfigParameter<'gross' | 'net'>('pricing.priceType'))); setBasketPayment(paymentName: string) { - this.store.dispatch(new SetBasketPayment({ id: paymentName })); + this.store.dispatch(setBasketPayment({ id: paymentName })); } createBasketPayment(paymentInstrument: PaymentInstrument, saveForLater = false) { - this.store.dispatch(new CreateBasketPayment({ paymentInstrument, saveForLater })); + this.store.dispatch(createBasketPayment({ paymentInstrument, saveForLater })); } deleteBasketPayment(paymentInstrument: PaymentInstrument) { - this.store.dispatch(new DeleteBasketPayment({ paymentInstrument })); + this.store.dispatch(deleteBasketPayment({ paymentInstrument })); } // ADDRESSES @@ -142,7 +142,7 @@ export class CheckoutFacade { ); assignBasketAddress(addressId: string, scope: 'invoice' | 'shipping' | 'any') { - this.store.dispatch(new AssignBasketAddress({ addressId, scope })); + this.store.dispatch(assignBasketAddress({ addressId, scope })); } createBasketAddress(address: Address, scope: 'invoice' | 'shipping' | 'any') { @@ -150,15 +150,15 @@ export class CheckoutFacade { return; } - this.store.dispatch(new CreateBasketAddress({ address, scope })); + this.store.dispatch(createBasketAddress({ address, scope })); } updateBasketAddress(address: Address) { - this.store.dispatch(new UpdateBasketAddress({ address })); + this.store.dispatch(updateBasketAddress({ address })); } deleteBasketAddress(addressId: string) { - this.store.dispatch(new DeleteBasketShippingAddress({ addressId })); + this.store.dispatch(deleteBasketShippingAddress({ addressId })); } // PROMOTIONS @@ -166,10 +166,10 @@ export class CheckoutFacade { promotionError$ = this.store.pipe(select(getBasketPromotionError)); addPromotionCodeToBasket(code: string) { - this.store.dispatch(new AddPromotionCodeToBasket({ code })); + this.store.dispatch(addPromotionCodeToBasket({ code })); } removePromotionCodeFromBasket(code: string) { - this.store.dispatch(new RemovePromotionCodeFromBasket({ code })); + this.store.dispatch(removePromotionCodeFromBasket({ code })); } } diff --git a/src/app/core/facades/cms.facade.ts b/src/app/core/facades/cms.facade.ts index 27fd2c9494..a2aa248630 100644 --- a/src/app/core/facades/cms.facade.ts +++ b/src/app/core/facades/cms.facade.ts @@ -3,7 +3,7 @@ import { Store, select } from '@ngrx/store'; import { Observable } from 'rxjs'; import { filter, map, switchMap, switchMapTo, tap } from 'rxjs/operators'; -import { LoadContentInclude, getContentInclude } from 'ish-core/store/content/includes'; +import { getContentInclude, loadContentInclude } from 'ish-core/store/content/includes'; import { getContentPagelet } from 'ish-core/store/content/pagelets'; import { getContentPageLoading, getSelectedContentPage } from 'ish-core/store/content/pages'; import { getPGID } from 'ish-core/store/customer/user'; @@ -22,7 +22,7 @@ export class CMSFacade { return this.store.pipe(select(getPGID)).pipe( switchMapTo(includeId$), whenTruthy(), - tap(includeId => this.store.dispatch(new LoadContentInclude({ includeId }))), + tap(includeId => this.store.dispatch(loadContentInclude({ includeId }))), switchMap(includeId => this.store.pipe(select(getContentInclude, includeId))) ); } diff --git a/src/app/core/facades/message.facade.ts b/src/app/core/facades/message.facade.ts index d47474ba1a..2790d11367 100644 --- a/src/app/core/facades/message.facade.ts +++ b/src/app/core/facades/message.facade.ts @@ -2,11 +2,11 @@ import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { - DisplayErrorMessage, - DisplayInfoMessage, - DisplaySuccessMessage, - DisplayWarningMessage, MessagesPayloadType, + displayErrorMessage, + displayInfoMessage, + displaySuccessMessage, + displayWarningMessage, } from 'ish-core/store/core/messages'; // not-dead-code @@ -15,18 +15,18 @@ export class MessageFacade { constructor(private store: Store) {} info(data: MessagesPayloadType) { - this.store.dispatch(new DisplayInfoMessage(data)); + this.store.dispatch(displayInfoMessage(data)); } error(data: MessagesPayloadType) { - this.store.dispatch(new DisplayErrorMessage(data)); + this.store.dispatch(displayErrorMessage(data)); } warn(data: MessagesPayloadType) { - this.store.dispatch(new DisplayWarningMessage(data)); + this.store.dispatch(displayWarningMessage(data)); } success(data: MessagesPayloadType) { - this.store.dispatch(new DisplaySuccessMessage(data)); + this.store.dispatch(displaySuccessMessage(data)); } } diff --git a/src/app/core/facades/shopping.facade.ts b/src/app/core/facades/shopping.facade.ts index c95751295b..7bb5b68c80 100644 --- a/src/app/core/facades/shopping.facade.ts +++ b/src/app/core/facades/shopping.facade.ts @@ -5,26 +5,24 @@ import { debounce, debounceTime, filter, map, switchMap, tap } from 'rxjs/operat import { ProductListingID } from 'ish-core/models/product-listing/product-listing.model'; import { ProductCompletenessLevel, ProductHelper } from 'ish-core/models/product/product.model'; -import { AddProductToBasket } from 'ish-core/store/customer/basket'; +import { addProductToBasket } from 'ish-core/store/customer/basket'; import { getCategoryLoading, getSelectedCategory, getTopLevelCategories } from 'ish-core/store/shopping/categories'; import { - AddToCompare, - RemoveFromCompare, - ToggleCompare, + addToCompare, getCompareProducts, getCompareProductsCount, isInCompareProducts, + removeFromCompare, + toggleCompare, } from 'ish-core/store/shopping/compare'; import { getAvailableFilter } from 'ish-core/store/shopping/filter'; import { - LoadMoreProducts, getProductListingLoading, getProductListingView, getProductListingViewType, + loadMoreProducts, } from 'ish-core/store/shopping/product-listing'; import { - LoadProductIfNotLoaded, - LoadProductLinks, getProduct, getProductBundleParts, getProductLinks, @@ -32,14 +30,16 @@ import { getProducts, getSelectedProduct, getSelectedProductVariationOptions, + loadProductIfNotLoaded, + loadProductLinks, } from 'ish-core/store/shopping/products'; -import { LoadPromotion, getPromotion, getPromotions } from 'ish-core/store/shopping/promotions'; +import { getPromotion, getPromotions, loadPromotion } from 'ish-core/store/shopping/promotions'; import { - ClearRecently, + clearRecently, getMostRecentlyViewedProducts, getRecentlyViewedProducts, } from 'ish-core/store/shopping/recently'; -import { SuggestSearch, getSearchTerm, getSuggestSearchResults } from 'ish-core/store/shopping/search'; +import { getSearchTerm, getSuggestSearchResults, suggestSearch } from 'ish-core/store/shopping/search'; import { toObservable } from 'ish-core/utils/functions'; import { whenFalsy } from 'ish-core/utils/operators'; @@ -64,7 +64,7 @@ export class ShoppingFacade { product$(sku: string | Observable, level: ProductCompletenessLevel) { return toObservable(sku).pipe( - tap(plainSKU => this.store.dispatch(new LoadProductIfNotLoaded({ sku: plainSKU, level }))), + tap(plainSKU => this.store.dispatch(loadProductIfNotLoaded({ sku: plainSKU, level }))), switchMap(plainSKU => this.store.pipe( select(getProduct, { sku: plainSKU }), @@ -102,7 +102,7 @@ export class ShoppingFacade { // CHECKOUT addProductToBasket(sku: string, quantity: number) { - this.store.dispatch(new AddProductToBasket({ sku, quantity })); + this.store.dispatch(addProductToBasket({ sku, quantity })); } // PRODUCT LISTING @@ -115,13 +115,13 @@ export class ShoppingFacade { productListingLoading$ = this.store.pipe(select(getProductListingLoading)); loadMoreProducts(id: ProductListingID, page: number) { - this.store.dispatch(new LoadMoreProducts({ id, page })); + this.store.dispatch(loadMoreProducts({ id, page })); } // PRODUCT LINKS productLinks$(sku: string) { - this.store.dispatch(new LoadProductLinks({ sku })); + this.store.dispatch(loadProductLinks({ sku })); return this.store.pipe(select(getProductLinks, { sku })); } @@ -130,7 +130,7 @@ export class ShoppingFacade { searchTerm$ = this.store.pipe(select(getSearchTerm)); searchResults$(searchTerm: Observable) { return searchTerm.pipe( - tap(term => this.store.dispatch(new SuggestSearch({ searchTerm: term }))), + tap(term => this.store.dispatch(suggestSearch({ searchTerm: term }))), switchMap(term => this.store.pipe(select(getSuggestSearchResults(term)))) ); } @@ -160,15 +160,15 @@ export class ShoppingFacade { } addProductToCompare(sku: string) { - this.store.dispatch(new AddToCompare({ sku })); + this.store.dispatch(addToCompare({ sku })); } toggleProductCompare(sku: string) { - this.store.dispatch(new ToggleCompare({ sku })); + this.store.dispatch(toggleCompare({ sku })); } removeProductFromCompare(sku: string) { - this.store.dispatch(new RemoveFromCompare({ sku })); + this.store.dispatch(removeFromCompare({ sku })); } // RECENTLY @@ -177,19 +177,19 @@ export class ShoppingFacade { mostRecentlyViewedProducts$ = this.store.pipe(select(getMostRecentlyViewedProducts)); clearRecentlyViewedProducts() { - this.store.dispatch(new ClearRecently()); + this.store.dispatch(clearRecently()); } // PROMOTIONS promotion$(promotionId: string) { - this.store.dispatch(new LoadPromotion({ promoId: promotionId })); + this.store.dispatch(loadPromotion({ promoId: promotionId })); return this.store.pipe(select(getPromotion(), { promoId: promotionId })); } promotions$(promotionIds: string[]) { promotionIds.forEach(promotionId => { - this.store.dispatch(new LoadPromotion({ promoId: promotionId })); + this.store.dispatch(loadPromotion({ promoId: promotionId })); }); return this.store.pipe(select(getPromotions(), { promotionIds })); } diff --git a/src/app/core/guards/auth.guard.spec.ts b/src/app/core/guards/auth.guard.spec.ts index 01812d89b1..aa53bfe2bb 100644 --- a/src/app/core/guards/auth.guard.spec.ts +++ b/src/app/core/guards/auth.guard.spec.ts @@ -9,7 +9,7 @@ import { Customer } from 'ish-core/models/customer/customer.model'; import { CookiesService } from 'ish-core/services/cookies/cookies.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; -import { LoginUserSuccess } from 'ish-core/store/customer/user'; +import { loginUserSuccess } from 'ish-core/store/customer/user'; import { AuthGuard } from './auth.guard'; @@ -39,7 +39,7 @@ describe('Auth Guard', () => { }); it('should return true when user is authorized', done => { - store$.dispatch(new LoginUserSuccess({ customer: {} as Customer })); + store$.dispatch(loginUserSuccess({ customer: {} as Customer })); authGuard .canActivate({} as ActivatedRouteSnapshot, { url: 'home' } as RouterStateSnapshot) diff --git a/src/app/core/guards/logout.guard.spec.ts b/src/app/core/guards/logout.guard.spec.ts index 1a918b2d74..a6695ca169 100644 --- a/src/app/core/guards/logout.guard.spec.ts +++ b/src/app/core/guards/logout.guard.spec.ts @@ -5,7 +5,7 @@ import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; -import { UserActionTypes } from 'ish-core/store/customer/user'; +import { logoutUser } from 'ish-core/store/customer/user'; import { StoreWithSnapshots, containsActionWithType, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; import { LogoutGuard } from './logout.guard'; @@ -41,7 +41,7 @@ describe('Logout Guard', () => { router.navigateByUrl('/any'); tick(500); - expect(store$.actionsArray()).toSatisfy(containsActionWithType(UserActionTypes.LogoutUser)); + expect(store$.actionsArray()).toSatisfy(containsActionWithType(logoutUser.type)); })); it('should redirect to /home when called', fakeAsync(() => { diff --git a/src/app/core/guards/logout.guard.ts b/src/app/core/guards/logout.guard.ts index 2cc6544afe..355fdcabb0 100644 --- a/src/app/core/guards/logout.guard.ts +++ b/src/app/core/guards/logout.guard.ts @@ -4,7 +4,7 @@ import { Store, select } from '@ngrx/store'; import { map } from 'rxjs/operators'; import { selectQueryParam } from 'ish-core/store/core/router'; -import { LogoutUser } from 'ish-core/store/customer/user'; +import { logoutUser } from 'ish-core/store/customer/user'; /** * triggers logging out the user if the guarded route is visited @@ -16,7 +16,7 @@ export class LogoutGuard implements CanActivate { constructor(private store: Store, private router: Router) {} canActivate() { - this.store.dispatch(new LogoutUser()); + this.store.dispatch(logoutUser()); return this.store.pipe( select(selectQueryParam('returnUrl')), map(returnUrl => returnUrl || '/home'), diff --git a/src/app/core/interceptors/auth.interceptor.ts b/src/app/core/interceptors/auth.interceptor.ts index 0e4ae02184..6e6961ee6a 100644 --- a/src/app/core/interceptors/auth.interceptor.ts +++ b/src/app/core/interceptors/auth.interceptor.ts @@ -13,7 +13,7 @@ import { Observable, throwError, timer } from 'rxjs'; import { catchError, switchMapTo, tap } from 'rxjs/operators'; import { ApiService } from 'ish-core/services/api/api.service'; -import { ResetAPIToken, SetAPIToken } from 'ish-core/store/customer/user'; +import { resetAPIToken, setAPIToken } from 'ish-core/store/customer/user'; /** * Intercepts incoming HTTP response and updates authentication-token. @@ -33,9 +33,9 @@ export class AuthInterceptor implements HttpInterceptor { const apiToken = event.headers.get(ApiService.TOKEN_HEADER_KEY); if (apiToken) { if (apiToken.startsWith('AuthenticationTokenOutdated') || apiToken.startsWith('AuthenticationTokenInvalid')) { - this.store.dispatch(new ResetAPIToken()); + this.store.dispatch(resetAPIToken()); } else { - this.store.dispatch(new SetAPIToken({ apiToken })); + this.store.dispatch(setAPIToken({ apiToken })); } } } @@ -49,7 +49,7 @@ export class AuthInterceptor implements HttpInterceptor { // tslint:disable-next-line:ban catchError(err => { if (AuthInterceptor.isAuthTokenError(err)) { - this.store.dispatch(new ResetAPIToken()); + this.store.dispatch(resetAPIToken()); // retry request without auth token const retryRequest = req.clone({ headers: req.headers.delete(ApiService.TOKEN_HEADER_KEY) }); diff --git a/src/app/core/services/api/api.service.errorhandler.spec.ts b/src/app/core/services/api/api.service.errorhandler.spec.ts index c833d2a855..d4c428c14d 100644 --- a/src/app/core/services/api/api.service.errorhandler.spec.ts +++ b/src/app/core/services/api/api.service.errorhandler.spec.ts @@ -7,7 +7,7 @@ import { cold } from 'jest-marbles'; import { noop } from 'rxjs'; import { anything, capture, spy, verify } from 'ts-mockito'; -import { ErrorActionTypes } from 'ish-core/store/core/error'; +import { communicationTimeoutError, serverError } from 'ish-core/store/core/error'; import { ApiServiceErrorHandler } from './api.service.errorhandler'; @@ -27,8 +27,8 @@ describe('Api Service Errorhandler', () => { function dataProviderKnown() { return [ - { error: { status: 0 }, expectedType: ErrorActionTypes.CommunicationTimeoutError }, - { error: { status: 500 }, expectedType: ErrorActionTypes.ServerError }, + { error: { status: 0 }, expectedType: communicationTimeoutError.type }, + { error: { status: 500 }, expectedType: serverError.type }, ]; } function dataProviderUnknown() { diff --git a/src/app/core/services/api/api.service.errorhandler.ts b/src/app/core/services/api/api.service.errorhandler.ts index 2b9df19429..11d0cac3a2 100644 --- a/src/app/core/services/api/api.service.errorhandler.ts +++ b/src/app/core/services/api/api.service.errorhandler.ts @@ -4,7 +4,7 @@ import { Store } from '@ngrx/store'; import { EMPTY, Observable, throwError } from 'rxjs'; import { HttpErrorMapper } from 'ish-core/models/http-error/http-error.mapper'; -import { CommunicationTimeoutError, ServerError } from 'ish-core/store/core/error'; +import { communicationTimeoutError, serverError } from 'ish-core/store/core/error'; @Injectable({ providedIn: 'root' }) export class ApiServiceErrorHandler { @@ -16,12 +16,12 @@ export class ApiServiceErrorHandler { if (error.status === 0) { console.error(error); - this.store.dispatch(new CommunicationTimeoutError({ error: mappedError })); + this.store.dispatch(communicationTimeoutError({ error: mappedError })); return EMPTY; } if (error.status >= 500 && error.status < 600) { console.error(error); - this.store.dispatch(new ServerError({ error: mappedError })); + this.store.dispatch(serverError({ error: mappedError })); return EMPTY; } return throwError(error); diff --git a/src/app/core/services/api/api.service.spec.ts b/src/app/core/services/api/api.service.spec.ts index 921702f0fa..2bf0fa934d 100644 --- a/src/app/core/services/api/api.service.spec.ts +++ b/src/app/core/services/api/api.service.spec.ts @@ -9,11 +9,11 @@ import { anything, capture, spy, verify } from 'ts-mockito'; import { Link } from 'ish-core/models/link/link.model'; import { Locale } from 'ish-core/models/locale/locale.model'; -import { ApplyConfiguration, getICMServerURL, getRestEndpoint } from 'ish-core/store/core/configuration'; +import { applyConfiguration, getICMServerURL, getRestEndpoint } from 'ish-core/store/core/configuration'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; -import { ErrorActionTypes } from 'ish-core/store/core/error'; +import { serverError } from 'ish-core/store/core/error'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; -import { SetAPIToken } from 'ish-core/store/customer/user'; +import { setAPIToken } from 'ish-core/store/customer/user'; import { ApiService, constructUrlForPath, resolveLink, resolveLinks, unpackEnvelope } from './api.service'; import { ApiServiceErrorHandler } from './api.service.errorhandler'; @@ -70,7 +70,7 @@ describe('Api Service', () => { verify(storeSpy$.dispatch(anything())).once(); // tslint:disable-next-line: no-any const [action] = capture(storeSpy$.dispatch).last() as any; - expect(action.type).toEqual(ErrorActionTypes.ServerError); + expect(action.type).toEqual(serverError.type); expect(action.payload.error).toHaveProperty('statusText', statusText); }); @@ -98,7 +98,7 @@ describe('Api Service', () => { verify(storeSpy$.dispatch(anything())).once(); // tslint:disable-next-line: no-any const [action] = capture(storeSpy$.dispatch).last() as any; - expect(action.type).toEqual(ErrorActionTypes.ServerError); + expect(action.type).toEqual(serverError.type); expect(action.payload.error).toHaveProperty('statusText', statusText); }); @@ -403,7 +403,7 @@ describe('Api Service', () => { httpTestingController = TestBed.inject(HttpTestingController); store$ = TestBed.inject(Store); - store$.dispatch(new ApplyConfiguration({ baseURL: 'http://www.example.org', server: 'WFS', channel: 'site' })); + store$.dispatch(applyConfiguration({ baseURL: 'http://www.example.org', server: 'WFS', channel: 'site' })); }); afterEach(() => { @@ -461,7 +461,7 @@ describe('Api Service', () => { it('should have a token, when token is in store', () => { const apiToken = 'blubb'; - store$.dispatch(new SetAPIToken({ apiToken })); + store$.dispatch(setAPIToken({ apiToken })); apiService.get('dummy').subscribe(fail, fail, fail); const req = httpTestingController.expectOne(`${REST_URL}/dummy`); @@ -471,7 +471,7 @@ describe('Api Service', () => { it('should not have a token, when request is authorization request', () => { const apiToken = 'blubb'; - store$.dispatch(new SetAPIToken({ apiToken })); + store$.dispatch(setAPIToken({ apiToken })); apiService .get('dummy', { headers: new HttpHeaders().set(ApiService.AUTHORIZATION_HEADER_KEY, 'dummy') }) .subscribe(fail, fail, fail); diff --git a/src/app/core/store/content/includes/includes.actions.ts b/src/app/core/store/content/includes/includes.actions.ts index e0b1f2f66d..bd4ef06f26 100644 --- a/src/app/core/store/content/includes/includes.actions.ts +++ b/src/app/core/store/content/includes/includes.actions.ts @@ -1,28 +1,17 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; import { ContentPageletEntryPoint } from 'ish-core/models/content-pagelet-entry-point/content-pagelet-entry-point.model'; import { ContentPagelet } from 'ish-core/models/content-pagelet/content-pagelet.model'; -import { HttpError } from 'ish-core/models/http-error/http-error.model'; +import { httpError, payload } from 'ish-core/utils/ngrx-creators'; -export enum IncludesActionTypes { - LoadContentInclude = '[Content Include] Load Content Include', - LoadContentIncludeFail = '[Content Include API] Load Content Include Fail', - LoadContentIncludeSuccess = '[Content Include API] Load Content Include Success', -} +export const loadContentInclude = createAction( + '[Content Include] Load Content Include', + payload<{ includeId: string }>() +); -export class LoadContentInclude implements Action { - readonly type = IncludesActionTypes.LoadContentInclude; - constructor(public payload: { includeId: string }) {} -} +export const loadContentIncludeFail = createAction('[Content Include API] Load Content Include Fail', httpError()); -export class LoadContentIncludeFail implements Action { - readonly type = IncludesActionTypes.LoadContentIncludeFail; - constructor(public payload: { error: HttpError }) {} -} - -export class LoadContentIncludeSuccess implements Action { - readonly type = IncludesActionTypes.LoadContentIncludeSuccess; - constructor(public payload: { include: ContentPageletEntryPoint; pagelets: ContentPagelet[] }) {} -} - -export type IncludesAction = LoadContentInclude | LoadContentIncludeFail | LoadContentIncludeSuccess; +export const loadContentIncludeSuccess = createAction( + '[Content Include API] Load Content Include Success', + payload<{ include: ContentPageletEntryPoint; pagelets: ContentPagelet[] }>() +); diff --git a/src/app/core/store/content/includes/includes.effects.spec.ts b/src/app/core/store/content/includes/includes.effects.spec.ts index b4fb2b2537..f6e93f67e1 100644 --- a/src/app/core/store/content/includes/includes.effects.spec.ts +++ b/src/app/core/store/content/includes/includes.effects.spec.ts @@ -9,7 +9,7 @@ import { ContentPageletEntryPoint } from 'ish-core/models/content-pagelet-entry- import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { CMSService } from 'ish-core/services/cms/cms.service'; -import { LoadContentInclude, LoadContentIncludeFail } from './includes.actions'; +import { loadContentInclude, loadContentIncludeFail } from './includes.actions'; import { IncludesEffects } from './includes.effects'; describe('Includes Effects', () => { @@ -36,7 +36,7 @@ describe('Includes Effects', () => { of({ include: { id: 'dummy' } as ContentPageletEntryPoint, pagelets: [] }) ); - actions$ = of(new LoadContentInclude({ includeId: 'dummy' })); + actions$ = of(loadContentInclude({ includeId: 'dummy' })); effects.loadContentInclude$.subscribe(action => { verify(cmsServiceMock.getContentInclude('dummy')).once(); @@ -52,7 +52,7 @@ describe('Includes Effects', () => { it('should send fail action when loading action via service is unsuccessful', done => { when(cmsServiceMock.getContentInclude('dummy')).thenReturn(throwError({ message: 'ERROR' })); - actions$ = of(new LoadContentInclude({ includeId: 'dummy' })); + actions$ = of(loadContentInclude({ includeId: 'dummy' })); effects.loadContentInclude$.subscribe(action => { verify(cmsServiceMock.getContentInclude('dummy')).once(); @@ -67,10 +67,10 @@ describe('Includes Effects', () => { it('should not die when encountering an error', () => { when(cmsServiceMock.getContentInclude('dummy')).thenReturn(throwError({ message: 'ERROR' })); - actions$ = hot('a-a-a-a', { a: new LoadContentInclude({ includeId: 'dummy' }) }); + actions$ = hot('a-a-a-a', { a: loadContentInclude({ includeId: 'dummy' }) }); expect(effects.loadContentInclude$).toBeObservable( - cold('a-a-a-a', { a: new LoadContentIncludeFail({ error: { message: 'ERROR' } as HttpError }) }) + cold('a-a-a-a', { a: loadContentIncludeFail({ error: { message: 'ERROR' } as HttpError }) }) ); }); }); diff --git a/src/app/core/store/content/includes/includes.effects.ts b/src/app/core/store/content/includes/includes.effects.ts index 686d4ce4bc..f4d02b0f48 100644 --- a/src/app/core/store/content/includes/includes.effects.ts +++ b/src/app/core/store/content/includes/includes.effects.ts @@ -1,33 +1,28 @@ import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { identity } from 'rxjs'; import { groupBy, map, mergeMap, switchMap } from 'rxjs/operators'; import { CMSService } from 'ish-core/services/cms/cms.service'; import { mapErrorToAction, mapToPayloadProperty } from 'ish-core/utils/operators'; -import { - IncludesActionTypes, - LoadContentInclude, - LoadContentIncludeFail, - LoadContentIncludeSuccess, -} from './includes.actions'; +import { loadContentInclude, loadContentIncludeFail, loadContentIncludeSuccess } from './includes.actions'; @Injectable() export class IncludesEffects { constructor(private actions$: Actions, private cmsService: CMSService) {} - @Effect() - loadContentInclude$ = this.actions$.pipe( - ofType(IncludesActionTypes.LoadContentInclude), - mapToPayloadProperty('includeId'), - groupBy(identity), - mergeMap(group$ => - group$.pipe( - switchMap(includeId => - this.cmsService.getContentInclude(includeId).pipe( - map(contentInclude => new LoadContentIncludeSuccess(contentInclude)), - mapErrorToAction(LoadContentIncludeFail) + loadContentInclude$ = createEffect(() => + this.actions$.pipe( + ofType(loadContentInclude), + mapToPayloadProperty('includeId'), + groupBy(identity), + mergeMap(group$ => + group$.pipe( + switchMap(includeId => + this.cmsService + .getContentInclude(includeId) + .pipe(map(loadContentIncludeSuccess), mapErrorToAction(loadContentIncludeFail)) ) ) ) diff --git a/src/app/core/store/content/includes/includes.reducer.spec.ts b/src/app/core/store/content/includes/includes.reducer.spec.ts index 01132be024..34b740462c 100644 --- a/src/app/core/store/content/includes/includes.reducer.spec.ts +++ b/src/app/core/store/content/includes/includes.reducer.spec.ts @@ -1,7 +1,7 @@ import { ContentPageletEntryPoint } from 'ish-core/models/content-pagelet-entry-point/content-pagelet-entry-point.model'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; -import { LoadContentInclude, LoadContentIncludeFail, LoadContentIncludeSuccess } from './includes.actions'; +import { loadContentInclude, loadContentIncludeFail, loadContentIncludeSuccess } from './includes.actions'; import { includesReducer, initialState } from './includes.reducer'; describe('Includes Reducer', () => { @@ -12,7 +12,7 @@ describe('Includes Reducer', () => { }); it('should set loading state when reducing LoadContentInclude', () => { - const newState = includesReducer(initialState, new LoadContentInclude({ includeId: '' })); + const newState = includesReducer(initialState, loadContentInclude({ includeId: '' })); expect(newState.entities).toBeEmpty(); expect(newState.ids).toBeEmpty(); @@ -23,13 +23,13 @@ describe('Includes Reducer', () => { let loadingState; beforeEach(() => { - loadingState = includesReducer(initialState, new LoadContentInclude({ includeId: '' })); + loadingState = includesReducer(initialState, loadContentInclude({ includeId: '' })); }); it('should unset loading state when reducing LoadContentIncludeFail', () => { const newState = includesReducer( loadingState, - new LoadContentIncludeFail({ error: { message: 'ERROR' } as HttpError }) + loadContentIncludeFail({ error: { message: 'ERROR' } as HttpError }) ); expect(newState.entities).toBeEmpty(); @@ -40,7 +40,7 @@ describe('Includes Reducer', () => { it('should add include when reducing LoadContentIncludeSuccess', () => { const newState = includesReducer( loadingState, - new LoadContentIncludeSuccess({ include: { id: 'dummy' } as ContentPageletEntryPoint, pagelets: [] }) + loadContentIncludeSuccess({ include: { id: 'dummy' } as ContentPageletEntryPoint, pagelets: [] }) ); expect(newState.entities).toHaveProperty('dummy'); @@ -58,7 +58,7 @@ describe('Includes Reducer', () => { title => (state = includesReducer( state, - new LoadContentIncludeSuccess({ include: { id: title } as ContentPageletEntryPoint, pagelets: [] }) + loadContentIncludeSuccess({ include: { id: title } as ContentPageletEntryPoint, pagelets: [] }) )) ); }); diff --git a/src/app/core/store/content/includes/includes.reducer.ts b/src/app/core/store/content/includes/includes.reducer.ts index 960e067a4e..3535ac79ee 100644 --- a/src/app/core/store/content/includes/includes.reducer.ts +++ b/src/app/core/store/content/includes/includes.reducer.ts @@ -1,8 +1,10 @@ import { EntityState, createEntityAdapter } from '@ngrx/entity'; +import { createReducer, on } from '@ngrx/store'; import { ContentPageletEntryPoint } from 'ish-core/models/content-pagelet-entry-point/content-pagelet-entry-point.model'; +import { setLoadingOn } from 'ish-core/utils/ngrx-creators'; -import { IncludesAction, IncludesActionTypes } from './includes.actions'; +import { loadContentInclude, loadContentIncludeFail, loadContentIncludeSuccess } from './includes.actions'; export const includesAdapter = createEntityAdapter({ selectId: contentInclude => contentInclude.id, @@ -16,31 +18,19 @@ export const initialState: IncludesState = includesAdapter.getInitialState({ loading: false, }); -export function includesReducer(state = initialState, action: IncludesAction): IncludesState { - switch (action.type) { - case IncludesActionTypes.LoadContentInclude: { - return { - ...state, - loading: true, - }; - } - - case IncludesActionTypes.LoadContentIncludeFail: { - return { - ...state, - loading: false, - }; - } - - case IncludesActionTypes.LoadContentIncludeSuccess: { - const { include } = action.payload; - - return { - ...includesAdapter.upsertOne(include, state), - loading: false, - }; - } - } - - return state; -} +export const includesReducer = createReducer( + initialState, + setLoadingOn(loadContentInclude), + on(loadContentIncludeFail, (state: IncludesState) => ({ + ...state, + loading: false, + })), + on(loadContentIncludeSuccess, (state: IncludesState, action) => { + const { include } = action.payload; + + return { + ...includesAdapter.upsertOne(include, state), + loading: false, + }; + }) +); diff --git a/src/app/core/store/content/includes/includes.selectors.spec.ts b/src/app/core/store/content/includes/includes.selectors.spec.ts index aa1f4281a7..51dc80ff20 100644 --- a/src/app/core/store/content/includes/includes.selectors.spec.ts +++ b/src/app/core/store/content/includes/includes.selectors.spec.ts @@ -5,7 +5,7 @@ import { ContentStoreModule } from 'ish-core/store/content/content-store.module' import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; -import { LoadContentIncludeSuccess } from './includes.actions'; +import { loadContentIncludeSuccess } from './includes.actions'; import { getContentInclude } from './includes.selectors'; describe('Includes Selectors', () => { @@ -27,7 +27,7 @@ describe('Includes Selectors', () => { it('should select include when it was successfully loaded', () => { store$.dispatch( - new LoadContentIncludeSuccess({ include: { id: 'dummy' } as ContentPageletEntryPoint, pagelets: [] }) + loadContentIncludeSuccess({ include: { id: 'dummy' } as ContentPageletEntryPoint, pagelets: [] }) ); expect(getContentInclude(store$.state, 'dummy')).toHaveProperty('id', 'dummy'); @@ -39,7 +39,7 @@ describe('Includes Selectors', () => { beforeEach(() => { IDS.forEach(title => store$.dispatch( - new LoadContentIncludeSuccess({ include: { id: title } as ContentPageletEntryPoint, pagelets: [] }) + loadContentIncludeSuccess({ include: { id: title } as ContentPageletEntryPoint, pagelets: [] }) ) ); }); diff --git a/src/app/core/store/content/pagelets/pagelets.integration.spec.ts b/src/app/core/store/content/pagelets/pagelets.integration.spec.ts index b4f1475a38..efb3a27e52 100644 --- a/src/app/core/store/content/pagelets/pagelets.integration.spec.ts +++ b/src/app/core/store/content/pagelets/pagelets.integration.spec.ts @@ -3,7 +3,7 @@ import { TestBed } from '@angular/core/testing'; import { ContentPageletEntryPoint } from 'ish-core/models/content-pagelet-entry-point/content-pagelet-entry-point.model'; import { ContentPagelet } from 'ish-core/models/content-pagelet/content-pagelet.model'; import { ContentStoreModule } from 'ish-core/store/content/content-store.module'; -import { LoadContentIncludeSuccess } from 'ish-core/store/content/includes'; +import { loadContentIncludeSuccess } from 'ish-core/store/content/includes'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; @@ -35,7 +35,7 @@ describe('Pagelets Integration', () => { }, ]; - store$.dispatch(new LoadContentIncludeSuccess({ include: { id: 'id' } as ContentPageletEntryPoint, pagelets })); + store$.dispatch(loadContentIncludeSuccess({ include: { id: 'id' } as ContentPageletEntryPoint, pagelets })); const entities = getContentPageletEntities(store$.state); expect(entities).not.toBeEmpty(); diff --git a/src/app/core/store/content/pagelets/pagelets.reducer.ts b/src/app/core/store/content/pagelets/pagelets.reducer.ts index 073c133d8d..e885021230 100644 --- a/src/app/core/store/content/pagelets/pagelets.reducer.ts +++ b/src/app/core/store/content/pagelets/pagelets.reducer.ts @@ -1,8 +1,9 @@ import { EntityState, createEntityAdapter } from '@ngrx/entity'; +import { createReducer, on } from '@ngrx/store'; import { ContentPagelet } from 'ish-core/models/content-pagelet/content-pagelet.model'; -import { IncludesAction, IncludesActionTypes } from 'ish-core/store/content/includes/includes.actions'; -import { PageAction, PagesActionTypes } from 'ish-core/store/content/pages/pages.actions'; +import { loadContentIncludeSuccess } from 'ish-core/store/content/includes/includes.actions'; +import { loadContentPageSuccess } from 'ish-core/store/content/pages/pages.actions'; export interface PageletsState extends EntityState {} @@ -10,15 +11,12 @@ export const pageletsAdapter = createEntityAdapter(); const initialState = pageletsAdapter.getInitialState(); -export function pageletsReducer(state = initialState, action: IncludesAction | PageAction) { - switch (action.type) { - case IncludesActionTypes.LoadContentIncludeSuccess: { - return pageletsAdapter.upsertMany(action.payload.pagelets, state); - } - case PagesActionTypes.LoadContentPageSuccess: { - return pageletsAdapter.upsertMany(action.payload.pagelets, state); - } - } - - return state; -} +export const pageletsReducer = createReducer( + initialState, + on(loadContentIncludeSuccess, (state: PageletsState, action) => + pageletsAdapter.upsertMany(action.payload.pagelets, state) + ), + on(loadContentPageSuccess, (state: PageletsState, action) => + pageletsAdapter.upsertMany(action.payload.pagelets, state) + ) +); diff --git a/src/app/core/store/content/pages/pages.actions.ts b/src/app/core/store/content/pages/pages.actions.ts index 9c22faa8fe..e25a405907 100644 --- a/src/app/core/store/content/pages/pages.actions.ts +++ b/src/app/core/store/content/pages/pages.actions.ts @@ -1,28 +1,14 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; import { ContentPageletEntryPoint } from 'ish-core/models/content-pagelet-entry-point/content-pagelet-entry-point.model'; import { ContentPagelet } from 'ish-core/models/content-pagelet/content-pagelet.model'; -import { HttpError } from 'ish-core/models/http-error/http-error.model'; +import { httpError, payload } from 'ish-core/utils/ngrx-creators'; -export enum PagesActionTypes { - LoadContentPage = '[Content Page] Load Content Page', - LoadContentPageFail = '[Content Page API] Load Content Page Fail', - LoadContentPageSuccess = '[Content Page API] Load Content Page Success', -} +export const loadContentPage = createAction('[Content Page] Load Content Page', payload<{ contentPageId: string }>()); -export class LoadContentPage implements Action { - readonly type = PagesActionTypes.LoadContentPage; - constructor(public payload: { contentPageId: string }) {} -} +export const loadContentPageFail = createAction('[Content Page API] Load Content Page Fail', httpError()); -export class LoadContentPageFail implements Action { - readonly type = PagesActionTypes.LoadContentPageFail; - constructor(public payload: { error: HttpError }) {} -} - -export class LoadContentPageSuccess implements Action { - readonly type = PagesActionTypes.LoadContentPageSuccess; - constructor(public payload: { page: ContentPageletEntryPoint; pagelets: ContentPagelet[] }) {} -} - -export type PageAction = LoadContentPage | LoadContentPageFail | LoadContentPageSuccess; +export const loadContentPageSuccess = createAction( + '[Content Page API] Load Content Page Success', + payload<{ page: ContentPageletEntryPoint; pagelets: ContentPagelet[] }>() +); diff --git a/src/app/core/store/content/pages/pages.effects.spec.ts b/src/app/core/store/content/pages/pages.effects.spec.ts index 68f890b7eb..7e7156d37f 100644 --- a/src/app/core/store/content/pages/pages.effects.spec.ts +++ b/src/app/core/store/content/pages/pages.effects.spec.ts @@ -12,7 +12,7 @@ import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { CMSService } from 'ish-core/services/cms/cms.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; -import { LoadContentPage, LoadContentPageFail } from './pages.actions'; +import { loadContentPage, loadContentPageFail } from './pages.actions'; import { PagesEffects } from './pages.effects'; describe('Pages Effects', () => { @@ -47,7 +47,7 @@ describe('Pages Effects', () => { it('should send fail action when loading action via service is unsuccessful', done => { when(cmsServiceMock.getContentPage('dummy')).thenReturn(throwError({ message: 'ERROR' })); - actions$ = of(new LoadContentPage({ contentPageId: 'dummy' })); + actions$ = of(loadContentPage({ contentPageId: 'dummy' })); effects.loadContentPage$.subscribe(action => { verify(cmsServiceMock.getContentPage('dummy')).once(); @@ -62,10 +62,10 @@ describe('Pages Effects', () => { it('should not die when encountering an error', () => { when(cmsServiceMock.getContentPage('dummy')).thenReturn(throwError({ message: 'ERROR' })); - actions$ = hot('a-a-a-a', { a: new LoadContentPage({ contentPageId: 'dummy' }) }); + actions$ = hot('a-a-a-a', { a: loadContentPage({ contentPageId: 'dummy' }) }); expect(effects.loadContentPage$).toBeObservable( - cold('a-a-a-a', { a: new LoadContentPageFail({ error: { message: 'ERROR' } as HttpError }) }) + cold('a-a-a-a', { a: loadContentPageFail({ error: { message: 'ERROR' } as HttpError }) }) ); }); }); diff --git a/src/app/core/store/content/pages/pages.effects.ts b/src/app/core/store/content/pages/pages.effects.ts index 19a0fcc7e6..bfa7a5abe3 100644 --- a/src/app/core/store/content/pages/pages.effects.ts +++ b/src/app/core/store/content/pages/pages.effects.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { map, mergeMap } from 'rxjs/operators'; @@ -7,28 +7,29 @@ import { CMSService } from 'ish-core/services/cms/cms.service'; import { selectRouteParam } from 'ish-core/store/core/router'; import { mapErrorToAction, mapToPayloadProperty, whenTruthy } from 'ish-core/utils/operators'; -import { LoadContentPage, LoadContentPageFail, LoadContentPageSuccess, PagesActionTypes } from './pages.actions'; +import { loadContentPage, loadContentPageFail, loadContentPageSuccess } from './pages.actions'; @Injectable() export class PagesEffects { constructor(private actions$: Actions, private store: Store, private cmsService: CMSService) {} - @Effect() - loadContentPage$ = this.actions$.pipe( - ofType(PagesActionTypes.LoadContentPage), - mapToPayloadProperty('contentPageId'), - mergeMap(contentPageId => - this.cmsService.getContentPage(contentPageId).pipe( - map(contentPage => new LoadContentPageSuccess(contentPage)), - mapErrorToAction(LoadContentPageFail) + loadContentPage$ = createEffect(() => + this.actions$.pipe( + ofType(loadContentPage), + mapToPayloadProperty('contentPageId'), + mergeMap(contentPageId => + this.cmsService + .getContentPage(contentPageId) + .pipe(map(loadContentPageSuccess), mapErrorToAction(loadContentPageFail)) ) ) ); - @Effect() - selectedContentPage$ = this.store.pipe( - select(selectRouteParam('contentPageId')), - whenTruthy(), - map(contentPageId => new LoadContentPage({ contentPageId })) + selectedContentPage$ = createEffect(() => + this.store.pipe( + select(selectRouteParam('contentPageId')), + whenTruthy(), + map(contentPageId => loadContentPage({ contentPageId })) + ) ); } diff --git a/src/app/core/store/content/pages/pages.reducer.ts b/src/app/core/store/content/pages/pages.reducer.ts index aef23e82a5..f1e29dd618 100644 --- a/src/app/core/store/content/pages/pages.reducer.ts +++ b/src/app/core/store/content/pages/pages.reducer.ts @@ -1,8 +1,10 @@ import { EntityState, createEntityAdapter } from '@ngrx/entity'; +import { createReducer, on } from '@ngrx/store'; import { ContentPageletEntryPoint } from 'ish-core/models/content-pagelet-entry-point/content-pagelet-entry-point.model'; +import { setLoadingOn } from 'ish-core/utils/ngrx-creators'; -import { PageAction, PagesActionTypes } from './pages.actions'; +import { loadContentPage, loadContentPageFail, loadContentPageSuccess } from './pages.actions'; export const pagesAdapter = createEntityAdapter({ selectId: contentPage => contentPage.id, @@ -16,31 +18,19 @@ const initialState: PagesState = pagesAdapter.getInitialState({ loading: false, }); -export function pagesReducer(state = initialState, action: PageAction): PagesState { - switch (action.type) { - case PagesActionTypes.LoadContentPage: { - return { - ...state, - loading: true, - }; - } - - case PagesActionTypes.LoadContentPageFail: { - return { - ...state, - loading: false, - }; - } - - case PagesActionTypes.LoadContentPageSuccess: { - const { page } = action.payload; - - return { - ...pagesAdapter.upsertOne(page, state), - loading: false, - }; - } - } - - return state; -} +export const pagesReducer = createReducer( + initialState, + setLoadingOn(loadContentPage), + on(loadContentPageFail, (state: PagesState) => ({ + ...state, + loading: false, + })), + on(loadContentPageSuccess, (state: PagesState, action) => { + const { page } = action.payload; + + return { + ...pagesAdapter.upsertOne(page, state), + loading: false, + }; + }) +); diff --git a/src/app/core/store/content/pages/pages.selectors.spec.ts b/src/app/core/store/content/pages/pages.selectors.spec.ts index 5d2e1e9e02..5bdaa375cb 100644 --- a/src/app/core/store/content/pages/pages.selectors.spec.ts +++ b/src/app/core/store/content/pages/pages.selectors.spec.ts @@ -8,7 +8,7 @@ import { ContentStoreModule } from 'ish-core/store/content/content-store.module' import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; -import { LoadContentPageSuccess } from './pages.actions'; +import { loadContentPageSuccess } from './pages.actions'; import { getContentPageLoading, getSelectedContentPage } from './pages.selectors'; describe('Pages Selectors', () => { @@ -45,7 +45,7 @@ describe('Pages Selectors', () => { }); it('should select include when it was successfully loaded', fakeAsync(() => { - store$.dispatch(new LoadContentPageSuccess({ page: { id: 'dummy' } as ContentPageletEntryPoint, pagelets: [] })); + store$.dispatch(loadContentPageSuccess({ page: { id: 'dummy' } as ContentPageletEntryPoint, pagelets: [] })); router.navigateByUrl('/any;contentPageId=dummy'); tick(500); diff --git a/src/app/core/store/core/configuration/configuration.actions.ts b/src/app/core/store/core/configuration/configuration.actions.ts index fe3c570247..52a2169bf9 100644 --- a/src/app/core/store/core/configuration/configuration.actions.ts +++ b/src/app/core/store/core/configuration/configuration.actions.ts @@ -1,22 +1,14 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; -import { ConfigurationState } from './configuration.reducer'; +import { payload } from 'ish-core/utils/ngrx-creators'; -export enum ConfigurationActionTypes { - ApplyConfiguration = '[Configuration] Apply Configuration', - SetGTMToken = '[Configuration] Set Google Tag Manager Token', -} +import { ConfigurationState } from './configuration.reducer'; type ConfigurationType = Partial; -export class ApplyConfiguration implements Action { - readonly type = ConfigurationActionTypes.ApplyConfiguration; - constructor(public payload: ConfigurationType) {} -} - -export class SetGTMToken implements Action { - readonly type = ConfigurationActionTypes.SetGTMToken; - constructor(public payload: { gtmToken: string }) {} -} +export const applyConfiguration = createAction('[Configuration] Apply Configuration', payload()); -export type ConfigurationAction = ApplyConfiguration | SetGTMToken; +export const setGTMToken = createAction( + '[Configuration] Set Google Tag Manager Token', + payload<{ gtmToken: string }>() +); diff --git a/src/app/core/store/core/configuration/configuration.effects.spec.ts b/src/app/core/store/core/configuration/configuration.effects.spec.ts index cd0d85222a..44ee9d9ecc 100644 --- a/src/app/core/store/core/configuration/configuration.effects.spec.ts +++ b/src/app/core/store/core/configuration/configuration.effects.spec.ts @@ -11,7 +11,7 @@ import { anything, capture, instance, mock, verify } from 'ts-mockito'; import { LARGE_BREAKPOINT_WIDTH, MEDIUM_BREAKPOINT_WIDTH } from 'ish-core/configurations/injection-keys'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; -import { ConfigurationActionTypes } from './configuration.actions'; +import { applyConfiguration, setGTMToken } from './configuration.actions'; import { ConfigurationEffects } from './configuration.effects'; describe('Configuration Effects', () => { @@ -48,7 +48,7 @@ describe('Configuration Effects', () => { effects.setInitialRestEndpoint$.subscribe( data => { - expect(data.type).toEqual(ConfigurationActionTypes.ApplyConfiguration); + expect(data.type).toEqual(applyConfiguration.type); testComplete$.next(); }, fail, @@ -89,7 +89,7 @@ describe('Configuration Effects', () => { effects.setGTMToken$.subscribe( data => { - expect(data.type).toEqual(ConfigurationActionTypes.SetGTMToken); + expect(data.type).toEqual(setGTMToken.type); expect(data.payload).toHaveProperty('gtmToken', 'dummy'); testComplete$.next(); }, diff --git a/src/app/core/store/core/configuration/configuration.effects.ts b/src/app/core/store/core/configuration/configuration.effects.ts index fd14617df3..dd051bd20c 100644 --- a/src/app/core/store/core/configuration/configuration.effects.ts +++ b/src/app/core/store/core/configuration/configuration.effects.ts @@ -1,7 +1,7 @@ import { isPlatformBrowser, isPlatformServer } from '@angular/common'; import { ApplicationRef, Inject, Injectable, Optional, PLATFORM_ID } from '@angular/core'; import { TransferState } from '@angular/platform-browser'; -import { Actions, Effect, ROOT_EFFECTS_INIT, ofType } from '@ngrx/effects'; +import { Actions, ROOT_EFFECTS_INIT, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; import { defer, fromEvent, iif, merge } from 'rxjs'; @@ -13,7 +13,7 @@ import { DeviceType } from 'ish-core/models/viewtype/viewtype.types'; import { distinctCompareWith, mapToProperty, whenTruthy } from 'ish-core/utils/operators'; import { StatePropertiesService } from 'ish-core/utils/state-transfer/state-properties.service'; -import { ApplyConfiguration, SetGTMToken } from './configuration.actions'; +import { applyConfiguration, setGTMToken } from './configuration.actions'; import { getCurrentLocale, getDeviceType } from './configuration.selectors'; @Injectable() @@ -30,75 +30,83 @@ export class ConfigurationEffects { @Inject(LARGE_BREAKPOINT_WIDTH) private largeBreakpointWidth: number ) {} - @Effect({ dispatch: false }) - $stable = this.appRef.isStable.pipe( - takeWhile(() => isPlatformBrowser(this.platformId)), - // tslint:disable-next-line:no-any - tap(stable => ((window as any).angularStable = stable)) + $stable = createEffect( + () => + this.appRef.isStable.pipe( + takeWhile(() => isPlatformBrowser(this.platformId)), + // tslint:disable-next-line:no-any + tap(stable => ((window as any).angularStable = stable)) + ), + { dispatch: false } ); - @Effect() - setInitialRestEndpoint$ = iif( - () => !this.transferState || !this.transferState.hasKey(NGRX_STATE_SK), - this.actions$.pipe( - ofType(ROOT_EFFECTS_INIT), - take(1), - withLatestFrom( - this.stateProperties.getStateOrEnvOrDefault('ICM_BASE_URL', 'icmBaseURL'), - this.stateProperties.getStateOrEnvOrDefault('ICM_SERVER', 'icmServer'), - this.stateProperties.getStateOrEnvOrDefault('ICM_SERVER_STATIC', 'icmServerStatic'), - this.stateProperties.getStateOrEnvOrDefault('ICM_CHANNEL', 'icmChannel'), - this.stateProperties.getStateOrEnvOrDefault('ICM_APPLICATION', 'icmApplication'), - this.stateProperties - .getStateOrEnvOrDefault('FEATURES', 'features') - .pipe(map(x => (typeof x === 'string' ? x.split(/,/g) : x))), - this.stateProperties.getStateOrEnvOrDefault('THEME', 'theme').pipe(map(x => x || 'default')) - ), - map( - ([, baseURL, server, serverStatic, channel, application, features, theme]) => - new ApplyConfiguration({ baseURL, server, serverStatic, channel, application, features, theme }) + setInitialRestEndpoint$ = createEffect(() => + iif( + () => !this.transferState || !this.transferState.hasKey(NGRX_STATE_SK), + this.actions$.pipe( + ofType(ROOT_EFFECTS_INIT), + take(1), + withLatestFrom( + this.stateProperties.getStateOrEnvOrDefault('ICM_BASE_URL', 'icmBaseURL'), + this.stateProperties.getStateOrEnvOrDefault('ICM_SERVER', 'icmServer'), + this.stateProperties.getStateOrEnvOrDefault('ICM_SERVER_STATIC', 'icmServerStatic'), + this.stateProperties.getStateOrEnvOrDefault('ICM_CHANNEL', 'icmChannel'), + this.stateProperties.getStateOrEnvOrDefault('ICM_APPLICATION', 'icmApplication'), + this.stateProperties + .getStateOrEnvOrDefault('FEATURES', 'features') + .pipe(map(x => (typeof x === 'string' ? x.split(/,/g) : x))), + this.stateProperties.getStateOrEnvOrDefault('THEME', 'theme').pipe(map(x => x || 'default')) + ), + map(([, baseURL, server, serverStatic, channel, application, features, theme]) => + applyConfiguration({ baseURL, server, serverStatic, channel, application, features, theme }) + ) ) ) ); - @Effect({ dispatch: false }) - setLocale$ = this.store.pipe( - select(getCurrentLocale), - mapToProperty('lang'), - distinctUntilChanged(), - // https://github.com/ngx-translate/core/issues/1030 - debounceTime(0), - whenTruthy(), - tap(lang => this.translateService.use(lang)) + setLocale$ = createEffect( + () => + this.store.pipe( + select(getCurrentLocale), + mapToProperty('lang'), + distinctUntilChanged(), + // https://github.com/ngx-translate/core/issues/1030 + debounceTime(0), + whenTruthy(), + tap(lang => this.translateService.use(lang)) + ), + { dispatch: false } ); - @Effect() - setGTMToken$ = this.actions$.pipe( - takeWhile(() => isPlatformServer(this.platformId)), - ofType(ROOT_EFFECTS_INIT), - take(1), - withLatestFrom(this.stateProperties.getStateOrEnvOrDefault('GTM_TOKEN', 'gtmToken')), - map(([, gtmToken]) => gtmToken), - whenTruthy(), - map(gtmToken => new SetGTMToken({ gtmToken })) + setGTMToken$ = createEffect(() => + this.actions$.pipe( + takeWhile(() => isPlatformServer(this.platformId)), + ofType(ROOT_EFFECTS_INIT), + take(1), + withLatestFrom(this.stateProperties.getStateOrEnvOrDefault('GTM_TOKEN', 'gtmToken')), + map(([, gtmToken]) => gtmToken), + whenTruthy(), + map(gtmToken => setGTMToken({ gtmToken })) + ) ); - @Effect() - setDeviceType$ = iif( - () => isPlatformBrowser(this.platformId), - defer(() => - merge(this.actions$.pipe(ofType(ROOT_EFFECTS_INIT)), fromEvent(window, 'resize')).pipe( - map(() => { - if (window.innerWidth < this.mediumBreakpointWidth) { - return 'mobile'; - } else if (window.innerWidth < this.largeBreakpointWidth) { - return 'tablet'; - } else { - return 'desktop'; - } - }), - distinctCompareWith(this.store.pipe(select(getDeviceType))), - map(deviceType => new ApplyConfiguration({ _deviceType: deviceType })) + setDeviceType$ = createEffect(() => + iif( + () => isPlatformBrowser(this.platformId), + defer(() => + merge(this.actions$.pipe(ofType(ROOT_EFFECTS_INIT)), fromEvent(window, 'resize')).pipe( + map(() => { + if (window.innerWidth < this.mediumBreakpointWidth) { + return 'mobile'; + } else if (window.innerWidth < this.largeBreakpointWidth) { + return 'tablet'; + } else { + return 'desktop'; + } + }), + distinctCompareWith(this.store.pipe(select(getDeviceType))), + map(deviceType => applyConfiguration({ _deviceType: deviceType })) + ) ) ) ); diff --git a/src/app/core/store/core/configuration/configuration.integration.spec.ts b/src/app/core/store/core/configuration/configuration.integration.spec.ts index b2b841569b..3c5d4fa2e9 100644 --- a/src/app/core/store/core/configuration/configuration.integration.spec.ts +++ b/src/app/core/store/core/configuration/configuration.integration.spec.ts @@ -12,7 +12,7 @@ import { LARGE_BREAKPOINT_WIDTH, MEDIUM_BREAKPOINT_WIDTH } from 'ish-core/config import { ConfigurationGuard } from 'ish-core/guards/configuration.guard'; import { Locale } from 'ish-core/models/locale/locale.model'; import { ConfigurationService } from 'ish-core/services/configuration/configuration.service'; -import { ApplyConfiguration, getFeatures, getRestEndpoint } from 'ish-core/store/core/configuration'; +import { applyConfiguration, getFeatures, getRestEndpoint } from 'ish-core/store/core/configuration'; import { ConfigurationEffects } from 'ish-core/store/core/configuration/configuration.effects'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; @@ -53,7 +53,7 @@ describe('Configuration Integration', () => { location = TestBed.inject(Location); store$ = TestBed.inject(StoreWithSnapshots); store$.dispatch( - new ApplyConfiguration({ + applyConfiguration({ baseURL: 'http://example.org', locales: [{ lang: 'en_US' }, { lang: 'de_DE' }] as Locale[], }) @@ -102,7 +102,7 @@ describe('Configuration Integration', () => { })); it('should unset features if "none" was provided', fakeAsync(() => { - store$.dispatch(new ApplyConfiguration({ features: ['a', 'b', 'c'] })); + store$.dispatch(applyConfiguration({ features: ['a', 'b', 'c'] })); router.navigateByUrl('/home;features=none;redirect=1'); tick(500); expect(location.path()).toMatchInlineSnapshot(`"/home"`); @@ -110,7 +110,7 @@ describe('Configuration Integration', () => { })); it('should not set features if "default" was provided', fakeAsync(() => { - store$.dispatch(new ApplyConfiguration({ features: ['a', 'b', 'c'] })); + store$.dispatch(applyConfiguration({ features: ['a', 'b', 'c'] })); router.navigateByUrl('/home;features=default;redirect=1'); tick(500); expect(location.path()).toMatchInlineSnapshot(`"/home"`); diff --git a/src/app/core/store/core/configuration/configuration.reducer.ts b/src/app/core/store/core/configuration/configuration.reducer.ts index c4f7bca17d..41979dd29a 100644 --- a/src/app/core/store/core/configuration/configuration.reducer.ts +++ b/src/app/core/store/core/configuration/configuration.reducer.ts @@ -1,9 +1,11 @@ +import { createReducer, on } from '@ngrx/store'; + import { Locale } from 'ish-core/models/locale/locale.model'; import { DeviceType } from 'ish-core/models/viewtype/viewtype.types'; import { environment } from '../../../../../environments/environment'; -import { ConfigurationAction, ConfigurationActionTypes } from './configuration.actions'; +import { applyConfiguration, setGTMToken } from './configuration.actions'; export interface ConfigurationState { baseURL?: string; @@ -34,17 +36,11 @@ const initialState: ConfigurationState = { _deviceType: environment.defaultDeviceType, }; -export function configurationReducer(state = initialState, action: ConfigurationAction): ConfigurationState { - switch (action.type) { - case ConfigurationActionTypes.ApplyConfiguration: { - return { ...state, ...action.payload }; - } - - case ConfigurationActionTypes.SetGTMToken: { - const { gtmToken } = action.payload; - return { ...state, gtmToken }; - } - } - - return state; -} +export const configurationReducer = createReducer( + initialState, + on(applyConfiguration, (state: ConfigurationState, action) => ({ ...state, ...action.payload })), + on(setGTMToken, (state: ConfigurationState, action) => { + const { gtmToken } = action.payload; + return { ...state, gtmToken }; + }) +); diff --git a/src/app/core/store/core/configuration/configuration.selectors.spec.ts b/src/app/core/store/core/configuration/configuration.selectors.spec.ts index e8e512f5bf..e8474a83df 100644 --- a/src/app/core/store/core/configuration/configuration.selectors.spec.ts +++ b/src/app/core/store/core/configuration/configuration.selectors.spec.ts @@ -3,7 +3,7 @@ import { TestBed } from '@angular/core/testing'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; -import { ApplyConfiguration, SetGTMToken } from './configuration.actions'; +import { applyConfiguration, setGTMToken } from './configuration.actions'; import { getAvailableLocales, getCurrentLocale, @@ -45,7 +45,7 @@ describe('Configuration Selectors', () => { describe('after importing settings', () => { beforeEach(() => { store$.dispatch( - new ApplyConfiguration({ + applyConfiguration({ baseURL: 'http://example.org', server: 'api', serverStatic: 'static', @@ -66,7 +66,7 @@ describe('Configuration Selectors', () => { describe('after setting application', () => { beforeEach(() => { store$.dispatch( - new ApplyConfiguration({ + applyConfiguration({ application: 'app', }) ); @@ -84,7 +84,7 @@ describe('Configuration Selectors', () => { describe('after setting gtm token', () => { beforeEach(() => { - store$.dispatch(new SetGTMToken({ gtmToken: 'dummy' })); + store$.dispatch(setGTMToken({ gtmToken: 'dummy' })); }); it('should set token to state', () => { diff --git a/src/app/core/store/core/error/error.actions.ts b/src/app/core/store/core/error/error.actions.ts index e584f11012..e4c0447b62 100644 --- a/src/app/core/store/core/error/error.actions.ts +++ b/src/app/core/store/core/error/error.actions.ts @@ -1,29 +1,9 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; -import { HttpError } from 'ish-core/models/http-error/http-error.model'; +import { httpError } from 'ish-core/utils/ngrx-creators'; -export enum ErrorActionTypes { - GeneralError = '[Error] Communication Error', - CommunicationTimeoutError = '[Error] Communication Timeout Error', - ServerError = '[Error] Server Error (5xx)', -} +export const generalError = createAction('[Error] Communication Error', httpError()); -export class GeneralError implements Action { - readonly type = ErrorActionTypes.GeneralError; - constructor(public payload: { error: HttpError }) {} -} +export const communicationTimeoutError = createAction('[Error] Communication Timeout Error', httpError()); -export class CommunicationTimeoutError implements Action { - readonly type = ErrorActionTypes.CommunicationTimeoutError; - constructor(public payload: { error: HttpError }) {} -} - -/** - * Internal Server Error - 500 - */ -export class ServerError implements Action { - readonly type = ErrorActionTypes.ServerError; - constructor(public payload: { error: HttpError }) {} -} - -export type HttpErrorAction = GeneralError | CommunicationTimeoutError | ServerError; +export const serverError = createAction('[Error] Server Error (5xx)', httpError()); diff --git a/src/app/core/store/core/error/error.effects.spec.ts b/src/app/core/store/core/error/error.effects.spec.ts index d534ab2e12..978abf928e 100644 --- a/src/app/core/store/core/error/error.effects.spec.ts +++ b/src/app/core/store/core/error/error.effects.spec.ts @@ -8,7 +8,7 @@ import { noop } from 'rxjs'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; -import { CommunicationTimeoutError } from './error.actions'; +import { communicationTimeoutError } from './error.actions'; import { ErrorEffects } from './error.effects'; describe('Error Effects', () => { @@ -36,7 +36,7 @@ describe('Error Effects', () => { describe('gotoErrorPageInCaseOfError$', () => { it('should call Router Navigation when Error is handled', fakeAsync(() => { - store$.dispatch(new CommunicationTimeoutError({ error: {} as HttpError })); + store$.dispatch(communicationTimeoutError({ error: {} as HttpError })); effects.gotoErrorPageInCaseOfError$.subscribe(noop, fail, fail); diff --git a/src/app/core/store/core/error/error.effects.ts b/src/app/core/store/core/error/error.effects.ts index 2dfdea543a..eab3982850 100644 --- a/src/app/core/store/core/error/error.effects.ts +++ b/src/app/core/store/core/error/error.effects.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { Effect } from '@ngrx/effects'; +import { createEffect } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { map, tap } from 'rxjs/operators'; @@ -13,12 +13,15 @@ import { getGeneralError } from './error.selectors'; export class ErrorEffects { constructor(private store: Store, private httpStatusCodeService: HttpStatusCodeService) {} - @Effect({ dispatch: false }) - gotoErrorPageInCaseOfError$ = this.store.pipe( - select(getGeneralError), - whenTruthy(), - map(error => this.mapStatus(error)), - tap(status => this.httpStatusCodeService.setStatusAndRedirect(status)) + gotoErrorPageInCaseOfError$ = createEffect( + () => + this.store.pipe( + select(getGeneralError), + whenTruthy(), + map(error => this.mapStatus(error)), + tap(status => this.httpStatusCodeService.setStatusAndRedirect(status)) + ), + { dispatch: false } ); private mapStatus(state: HttpError): number { diff --git a/src/app/core/store/core/error/error.reducer.spec.ts b/src/app/core/store/core/error/error.reducer.spec.ts index 78cdc15a6a..e7225ae7bb 100644 --- a/src/app/core/store/core/error/error.reducer.spec.ts +++ b/src/app/core/store/core/error/error.reducer.spec.ts @@ -2,9 +2,9 @@ import * as using from 'jasmine-data-provider'; import { anything } from 'ts-mockito'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; -import { LoginUserSuccess } from 'ish-core/store/customer/user'; +import { loginUserSuccess } from 'ish-core/store/customer/user'; -import { CommunicationTimeoutError, ErrorActionTypes, HttpErrorAction } from './error.actions'; +import { communicationTimeoutError, generalError, serverError } from './error.actions'; import { errorReducer, initialState } from './error.reducer'; describe('Error Reducer', () => { @@ -20,7 +20,10 @@ describe('Error Reducer', () => { describe('reducer', () => { it('should return initial state when undefined state is supplied', () => { - const newState = errorReducer(undefined, {} as HttpErrorAction); + const newState = errorReducer( + undefined, + {} as ReturnType + ); expect(newState).toEqual(initialState); }); @@ -30,17 +33,17 @@ describe('Error Reducer', () => { return [ { state: initialState, - action: {} as HttpErrorAction, + action: {} as ReturnType, expected: initialState, }, { state: initialState, - action: new CommunicationTimeoutError({ error: {} as HttpError }), - expected: { current: {}, type: ErrorActionTypes.CommunicationTimeoutError }, + action: communicationTimeoutError({ error: {} as HttpError }), + expected: { current: {}, type: communicationTimeoutError.type }, }, { state: initialState, - action: new LoginUserSuccess(anything()), + action: loginUserSuccess(anything()), expected: initialState, }, ]; diff --git a/src/app/core/store/core/error/error.reducer.ts b/src/app/core/store/core/error/error.reducer.ts index 1619ee8983..e44f3ce06e 100644 --- a/src/app/core/store/core/error/error.reducer.ts +++ b/src/app/core/store/core/error/error.reducer.ts @@ -1,6 +1,8 @@ +import { createReducer, on } from '@ngrx/store'; + import { HttpError } from 'ish-core/models/http-error/http-error.model'; -import { ErrorActionTypes, HttpErrorAction } from './error.actions'; +import { communicationTimeoutError, generalError, serverError } from './error.actions'; export interface ErrorState { current: HttpError; @@ -12,13 +14,11 @@ export const initialState: ErrorState = { type: undefined, }; -export function errorReducer(state = initialState, action: HttpErrorAction): ErrorState { - switch (action.type) { - case ErrorActionTypes.GeneralError: - case ErrorActionTypes.ServerError: - case ErrorActionTypes.CommunicationTimeoutError: { - return { ...state, current: action.payload.error, type: action.type }; - } - } - return state; -} +export const errorReducer = createReducer( + initialState, + on(generalError, serverError, communicationTimeoutError, (state: ErrorState, action) => ({ + ...state, + current: action.payload.error, + type: action.type, + })) +); diff --git a/src/app/core/store/core/error/error.selectors.spec.ts b/src/app/core/store/core/error/error.selectors.spec.ts index d911a80d5b..e0ac429f2b 100644 --- a/src/app/core/store/core/error/error.selectors.spec.ts +++ b/src/app/core/store/core/error/error.selectors.spec.ts @@ -4,7 +4,7 @@ import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; -import { CommunicationTimeoutError } from './error.actions'; +import { communicationTimeoutError } from './error.actions'; import { getGeneralError, getGeneralErrorType } from './error.selectors'; describe('Error Selectors', () => { @@ -25,7 +25,7 @@ describe('Error Selectors', () => { }); it('should select a error when a HttpError action is reduced', () => { - store$.dispatch(new CommunicationTimeoutError({ error: { status: 123 } as HttpError })); + store$.dispatch(communicationTimeoutError({ error: { status: 123 } as HttpError })); expect(getGeneralError(store$.state)).toMatchInlineSnapshot(` Object { diff --git a/src/app/core/store/core/messages/messages.actions.ts b/src/app/core/store/core/messages/messages.actions.ts index bab5dcd6c5..2ec42cdd91 100644 --- a/src/app/core/store/core/messages/messages.actions.ts +++ b/src/app/core/store/core/messages/messages.actions.ts @@ -1,4 +1,6 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; + +import { payload } from 'ish-core/utils/ngrx-creators'; export interface MessagesPayloadType { /** @@ -27,29 +29,10 @@ export interface MessagesPayloadType { duration?: number; } -export enum MessagesActionTypes { - DisplayInfoMessage = '[Message] Info Toast', - DisplayErrorMessage = '[Message] Error Toast', - DisplayWarningMessage = '[Message] Warning Toast', - DisplaySuccessMessage = '[Message] Success Toast', -} +export const displayInfoMessage = createAction('[Message] Info Toast', payload()); -export class DisplayInfoMessage implements Action { - readonly type = MessagesActionTypes.DisplayInfoMessage; - constructor(public payload: MessagesPayloadType) {} -} +export const displayErrorMessage = createAction('[Message] Error Toast', payload()); -export class DisplayErrorMessage implements Action { - readonly type = MessagesActionTypes.DisplayErrorMessage; - constructor(public payload: MessagesPayloadType) {} -} +export const displayWarningMessage = createAction('[Message] Warning Toast', payload()); -export class DisplayWarningMessage implements Action { - readonly type = MessagesActionTypes.DisplayWarningMessage; - constructor(public payload: MessagesPayloadType) {} -} - -export class DisplaySuccessMessage implements Action { - readonly type = MessagesActionTypes.DisplaySuccessMessage; - constructor(public payload: MessagesPayloadType) {} -} +export const displaySuccessMessage = createAction('[Message] Success Toast', payload()); diff --git a/src/app/core/store/core/messages/messages.effects.spec.ts b/src/app/core/store/core/messages/messages.effects.spec.ts index 3b57dc630b..0ec1df4ba3 100644 --- a/src/app/core/store/core/messages/messages.effects.spec.ts +++ b/src/app/core/store/core/messages/messages.effects.spec.ts @@ -6,7 +6,7 @@ import { ToastrService } from 'ngx-toastr'; import { Observable, of } from 'rxjs'; import { anything, instance, mock, verify } from 'ts-mockito'; -import { DisplaySuccessMessage } from './messages.actions'; +import { displaySuccessMessage } from './messages.actions'; import { MessagesEffects } from './messages.effects'; describe('Messages Effects', () => { @@ -34,7 +34,7 @@ describe('Messages Effects', () => { }); it('should call ToastrService when handling messages', done => { - actions$ = of(new DisplaySuccessMessage({ message: 'test' })); + actions$ = of(displaySuccessMessage({ message: 'test' })); effects.successToast$.subscribe(() => { verify(toastrServiceMock.success(anything(), anything(), anything())).once(); diff --git a/src/app/core/store/core/messages/messages.effects.ts b/src/app/core/store/core/messages/messages.effects.ts index 8a00046e0d..4b1c657f8d 100644 --- a/src/app/core/store/core/messages/messages.effects.ts +++ b/src/app/core/store/core/messages/messages.effects.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { TranslateService } from '@ngx-translate/core'; import { IndividualConfig, ToastrService } from 'ngx-toastr'; import { tap } from 'rxjs/operators'; @@ -7,52 +7,63 @@ import { tap } from 'rxjs/operators'; import { mapToPayload } from 'ish-core/utils/operators'; import { - DisplayErrorMessage, - DisplayInfoMessage, - DisplaySuccessMessage, - DisplayWarningMessage, - MessagesActionTypes, MessagesPayloadType, + displayErrorMessage, + displayInfoMessage, + displaySuccessMessage, + displayWarningMessage, } from './messages.actions'; @Injectable() export class MessagesEffects { constructor(private actions$: Actions, private translate: TranslateService, private toastr: ToastrService) {} - @Effect({ dispatch: false }) - infoToast$ = this.actions$.pipe( - ofType(MessagesActionTypes.DisplayInfoMessage), - mapToPayload(), - tap(payload => { - this.toastr.info(...this.composeToastServiceArguments(payload)); - }) + infoToast$ = createEffect( + () => + this.actions$.pipe( + ofType(displayInfoMessage), + mapToPayload(), + tap(payload => { + this.toastr.info(...this.composeToastServiceArguments(payload)); + }) + ), + { dispatch: false } ); - @Effect({ dispatch: false }) - errorToast$ = this.actions$.pipe( - ofType(MessagesActionTypes.DisplayErrorMessage), - mapToPayload(), - tap(payload => { - this.toastr.error(...this.composeToastServiceArguments(payload)); - }) + errorToast$ = createEffect( + () => + this.actions$.pipe( + ofType(displayErrorMessage), + mapToPayload(), + tap(payload => { + this.toastr.error(...this.composeToastServiceArguments(payload)); + }) + ), + { dispatch: false } ); - @Effect({ dispatch: false }) - warningToast$ = this.actions$.pipe( - ofType(MessagesActionTypes.DisplayWarningMessage), - mapToPayload(), - tap(payload => { - this.toastr.warning(...this.composeToastServiceArguments(payload)); - }) + warningToast$ = createEffect( + () => + this.actions$.pipe( + ofType(displayWarningMessage), + mapToPayload(), + tap(payload => { + this.toastr.warning(...this.composeToastServiceArguments(payload)); + }) + ), + { dispatch: false } ); - @Effect({ dispatch: false }) - successToast$ = this.actions$.pipe( - ofType(MessagesActionTypes.DisplaySuccessMessage), - mapToPayload(), - tap(payload => { - this.toastr.success(...this.composeToastServiceArguments(payload)); - }) + successToast$ = createEffect( + () => + this.actions$.pipe( + ofType(displaySuccessMessage), + mapToPayload(), + tap(payload => { + this.toastr.success(...this.composeToastServiceArguments(payload)); + }) + ), + { dispatch: false } ); private composeToastServiceArguments(payload: MessagesPayloadType): [string, string, Partial] { diff --git a/src/app/core/store/core/viewconf/viewconf.actions.ts b/src/app/core/store/core/viewconf/viewconf.actions.ts index ee50c37065..3ec8b28db2 100644 --- a/src/app/core/store/core/viewconf/viewconf.actions.ts +++ b/src/app/core/store/core/viewconf/viewconf.actions.ts @@ -1,20 +1,11 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; import { BreadcrumbItem } from 'ish-core/models/breadcrumb-item/breadcrumb-item.interface'; +import { payload } from 'ish-core/utils/ngrx-creators'; -export enum ViewconfActionTypes { - SetBreadcrumbData = '[Viewconf Internal] Set Breadcrumb Data', - SetStickyHeader = '[Viewconf Internal] Set Sticky Header', -} +export const setBreadcrumbData = createAction( + '[Viewconf Internal] Set Breadcrumb Data', + payload<{ breadcrumbData: BreadcrumbItem[] }>() +); -export class SetBreadcrumbData implements Action { - readonly type = ViewconfActionTypes.SetBreadcrumbData; - constructor(public payload: { breadcrumbData: BreadcrumbItem[] }) {} -} - -export class SetStickyHeader implements Action { - readonly type = ViewconfActionTypes.SetStickyHeader; - constructor(public payload: { sticky: boolean }) {} -} - -export type ViewconfActions = SetBreadcrumbData | SetStickyHeader; +export const setStickyHeader = createAction('[Viewconf Internal] Set Sticky Header', payload<{ sticky: boolean }>()); diff --git a/src/app/core/store/core/viewconf/viewconf.effects.ts b/src/app/core/store/core/viewconf/viewconf.effects.ts index 5331153ae9..c4b4f1d56a 100644 --- a/src/app/core/store/core/viewconf/viewconf.effects.ts +++ b/src/app/core/store/core/viewconf/viewconf.effects.ts @@ -1,6 +1,6 @@ import { isPlatformBrowser } from '@angular/common'; import { Inject, Injectable, PLATFORM_ID } from '@angular/core'; -import { Effect } from '@ngrx/effects'; +import { createEffect } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { defer, fromEvent, iif } from 'rxjs'; import { distinctUntilChanged, map } from 'rxjs/operators'; @@ -8,29 +8,31 @@ import { distinctUntilChanged, map } from 'rxjs/operators'; import { selectRouteData } from 'ish-core/store/core/router'; import { distinctCompareWith } from 'ish-core/utils/operators'; -import { SetBreadcrumbData, SetStickyHeader } from './viewconf.actions'; +import { setBreadcrumbData, setStickyHeader } from './viewconf.actions'; import { getBreadcrumbData } from './viewconf.selectors'; @Injectable() export class ViewconfEffects { constructor(private store: Store, @Inject(PLATFORM_ID) private platformId: string) {} - @Effect() - toggleStickyHeader$ = iif( - () => isPlatformBrowser(this.platformId), - defer(() => - fromEvent(window, 'scroll').pipe( - map(() => window.pageYOffset >= 170), - distinctUntilChanged(), - map(sticky => new SetStickyHeader({ sticky })) + toggleStickyHeader$ = createEffect(() => + iif( + () => isPlatformBrowser(this.platformId), + defer(() => + fromEvent(window, 'scroll').pipe( + map(() => window.pageYOffset >= 170), + distinctUntilChanged(), + map(sticky => setStickyHeader({ sticky })) + ) ) ) ); - @Effect() - retrieveBreadcrumbDataFromRouting$ = this.store.pipe( - select(selectRouteData('breadcrumbData')), - distinctCompareWith(this.store.pipe(select(getBreadcrumbData))), - map(breadcrumbData => new SetBreadcrumbData({ breadcrumbData })) + retrieveBreadcrumbDataFromRouting$ = createEffect(() => + this.store.pipe( + select(selectRouteData('breadcrumbData')), + distinctCompareWith(this.store.pipe(select(getBreadcrumbData))), + map(breadcrumbData => setBreadcrumbData({ breadcrumbData })) + ) ); } diff --git a/src/app/core/store/core/viewconf/viewconf.reducer.ts b/src/app/core/store/core/viewconf/viewconf.reducer.ts index 27b94d1025..5935bf76b9 100644 --- a/src/app/core/store/core/viewconf/viewconf.reducer.ts +++ b/src/app/core/store/core/viewconf/viewconf.reducer.ts @@ -1,6 +1,8 @@ +import { createReducer, on } from '@ngrx/store'; + import { BreadcrumbItem } from 'ish-core/models/breadcrumb-item/breadcrumb-item.interface'; -import { ViewconfActionTypes, ViewconfActions } from './viewconf.actions'; +import { setBreadcrumbData, setStickyHeader } from './viewconf.actions'; export interface ViewconfState { breadcrumbData: BreadcrumbItem[]; @@ -12,19 +14,14 @@ export const initialState: ViewconfState = { stickyHeader: false, }; -export function viewconfReducer(state: ViewconfState = initialState, action: ViewconfActions): ViewconfState { - switch (action.type) { - case ViewconfActionTypes.SetBreadcrumbData: - return { - ...state, - breadcrumbData: action.payload.breadcrumbData, - }; - case ViewconfActionTypes.SetStickyHeader: - return { - ...state, - stickyHeader: action.payload.sticky, - }; - } - - return state; -} +export const viewconfReducer = createReducer( + initialState, + on(setBreadcrumbData, (state: ViewconfState, action) => ({ + ...state, + breadcrumbData: action.payload.breadcrumbData, + })), + on(setStickyHeader, (state: ViewconfState, action) => ({ + ...state, + stickyHeader: action.payload.sticky, + })) +); diff --git a/src/app/core/store/customer/addresses/addresses.actions.ts b/src/app/core/store/customer/addresses/addresses.actions.ts index 47c0b73420..2f9ff5aa8f 100644 --- a/src/app/core/store/customer/addresses/addresses.actions.ts +++ b/src/app/core/store/customer/addresses/addresses.actions.ts @@ -1,85 +1,41 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; import { Address } from 'ish-core/models/address/address.model'; -import { HttpError } from 'ish-core/models/http-error/http-error.model'; +import { httpError, payload } from 'ish-core/utils/ngrx-creators'; -export enum AddressActionTypes { - LoadAddresses = '[Address Internal] Load Addresses', - LoadAddressesFail = '[Address API] Load Addresses Fail', - LoadAddressesSuccess = '[Address API] Load Addresses Success', - CreateCustomerAddress = '[Address] Create Customer Address', - CreateCustomerAddressFail = '[Address API] Create Customer Address Fail', - CreateCustomerAddressSuccess = '[Address API] Create Customer Address Success', - UpdateCustomerAddressFail = '[Address API] Update Customer Address Fail', - UpdateCustomerAddressSuccess = '[Address API] Update Customer Address Success', - DeleteCustomerAddress = '[Address] Delete Customer Address', - DeleteCustomerAddressFail = '[Address API] Delete Customer Address Fail', - DeleteCustomerAddressSuccess = '[Address API] Delete Customer Address Success', -} +export const loadAddresses = createAction('[Address Internal] Load Addresses'); -export class LoadAddresses implements Action { - readonly type = AddressActionTypes.LoadAddresses; -} +export const loadAddressesFail = createAction('[Address API] Load Addresses Fail', httpError()); -export class LoadAddressesFail implements Action { - readonly type = AddressActionTypes.LoadAddressesFail; - constructor(public payload: { error: HttpError }) {} -} +export const loadAddressesSuccess = createAction( + '[Address API] Load Addresses Success', + payload<{ addresses: Address[] }>() +); -export class LoadAddressesSuccess implements Action { - readonly type = AddressActionTypes.LoadAddressesSuccess; - constructor(public payload: { addresses: Address[] }) {} -} +export const createCustomerAddress = createAction('[Address] Create Customer Address', payload<{ address: Address }>()); -export class CreateCustomerAddress implements Action { - readonly type = AddressActionTypes.CreateCustomerAddress; - constructor(public payload: { address: Address }) {} -} +export const createCustomerAddressFail = createAction('[Address API] Create Customer Address Fail', httpError()); -export class CreateCustomerAddressFail implements Action { - readonly type = AddressActionTypes.CreateCustomerAddressFail; - constructor(public payload: { error: HttpError }) {} -} +export const createCustomerAddressSuccess = createAction( + '[Address API] Create Customer Address Success', + payload<{ address: Address }>() +); -export class CreateCustomerAddressSuccess implements Action { - readonly type = AddressActionTypes.CreateCustomerAddressSuccess; - constructor(public payload: { address: Address }) {} -} +export const updateCustomerAddressFail = createAction('[Address API] Update Customer Address Fail', httpError()); -export class UpdateCustomerAddressFail implements Action { - readonly type = AddressActionTypes.UpdateCustomerAddressFail; - constructor(public payload: { error: HttpError }) {} -} +export const updateCustomerAddressSuccess = createAction( + '[Address API] Update Customer Address Success', + payload<{ address: Address }>() +); -export class UpdateCustomerAddressSuccess implements Action { - readonly type = AddressActionTypes.UpdateCustomerAddressSuccess; - constructor(public payload: { address: Address }) {} -} +export const deleteCustomerAddress = createAction( + '[Address] Delete Customer Address', + payload<{ addressId: string }>() +); -export class DeleteCustomerAddress implements Action { - readonly type = AddressActionTypes.DeleteCustomerAddress; - constructor(public payload: { addressId: string }) {} -} +export const deleteCustomerAddressFail = createAction('[Address API] Delete Customer Address Fail', httpError()); -export class DeleteCustomerAddressFail implements Action { - readonly type = AddressActionTypes.DeleteCustomerAddressFail; - constructor(public payload: { error: HttpError }) {} -} - -export class DeleteCustomerAddressSuccess implements Action { - readonly type = AddressActionTypes.DeleteCustomerAddressSuccess; - constructor(public payload: { addressId: string }) {} -} - -export type AddressAction = - | LoadAddresses - | LoadAddressesFail - | LoadAddressesSuccess - | CreateCustomerAddress - | CreateCustomerAddressFail - | CreateCustomerAddressSuccess - | UpdateCustomerAddressFail - | UpdateCustomerAddressSuccess - | DeleteCustomerAddress - | DeleteCustomerAddressFail - | DeleteCustomerAddressSuccess; +export const deleteCustomerAddressSuccess = createAction( + '[Address API] Delete Customer Address Success', + payload<{ addressId: string }>() +); diff --git a/src/app/core/store/customer/addresses/addresses.effects.spec.ts b/src/app/core/store/customer/addresses/addresses.effects.spec.ts index 2a04dec036..01b85c6933 100644 --- a/src/app/core/store/customer/addresses/addresses.effects.spec.ts +++ b/src/app/core/store/customer/addresses/addresses.effects.spec.ts @@ -10,19 +10,19 @@ import { Customer } from 'ish-core/models/customer/customer.model'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { AddressService } from 'ish-core/services/address/address.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; -import { DisplaySuccessMessage } from 'ish-core/store/core/messages'; +import { displaySuccessMessage } from 'ish-core/store/core/messages'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; -import { LoginUserSuccess } from 'ish-core/store/customer/user'; +import { loginUserSuccess } from 'ish-core/store/customer/user'; import { - CreateCustomerAddress, - CreateCustomerAddressFail, - CreateCustomerAddressSuccess, - DeleteCustomerAddress, - DeleteCustomerAddressFail, - DeleteCustomerAddressSuccess, - LoadAddresses, - LoadAddressesSuccess, + createCustomerAddress, + createCustomerAddressFail, + createCustomerAddressSuccess, + deleteCustomerAddress, + deleteCustomerAddressFail, + deleteCustomerAddressSuccess, + loadAddresses, + loadAddressesSuccess, } from './addresses.actions'; import { AddressesEffects } from './addresses.effects'; @@ -51,12 +51,12 @@ describe('Addresses Effects', () => { effects = TestBed.inject(AddressesEffects); store$ = TestBed.inject(Store); const customer = { customerNo: 'patricia' } as Customer; - store$.dispatch(new LoginUserSuccess({ customer })); + store$.dispatch(loginUserSuccess({ customer })); }); describe('loadAddresses$', () => { it('should call the addressService for loadAddresses', done => { - const action = new LoadAddresses(); + const action = loadAddresses(); actions$ = of(action); effects.loadAddresses$.subscribe(() => { @@ -66,8 +66,8 @@ describe('Addresses Effects', () => { }); it('should map to action of type LoadAddressesSuccess', () => { - const action = new LoadAddresses(); - const completion = new LoadAddressesSuccess({ addresses: [{ urn: 'test' } as Address] }); + const action = loadAddresses(); + const completion = loadAddressesSuccess({ addresses: [{ urn: 'test' } as Address] }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -78,7 +78,7 @@ describe('Addresses Effects', () => { describe('createCustomerAddress$', () => { it('should call the addressService for createCustomerAddress', done => { const address = { urn: '123' } as Address; - const action = new CreateCustomerAddress({ address }); + const action = createCustomerAddress({ address }); actions$ = of(action); effects.createCustomerAddress$.subscribe(() => { @@ -89,9 +89,9 @@ describe('Addresses Effects', () => { it('should map to action of type CreateCustomerSuccess', () => { const address = { urn: '123' } as Address; - const action = new CreateCustomerAddress({ address }); - const completion = new CreateCustomerAddressSuccess({ address: { urn: 'test' } as Address }); - const completion2 = new DisplaySuccessMessage({ + const action = createCustomerAddress({ address }); + const completion = createCustomerAddressSuccess({ address: { urn: 'test' } as Address }); + const completion2 = displaySuccessMessage({ message: 'account.addresses.new_address_created.message', }); @@ -106,9 +106,9 @@ describe('Addresses Effects', () => { throwError({ message: 'invalid' }) ); const address = { urn: '123' } as Address; - const action = new CreateCustomerAddress({ address }); + const action = createCustomerAddress({ address }); const error = { message: 'invalid' } as HttpError; - const completion = new CreateCustomerAddressFail({ error }); + const completion = createCustomerAddressFail({ error }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -119,7 +119,7 @@ describe('Addresses Effects', () => { describe('deleteCustomerAddress$', () => { it('should call the addressService for deleteCustomerAddress', done => { const addressId = '123'; - const action = new DeleteCustomerAddress({ addressId }); + const action = deleteCustomerAddress({ addressId }); actions$ = of(action); effects.deleteCustomerAddress$.subscribe(() => { @@ -130,9 +130,9 @@ describe('Addresses Effects', () => { it('should map to action of type DeleteCustomerSuccess', () => { const addressId = '123'; - const action = new DeleteCustomerAddress({ addressId }); - const completion = new DeleteCustomerAddressSuccess({ addressId }); - const completion2 = new DisplaySuccessMessage({ + const action = deleteCustomerAddress({ addressId }); + const completion = deleteCustomerAddressSuccess({ addressId }); + const completion2 = displaySuccessMessage({ message: 'account.addresses.new_address_deleted.message', }); actions$ = hot('-a----a----a----|', { a: action }); @@ -146,9 +146,9 @@ describe('Addresses Effects', () => { throwError({ message: 'invalid' }) ); const addressId = '123'; - const action = new DeleteCustomerAddress({ addressId }); + const action = deleteCustomerAddress({ addressId }); const error = { message: 'invalid' } as HttpError; - const completion = new DeleteCustomerAddressFail({ error }); + const completion = deleteCustomerAddressFail({ error }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); diff --git a/src/app/core/store/customer/addresses/addresses.effects.ts b/src/app/core/store/customer/addresses/addresses.effects.ts index 12e3d70ba5..45a573fe66 100644 --- a/src/app/core/store/customer/addresses/addresses.effects.ts +++ b/src/app/core/store/customer/addresses/addresses.effects.ts @@ -1,37 +1,38 @@ import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { concatMap, filter, map, mergeMap, switchMap, withLatestFrom } from 'rxjs/operators'; import { AddressService } from 'ish-core/services/address/address.service'; -import { DisplaySuccessMessage } from 'ish-core/store/core/messages'; +import { displaySuccessMessage } from 'ish-core/store/core/messages'; import { getLoggedInCustomer } from 'ish-core/store/customer/user'; import { mapErrorToAction, mapToPayloadProperty } from 'ish-core/utils/operators'; import { - AddressActionTypes, - CreateCustomerAddress, - CreateCustomerAddressFail, - CreateCustomerAddressSuccess, - DeleteCustomerAddress, - DeleteCustomerAddressFail, - DeleteCustomerAddressSuccess, - LoadAddressesFail, - LoadAddressesSuccess, + createCustomerAddress, + createCustomerAddressFail, + createCustomerAddressSuccess, + deleteCustomerAddress, + deleteCustomerAddressFail, + deleteCustomerAddressSuccess, + loadAddresses, + loadAddressesFail, + loadAddressesSuccess, } from './addresses.actions'; @Injectable() export class AddressesEffects { constructor(private actions$: Actions, private addressService: AddressService, private store: Store) {} - @Effect() - loadAddresses$ = this.actions$.pipe( - ofType(AddressActionTypes.LoadAddresses), - withLatestFrom(this.store.pipe(select(getLoggedInCustomer))), - switchMap(([, customer]) => - this.addressService.getCustomerAddresses(customer.customerNo).pipe( - map(addresses => new LoadAddressesSuccess({ addresses })), - mapErrorToAction(LoadAddressesFail) + loadAddresses$ = createEffect(() => + this.actions$.pipe( + ofType(loadAddresses), + withLatestFrom(this.store.pipe(select(getLoggedInCustomer))), + switchMap(([, customer]) => + this.addressService.getCustomerAddresses(customer.customerNo).pipe( + map(addresses => loadAddressesSuccess({ addresses })), + mapErrorToAction(loadAddressesFail) + ) ) ) ); @@ -39,21 +40,22 @@ export class AddressesEffects { /** * Creates a new customer address. */ - @Effect() - createCustomerAddress$ = this.actions$.pipe( - ofType(AddressActionTypes.CreateCustomerAddress), - mapToPayloadProperty('address'), - withLatestFrom(this.store.pipe(select(getLoggedInCustomer))), - filter(([address, customer]) => !!address || !!customer), - concatMap(([address, customer]) => - this.addressService.createCustomerAddress(customer.customerNo, address).pipe( - mergeMap(newAddress => [ - new CreateCustomerAddressSuccess({ address: newAddress }), - new DisplaySuccessMessage({ - message: 'account.addresses.new_address_created.message', - }), - ]), - mapErrorToAction(CreateCustomerAddressFail) + createCustomerAddress$ = createEffect(() => + this.actions$.pipe( + ofType(createCustomerAddress), + mapToPayloadProperty('address'), + withLatestFrom(this.store.pipe(select(getLoggedInCustomer))), + filter(([address, customer]) => !!address || !!customer), + concatMap(([address, customer]) => + this.addressService.createCustomerAddress(customer.customerNo, address).pipe( + mergeMap(newAddress => [ + createCustomerAddressSuccess({ address: newAddress }), + displaySuccessMessage({ + message: 'account.addresses.new_address_created.message', + }), + ]), + mapErrorToAction(createCustomerAddressFail) + ) ) ) ); @@ -61,19 +63,20 @@ export class AddressesEffects { /** * Deletes a customer address. */ - @Effect() - deleteCustomerAddress$ = this.actions$.pipe( - ofType(AddressActionTypes.DeleteCustomerAddress), - mapToPayloadProperty('addressId'), - withLatestFrom(this.store.pipe(select(getLoggedInCustomer))), - filter(([addressId, customer]) => !!addressId || !!customer), - mergeMap(([addressId, customer]) => - this.addressService.deleteCustomerAddress(customer.customerNo, addressId).pipe( - mergeMap(id => [ - new DeleteCustomerAddressSuccess({ addressId: id }), - new DisplaySuccessMessage({ message: 'account.addresses.new_address_deleted.message' }), - ]), - mapErrorToAction(DeleteCustomerAddressFail) + deleteCustomerAddress$ = createEffect(() => + this.actions$.pipe( + ofType(deleteCustomerAddress), + mapToPayloadProperty('addressId'), + withLatestFrom(this.store.pipe(select(getLoggedInCustomer))), + filter(([addressId, customer]) => !!addressId || !!customer), + mergeMap(([addressId, customer]) => + this.addressService.deleteCustomerAddress(customer.customerNo, addressId).pipe( + mergeMap(id => [ + deleteCustomerAddressSuccess({ addressId: id }), + displaySuccessMessage({ message: 'account.addresses.new_address_deleted.message' }), + ]), + mapErrorToAction(deleteCustomerAddressFail) + ) ) ) ); diff --git a/src/app/core/store/customer/addresses/addresses.reducer.spec.ts b/src/app/core/store/customer/addresses/addresses.reducer.spec.ts index 0e227b6e1c..91a9121d5f 100644 --- a/src/app/core/store/customer/addresses/addresses.reducer.spec.ts +++ b/src/app/core/store/customer/addresses/addresses.reducer.spec.ts @@ -1,25 +1,25 @@ import { Address } from 'ish-core/models/address/address.model'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { - CreateBasketAddress, - CreateBasketAddressSuccess, - DeleteBasketShippingAddress, - UpdateBasketAddress, + createBasketAddress, + createBasketAddressSuccess, + deleteBasketShippingAddress, + updateBasketAddress, } from 'ish-core/store/customer/basket/basket.actions'; import { BasketMockData } from 'ish-core/utils/dev/basket-mock-data'; import { - CreateCustomerAddress, - CreateCustomerAddressFail, - CreateCustomerAddressSuccess, - DeleteCustomerAddress, - DeleteCustomerAddressFail, - DeleteCustomerAddressSuccess, - LoadAddresses, - LoadAddressesFail, - LoadAddressesSuccess, - UpdateCustomerAddressFail, - UpdateCustomerAddressSuccess, + createCustomerAddress, + createCustomerAddressFail, + createCustomerAddressSuccess, + deleteCustomerAddress, + deleteCustomerAddressFail, + deleteCustomerAddressSuccess, + loadAddresses, + loadAddressesFail, + loadAddressesSuccess, + updateCustomerAddressFail, + updateCustomerAddressSuccess, } from './addresses.actions'; import { addressesReducer, initialState } from './addresses.reducer'; @@ -27,7 +27,7 @@ describe('Addresses Reducer', () => { describe('LoadAddresses actions', () => { describe('LoadAddresses action', () => { it('should set loading to true', () => { - const action = new LoadAddresses(); + const action = loadAddresses(); const state = addressesReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -37,7 +37,7 @@ describe('Addresses Reducer', () => { describe('LoadAddressesFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new LoadAddressesFail({ error }); + const action = loadAddressesFail({ error }); const state = addressesReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -53,7 +53,7 @@ describe('Addresses Reducer', () => { } as Address, ]; - const action = new LoadAddressesSuccess({ addresses }); + const action = loadAddressesSuccess({ addresses }); const state = addressesReducer(initialState, action); expect(state.ids).toHaveLength(1); @@ -66,7 +66,7 @@ describe('Addresses Reducer', () => { describe('CreateCustomerAddress actions', () => { describe('CreateCustomerAddress action', () => { it('should set loading to true', () => { - const action = new CreateCustomerAddress({ address: BasketMockData.getAddress() }); + const action = createCustomerAddress({ address: BasketMockData.getAddress() }); const state = addressesReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -75,7 +75,7 @@ describe('Addresses Reducer', () => { describe('CreateBasketAddress action', () => { it('should set loading to true', () => { - const action = new CreateBasketAddress({ address: BasketMockData.getAddress(), scope: 'invoice' }); + const action = createBasketAddress({ address: BasketMockData.getAddress(), scope: 'invoice' }); const state = addressesReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -85,7 +85,7 @@ describe('Addresses Reducer', () => { describe('CreateCustomerAddressFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new CreateCustomerAddressFail({ error }); + const action = createCustomerAddressFail({ error }); const state = addressesReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -99,7 +99,7 @@ describe('Addresses Reducer', () => { id: 'test', } as Address; - const action = new CreateCustomerAddressSuccess({ address }); + const action = createCustomerAddressSuccess({ address }); const state = addressesReducer(initialState, action); expect(state.ids).toHaveLength(1); @@ -114,7 +114,7 @@ describe('Addresses Reducer', () => { id: 'test', } as Address; - const action = new CreateBasketAddressSuccess({ address, scope: 'invoice' }); + const action = createBasketAddressSuccess({ address, scope: 'invoice' }); const state = addressesReducer(initialState, action); expect(state.ids).toHaveLength(1); @@ -127,7 +127,7 @@ describe('Addresses Reducer', () => { describe('UpdateCustomerAddress actions', () => { describe('UpdateBasketCustomerAddress action', () => { it('should set loading to true', () => { - const action = new UpdateBasketAddress({ address: BasketMockData.getAddress() }); + const action = updateBasketAddress({ address: BasketMockData.getAddress() }); const state = addressesReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -137,7 +137,7 @@ describe('Addresses Reducer', () => { describe('UpdateCustomerAddressFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new UpdateCustomerAddressFail({ error }); + const action = updateCustomerAddressFail({ error }); const state = addressesReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -152,11 +152,11 @@ describe('Addresses Reducer', () => { firstName: 'Patricia', } as Address; - const preAction = new CreateBasketAddressSuccess({ address, scope: 'shipping' }); + const preAction = createBasketAddressSuccess({ address, scope: 'shipping' }); let state = addressesReducer(initialState, preAction); address.firstName = 'John'; - const action = new UpdateCustomerAddressSuccess({ address }); + const action = updateCustomerAddressSuccess({ address }); state = addressesReducer(state, action); expect(state.ids).toHaveLength(1); @@ -169,7 +169,7 @@ describe('Addresses Reducer', () => { describe('DeleteCustomerAddress actions', () => { describe('DeleteBasketShippingAddress action', () => { it('should set loading to true', () => { - const action = new DeleteBasketShippingAddress({ addressId: 'addressId' }); + const action = deleteBasketShippingAddress({ addressId: 'addressId' }); const state = addressesReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -178,7 +178,7 @@ describe('Addresses Reducer', () => { describe('DeleteCustomerAddress action', () => { it('should set loading to true', () => { - const action = new DeleteCustomerAddress({ addressId: 'addressId' }); + const action = deleteCustomerAddress({ addressId: 'addressId' }); const state = addressesReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -188,7 +188,7 @@ describe('Addresses Reducer', () => { describe('DeleteCustomerAddressFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new DeleteCustomerAddressFail({ error }); + const action = deleteCustomerAddressFail({ error }); const state = addressesReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -202,10 +202,10 @@ describe('Addresses Reducer', () => { id: 'addressId', } as Address; - const preAction = new CreateBasketAddressSuccess({ address, scope: 'shipping' }); + const preAction = createBasketAddressSuccess({ address, scope: 'shipping' }); let state = addressesReducer(initialState, preAction); - const action = new DeleteCustomerAddressSuccess({ addressId: address.id }); + const action = deleteCustomerAddressSuccess({ addressId: address.id }); state = addressesReducer(state, action); expect(state.ids).toHaveLength(0); diff --git a/src/app/core/store/customer/addresses/addresses.reducer.ts b/src/app/core/store/customer/addresses/addresses.reducer.ts index c6737faa6a..b23beb1818 100644 --- a/src/app/core/store/customer/addresses/addresses.reducer.ts +++ b/src/app/core/store/customer/addresses/addresses.reducer.ts @@ -1,10 +1,29 @@ import { EntityState, createEntityAdapter } from '@ngrx/entity'; +import { createReducer, on } from '@ngrx/store'; import { Address } from 'ish-core/models/address/address.model'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; -import { BasketAction, BasketActionTypes } from 'ish-core/store/customer/basket'; - -import { AddressAction, AddressActionTypes } from './addresses.actions'; +import { + createBasketAddress, + createBasketAddressSuccess, + deleteBasketShippingAddress, + updateBasketAddress, +} from 'ish-core/store/customer/basket'; +import { setErrorOn, setLoadingOn } from 'ish-core/utils/ngrx-creators'; + +import { + createCustomerAddress, + createCustomerAddressFail, + createCustomerAddressSuccess, + deleteCustomerAddress, + deleteCustomerAddressFail, + deleteCustomerAddressSuccess, + loadAddresses, + loadAddressesFail, + loadAddressesSuccess, + updateCustomerAddressFail, + updateCustomerAddressSuccess, +} from './addresses.actions'; export const addressAdapter = createEntityAdapter
({}); @@ -18,74 +37,51 @@ export const initialState: AddressesState = addressAdapter.getInitialState({ error: undefined, }); -export function addressesReducer(state = initialState, action: AddressAction | BasketAction): AddressesState { - switch (action.type) { - case AddressActionTypes.LoadAddresses: - case AddressActionTypes.CreateCustomerAddress: - case BasketActionTypes.CreateBasketAddress: - case BasketActionTypes.UpdateBasketAddress: - case AddressActionTypes.DeleteCustomerAddress: - case BasketActionTypes.DeleteBasketShippingAddress: { - return { - ...state, - loading: true, - }; - } - - case AddressActionTypes.LoadAddressesFail: - case AddressActionTypes.CreateCustomerAddressFail: - case AddressActionTypes.UpdateCustomerAddressFail: - case AddressActionTypes.DeleteCustomerAddressFail: { - const { error } = action.payload; - - return { - ...state, - error, - loading: false, - }; - } - - case AddressActionTypes.LoadAddressesSuccess: { - const { addresses } = action.payload; - - return { - ...addressAdapter.setAll(addresses, state), - error: undefined, - loading: false, - }; - } - - case AddressActionTypes.CreateCustomerAddressSuccess: - case BasketActionTypes.CreateBasketAddressSuccess: { - const { address } = action.payload; - - return { - ...addressAdapter.addOne(address, state), - loading: false, - error: undefined, - }; - } - - case AddressActionTypes.UpdateCustomerAddressSuccess: { - const { address } = action.payload; - - return { - ...addressAdapter.updateOne({ id: address.id, changes: address }, state), - loading: false, - error: undefined, - }; - } - - case AddressActionTypes.DeleteCustomerAddressSuccess: { - const { addressId } = action.payload; - - return { - ...addressAdapter.removeOne(addressId, state), - loading: false, - error: undefined, - }; - } - } - - return state; -} +export const addressesReducer = createReducer( + initialState, + setLoadingOn( + loadAddresses, + createCustomerAddress, + createBasketAddress, + updateBasketAddress, + deleteCustomerAddress, + deleteBasketShippingAddress + ), + setErrorOn(loadAddressesFail, createCustomerAddressFail, updateCustomerAddressFail, deleteCustomerAddressFail), + on(loadAddressesSuccess, (state: AddressesState, action) => { + const { addresses } = action.payload; + + return { + ...addressAdapter.setAll(addresses, state), + error: undefined, + loading: false, + }; + }), + on(createCustomerAddressSuccess, createBasketAddressSuccess, (state: AddressesState, action) => { + const { address } = action.payload; + + return { + ...addressAdapter.addOne(address, state), + loading: false, + error: undefined, + }; + }), + on(updateCustomerAddressSuccess, (state: AddressesState, action) => { + const { address } = action.payload; + + return { + ...addressAdapter.updateOne({ id: address.id, changes: address }, state), + loading: false, + error: undefined, + }; + }), + on(deleteCustomerAddressSuccess, (state: AddressesState, action) => { + const { addressId } = action.payload; + + return { + ...addressAdapter.removeOne(addressId, state), + loading: false, + error: undefined, + }; + }) +); diff --git a/src/app/core/store/customer/addresses/addresses.selectors.spec.ts b/src/app/core/store/customer/addresses/addresses.selectors.spec.ts index 3f723a6bb9..eeeec18893 100644 --- a/src/app/core/store/customer/addresses/addresses.selectors.spec.ts +++ b/src/app/core/store/customer/addresses/addresses.selectors.spec.ts @@ -6,7 +6,7 @@ import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; -import { LoadAddresses, LoadAddressesFail, LoadAddressesSuccess } from './addresses.actions'; +import { loadAddresses, loadAddressesFail, loadAddressesSuccess } from './addresses.actions'; import { getAddressesError, getAddressesLoading, getAllAddresses } from './addresses.selectors'; describe('Addresses Selectors', () => { @@ -35,7 +35,7 @@ describe('Addresses Selectors', () => { describe('loading addresses', () => { beforeEach(() => { - store$.dispatch(new LoadAddresses()); + store$.dispatch(loadAddresses()); }); it('should set the state to loading', () => { @@ -44,7 +44,7 @@ describe('Addresses Selectors', () => { describe('and reporting success', () => { beforeEach(() => { - store$.dispatch(new LoadAddressesSuccess({ addresses })); + store$.dispatch(loadAddressesSuccess({ addresses })); }); it('should set loading to false', () => { @@ -55,7 +55,7 @@ describe('Addresses Selectors', () => { describe('and reporting failure', () => { beforeEach(() => { - store$.dispatch(new LoadAddressesFail({ error: { message: 'error' } as HttpError })); + store$.dispatch(loadAddressesFail({ error: { message: 'error' } as HttpError })); }); it('should not have loaded addresses on error', () => { diff --git a/src/app/core/store/customer/basket/basket-addresses.effects.spec.ts b/src/app/core/store/customer/basket/basket-addresses.effects.spec.ts index 36b53ad7f7..c52680d770 100644 --- a/src/app/core/store/customer/basket/basket-addresses.effects.spec.ts +++ b/src/app/core/store/customer/basket/basket-addresses.effects.spec.ts @@ -12,25 +12,25 @@ import { BasketService } from 'ish-core/services/basket/basket.service'; import { OrderService } from 'ish-core/services/order/order.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { - DeleteCustomerAddressFail, - DeleteCustomerAddressSuccess, - UpdateCustomerAddressFail, - UpdateCustomerAddressSuccess, + deleteCustomerAddressFail, + deleteCustomerAddressSuccess, + updateCustomerAddressFail, + updateCustomerAddressSuccess, } from 'ish-core/store/customer/addresses'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; -import { LoginUserSuccess } from 'ish-core/store/customer/user'; +import { loginUserSuccess } from 'ish-core/store/customer/user'; import { BasketMockData } from 'ish-core/utils/dev/basket-mock-data'; import { BasketAddressesEffects } from './basket-addresses.effects'; import { - AssignBasketAddress, - CreateBasketAddress, - CreateBasketAddressSuccess, - DeleteBasketShippingAddress, - LoadBasket, - ResetBasketErrors, - UpdateBasket, - UpdateBasketAddress, + assignBasketAddress, + createBasketAddress, + createBasketAddressSuccess, + deleteBasketShippingAddress, + loadBasket, + resetBasketErrors, + updateBasket, + updateBasketAddress, } from './basket.actions'; describe('Basket Addresses Effects', () => { @@ -66,7 +66,7 @@ describe('Basket Addresses Effects', () => { when(addressServiceMock.createCustomerAddress('-', anything())).thenReturn(of(BasketMockData.getAddress())); store$.dispatch( - new LoginUserSuccess({ + loginUserSuccess({ customer: { customerNo: '4711', } as Customer, @@ -75,7 +75,7 @@ describe('Basket Addresses Effects', () => { }); it('should call the addressService if user is logged in', done => { const address = BasketMockData.getAddress(); - const action = new CreateBasketAddress({ address, scope: 'invoice' }); + const action = createBasketAddress({ address, scope: 'invoice' }); actions$ = of(action); effects.createAddressForBasket$.subscribe(() => { @@ -86,8 +86,8 @@ describe('Basket Addresses Effects', () => { it('should map to Action createBasketAddressSuccess', () => { const address = BasketMockData.getAddress(); - const action = new CreateBasketAddress({ address, scope: 'invoice' }); - const completion = new CreateBasketAddressSuccess({ address, scope: 'invoice' }); + const action = createBasketAddress({ address, scope: 'invoice' }); + const completion = createBasketAddressSuccess({ address, scope: 'invoice' }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -101,7 +101,7 @@ describe('Basket Addresses Effects', () => { }); it('should call the basketService if user is not logged in', done => { const address = BasketMockData.getAddress(); - const action = new CreateBasketAddress({ address, scope: 'invoice' }); + const action = createBasketAddress({ address, scope: 'invoice' }); actions$ = of(action); effects.createAddressForBasket$.subscribe(() => { @@ -112,8 +112,8 @@ describe('Basket Addresses Effects', () => { it('should map to Action createBasketAddressSuccess', () => { const address = BasketMockData.getAddress(); - const action = new CreateBasketAddress({ address, scope: 'invoice' }); - const completion = new CreateBasketAddressSuccess({ address, scope: 'invoice' }); + const action = createBasketAddress({ address, scope: 'invoice' }); + const completion = createBasketAddressSuccess({ address, scope: 'invoice' }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -124,8 +124,8 @@ describe('Basket Addresses Effects', () => { describe('assignNewAddressToBasket$', () => { it('should map to Action AssignBasketAddress for Invoice Address', () => { const address = BasketMockData.getAddress(); - const action = new CreateBasketAddressSuccess({ address, scope: 'invoice' }); - const completion = new AssignBasketAddress({ addressId: address.id, scope: 'invoice' }); + const action = createBasketAddressSuccess({ address, scope: 'invoice' }); + const completion = assignBasketAddress({ addressId: address.id, scope: 'invoice' }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -134,8 +134,8 @@ describe('Basket Addresses Effects', () => { it('should map to Action AssignBasketAddress for Shipping Address', () => { const address = BasketMockData.getAddress(); - const action = new CreateBasketAddressSuccess({ address, scope: 'shipping' }); - const completion = new AssignBasketAddress({ addressId: address.id, scope: 'shipping' }); + const action = createBasketAddressSuccess({ address, scope: 'shipping' }); + const completion = assignBasketAddress({ addressId: address.id, scope: 'shipping' }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -144,8 +144,8 @@ describe('Basket Addresses Effects', () => { it('should map to Action AssignBasketAddress for Invoice and Shipping Address', () => { const address = BasketMockData.getAddress(); - const action = new CreateBasketAddressSuccess({ address, scope: 'any' }); - const completion = new AssignBasketAddress({ addressId: address.id, scope: 'any' }); + const action = createBasketAddressSuccess({ address, scope: 'any' }); + const completion = assignBasketAddress({ addressId: address.id, scope: 'any' }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -156,8 +156,8 @@ describe('Basket Addresses Effects', () => { describe('assignBasketAddress$', () => { it('should trigger the updateBasket action to assign an Invoice Address', () => { const addressId = 'addressId'; - const action = new AssignBasketAddress({ addressId, scope: 'invoice' }); - const completion = new UpdateBasket({ + const action = assignBasketAddress({ addressId, scope: 'invoice' }); + const completion = updateBasket({ update: { invoiceToAddress: addressId }, }); actions$ = hot('-a-a-a', { a: action }); @@ -168,9 +168,9 @@ describe('Basket Addresses Effects', () => { it('should trigger the updateBasket action to assign a Shipping Address', () => { const addressId = 'addressId'; - const action = new AssignBasketAddress({ addressId, scope: 'shipping' }); + const action = assignBasketAddress({ addressId, scope: 'shipping' }); - const completion = new UpdateBasket({ + const completion = updateBasket({ update: { commonShipToAddress: addressId }, }); actions$ = hot('-a-a-a', { a: action }); @@ -181,8 +181,8 @@ describe('Basket Addresses Effects', () => { it('should trigger the updateBasket action to assign an Invoice and Shipping Address', () => { const addressId = 'addressId'; - const action = new AssignBasketAddress({ addressId, scope: 'any' }); - const completion = new UpdateBasket({ + const action = assignBasketAddress({ addressId, scope: 'any' }); + const completion = updateBasket({ update: { invoiceToAddress: addressId, commonShipToAddress: addressId }, }); actions$ = hot('-a-a-a', { a: action }); @@ -197,12 +197,12 @@ describe('Basket Addresses Effects', () => { when(addressServiceMock.updateCustomerAddress(anyString(), anything())).thenReturn( of(BasketMockData.getAddress()) ); - store$.dispatch(new LoginUserSuccess({ customer: {} as Customer })); + store$.dispatch(loginUserSuccess({ customer: {} as Customer })); }); it('should call the addressService for updateBasketAddress', done => { const address = BasketMockData.getAddress(); - const action = new UpdateBasketAddress({ address }); + const action = updateBasketAddress({ address }); actions$ = of(action); effects.updateBasketAddress$.subscribe(() => { @@ -213,10 +213,10 @@ describe('Basket Addresses Effects', () => { it('should map to action of type UpdateCustomerAddressSuccess and LoadBasket', () => { const address = BasketMockData.getAddress(); - const action = new UpdateBasketAddress({ address }); - const completion1 = new UpdateCustomerAddressSuccess({ address }); - const completion2 = new LoadBasket(); - const completion3 = new ResetBasketErrors(); + const action = updateBasketAddress({ address }); + const completion1 = updateCustomerAddressSuccess({ address }); + const completion2 = loadBasket(); + const completion3 = resetBasketErrors(); actions$ = hot('-a', { a: action }); const expected$ = cold('-(cde)', { c: completion1, d: completion2, e: completion3 }); @@ -229,8 +229,8 @@ describe('Basket Addresses Effects', () => { throwError({ message: 'invalid' }) ); - const action = new UpdateBasketAddress({ address }); - const completion = new UpdateCustomerAddressFail({ error: { message: 'invalid' } as HttpError }); + const action = updateBasketAddress({ address }); + const completion = updateCustomerAddressFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -245,7 +245,7 @@ describe('Basket Addresses Effects', () => { it('should call the basketService for updateBasketAddress', done => { const address = BasketMockData.getAddress(); - const action = new UpdateBasketAddress({ address }); + const action = updateBasketAddress({ address }); actions$ = of(action); effects.updateBasketAddress$.subscribe(() => { @@ -256,10 +256,10 @@ describe('Basket Addresses Effects', () => { it('should map to action of type UpdateCustomerAddressSuccess and LoadBasket', () => { const address = BasketMockData.getAddress(); - const action = new UpdateBasketAddress({ address }); - const completion1 = new UpdateCustomerAddressSuccess({ address }); - const completion2 = new LoadBasket(); - const completion3 = new ResetBasketErrors(); + const action = updateBasketAddress({ address }); + const completion1 = updateCustomerAddressSuccess({ address }); + const completion2 = loadBasket(); + const completion3 = resetBasketErrors(); actions$ = hot('-a', { a: action }); const expected$ = cold('-(cde)', { c: completion1, d: completion2, e: completion3 }); @@ -272,8 +272,8 @@ describe('Basket Addresses Effects', () => { throwError({ message: 'invalid' }) ); - const action = new UpdateBasketAddress({ address }); - const completion = new UpdateCustomerAddressFail({ error: { message: 'invalid' } as HttpError }); + const action = updateBasketAddress({ address }); + const completion = updateCustomerAddressFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -288,7 +288,7 @@ describe('Basket Addresses Effects', () => { it('should call the addressService for deleteBasketShippingAddress', done => { const addressId = 'addressId'; - const action = new DeleteBasketShippingAddress({ addressId }); + const action = deleteBasketShippingAddress({ addressId }); actions$ = of(action); effects.deleteBasketShippingAddress$.subscribe(() => { @@ -299,9 +299,9 @@ describe('Basket Addresses Effects', () => { it('should map to action of type DeleteCustomerAddressSuccess and LoadBasket', () => { const addressId = 'addressId'; - const action = new DeleteBasketShippingAddress({ addressId }); - const completion1 = new DeleteCustomerAddressSuccess({ addressId }); - const completion2 = new LoadBasket(); + const action = deleteBasketShippingAddress({ addressId }); + const completion1 = deleteCustomerAddressSuccess({ addressId }); + const completion2 = loadBasket(); actions$ = hot('-a', { a: action }); const expected$ = cold('-(cd)', { c: completion1, d: completion2 }); @@ -314,8 +314,8 @@ describe('Basket Addresses Effects', () => { throwError({ message: 'invalid' }) ); - const action = new DeleteBasketShippingAddress({ addressId }); - const completion = new DeleteCustomerAddressFail({ error: { message: 'invalid' } as HttpError }); + const action = deleteBasketShippingAddress({ addressId }); + const completion = deleteCustomerAddressFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); diff --git a/src/app/core/store/customer/basket/basket-addresses.effects.ts b/src/app/core/store/customer/basket/basket-addresses.effects.ts index 76289d867d..408e9ceb9d 100644 --- a/src/app/core/store/customer/basket/basket-addresses.effects.ts +++ b/src/app/core/store/customer/basket/basket-addresses.effects.ts @@ -1,30 +1,29 @@ import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { concatMapTo, map, mergeMap, withLatestFrom } from 'rxjs/operators'; import { AddressService } from 'ish-core/services/address/address.service'; import { BasketService, BasketUpdateType } from 'ish-core/services/basket/basket.service'; import { - CreateCustomerAddressFail, - DeleteCustomerAddressFail, - DeleteCustomerAddressSuccess, - UpdateCustomerAddressFail, - UpdateCustomerAddressSuccess, + createCustomerAddressFail, + deleteCustomerAddressFail, + deleteCustomerAddressSuccess, + updateCustomerAddressFail, + updateCustomerAddressSuccess, } from 'ish-core/store/customer/addresses'; import { getLoggedInCustomer } from 'ish-core/store/customer/user'; import { mapErrorToAction, mapToPayload, mapToPayloadProperty } from 'ish-core/utils/operators'; import { - AssignBasketAddress, - BasketActionTypes, - CreateBasketAddress, - CreateBasketAddressSuccess, - DeleteBasketShippingAddress, - LoadBasket, - ResetBasketErrors, - UpdateBasket, - UpdateBasketAddress, + assignBasketAddress, + createBasketAddress, + createBasketAddressSuccess, + deleteBasketShippingAddress, + loadBasket, + resetBasketErrors, + updateBasket, + updateBasketAddress, } from './basket.actions'; @Injectable() @@ -40,40 +39,41 @@ export class BasketAddressesEffects { * Creates a new invoice/shipping address which is assigned to the basket later on * if the user is logged in a customer address will be created, otherwise a new basket address will be created */ - @Effect() - createAddressForBasket$ = this.actions$.pipe( - ofType(BasketActionTypes.CreateBasketAddress), - withLatestFrom(this.store.pipe(select(getLoggedInCustomer))), + createAddressForBasket$ = createEffect(() => + this.actions$.pipe( + ofType(createBasketAddress), + withLatestFrom(this.store.pipe(select(getLoggedInCustomer))), - mergeMap(([action, customer]) => { - // create address at customer for logged in user - if (customer) { - return this.addressService.createCustomerAddress('-', action.payload.address).pipe( - map(newAddress => new CreateBasketAddressSuccess({ address: newAddress, scope: action.payload.scope })), - mapErrorToAction(CreateCustomerAddressFail) - ); - // create address at basket for anonymous user - } else { - return this.basketService.createBasketAddress('current', action.payload.address).pipe( - map(newAddress => new CreateBasketAddressSuccess({ address: newAddress, scope: action.payload.scope })), - mapErrorToAction(CreateCustomerAddressFail) - ); - } - }) + mergeMap(([action, customer]) => { + // create address at customer for logged in user + if (customer) { + return this.addressService.createCustomerAddress('-', action.payload.address).pipe( + map(newAddress => createBasketAddressSuccess({ address: newAddress, scope: action.payload.scope })), + mapErrorToAction(createCustomerAddressFail) + ); + // create address at basket for anonymous user + } else { + return this.basketService.createBasketAddress('current', action.payload.address).pipe( + map(newAddress => createBasketAddressSuccess({ address: newAddress, scope: action.payload.scope })), + mapErrorToAction(createCustomerAddressFail) + ); + } + }) + ) ); /** * Assigns an address that has just created as basket invoice/shipping address */ - @Effect() - assignNewAddressToBasket$ = this.actions$.pipe( - ofType(BasketActionTypes.CreateBasketAddressSuccess), - map( - action => - new AssignBasketAddress({ + assignNewAddressToBasket$ = createEffect(() => + this.actions$.pipe( + ofType(createBasketAddressSuccess), + map(action => + assignBasketAddress({ addressId: action.payload.address.id, scope: action.payload.scope, }) + ) ) ); @@ -81,74 +81,77 @@ export class BasketAddressesEffects { * Assigns the address to the basket according to the scope of the payload. * Triggers the internal UpdateBasket action that handles the actual updating operation. */ - @Effect() - assignBasketAddress$ = this.actions$.pipe( - ofType(BasketActionTypes.AssignBasketAddress), - mapToPayload(), - map(payload => { - let body: BasketUpdateType; - switch (payload.scope) { - case 'invoice': { - body = { invoiceToAddress: payload.addressId }; - break; + assignBasketAddress$ = createEffect(() => + this.actions$.pipe( + ofType(assignBasketAddress), + mapToPayload(), + map(payload => { + let body: BasketUpdateType; + switch (payload.scope) { + case 'invoice': { + body = { invoiceToAddress: payload.addressId }; + break; + } + case 'shipping': { + body = { commonShipToAddress: payload.addressId }; + break; + } + case 'any': { + body = { invoiceToAddress: payload.addressId, commonShipToAddress: payload.addressId }; + break; + } } - case 'shipping': { - body = { commonShipToAddress: payload.addressId }; - break; - } - case 'any': { - body = { invoiceToAddress: payload.addressId, commonShipToAddress: payload.addressId }; - break; - } - } - return new UpdateBasket({ update: body }); - }) + return updateBasket({ update: body }); + }) + ) ); /** * Updates an address (basket or customer) and reloads the basket in case of success. */ - @Effect() - updateBasketAddress$ = this.actions$.pipe( - ofType(BasketActionTypes.UpdateBasketAddress), - mapToPayloadProperty('address'), - withLatestFrom(this.store.pipe(select(getLoggedInCustomer))), - mergeMap(([address, customer]) => { - // create address at customer for logged in user - if (customer) { - return this.addressService - .updateCustomerAddress('-', address) - .pipe( - concatMapTo([new UpdateCustomerAddressSuccess({ address }), new LoadBasket(), new ResetBasketErrors()]), - mapErrorToAction(UpdateCustomerAddressFail) - ); - // create address at basket for anonymous user - } else { - return this.basketService - .updateBasketAddress('current', address) - .pipe( - concatMapTo([new UpdateCustomerAddressSuccess({ address }), new LoadBasket(), new ResetBasketErrors()]), - mapErrorToAction(UpdateCustomerAddressFail) - ); - } - }) + updateBasketAddress$ = createEffect(() => + this.actions$.pipe( + ofType(updateBasketAddress), + mapToPayloadProperty('address'), + withLatestFrom(this.store.pipe(select(getLoggedInCustomer))), + mergeMap(([address, customer]) => { + // create address at customer for logged in user + if (customer) { + return this.addressService + .updateCustomerAddress('-', address) + .pipe( + concatMapTo([updateCustomerAddressSuccess({ address }), loadBasket(), resetBasketErrors()]), + mapErrorToAction(updateCustomerAddressFail) + ); + // create address at basket for anonymous user + } else { + return this.basketService + .updateBasketAddress('current', address) + .pipe( + concatMapTo([updateCustomerAddressSuccess({ address }), loadBasket(), resetBasketErrors()]), + mapErrorToAction(updateCustomerAddressFail) + ); + } + }) + ) ); /** * Deletes a basket shipping address and reloads the basket in case of success. */ - @Effect() - deleteBasketShippingAddress$ = this.actions$.pipe( - ofType(BasketActionTypes.DeleteBasketShippingAddress), - mapToPayloadProperty('addressId'), - mergeMap(addressId => - this.addressService - .deleteCustomerAddress('-', addressId) - .pipe( - concatMapTo([new DeleteCustomerAddressSuccess({ addressId }), new LoadBasket()]), - mapErrorToAction(DeleteCustomerAddressFail) - ) + deleteBasketShippingAddress$ = createEffect(() => + this.actions$.pipe( + ofType(deleteBasketShippingAddress), + mapToPayloadProperty('addressId'), + mergeMap(addressId => + this.addressService + .deleteCustomerAddress('-', addressId) + .pipe( + concatMapTo([deleteCustomerAddressSuccess({ addressId }), loadBasket()]), + mapErrorToAction(deleteCustomerAddressFail) + ) + ) ) ); } diff --git a/src/app/core/store/customer/basket/basket-items.effects.spec.ts b/src/app/core/store/customer/basket/basket-items.effects.spec.ts index dde7920b12..aa89704f6c 100644 --- a/src/app/core/store/customer/basket/basket-items.effects.spec.ts +++ b/src/app/core/store/customer/basket/basket-items.effects.spec.ts @@ -16,24 +16,24 @@ import { BasketService } from 'ish-core/services/basket/basket.service'; import { OrderService } from 'ish-core/services/order/order.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; -import { LoadProduct, LoadProductSuccess } from 'ish-core/store/shopping/products'; +import { loadProduct, loadProductSuccess } from 'ish-core/store/shopping/products'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { BasketItemsEffects } from './basket-items.effects'; import { - AddItemsToBasket, - AddItemsToBasketFail, - AddItemsToBasketSuccess, - AddProductToBasket, - DeleteBasketItem, - DeleteBasketItemFail, - DeleteBasketItemSuccess, - LoadBasket, - LoadBasketSuccess, - UpdateBasketItems, - UpdateBasketItemsFail, - UpdateBasketItemsSuccess, - ValidateBasket, + addItemsToBasket, + addItemsToBasketFail, + addItemsToBasketSuccess, + addProductToBasket, + deleteBasketItem, + deleteBasketItemFail, + deleteBasketItemSuccess, + loadBasket, + loadBasketSuccess, + updateBasketItems, + updateBasketItemsFail, + updateBasketItemsSuccess, + validateBasket, } from './basket.actions'; describe('Basket Items Effects', () => { @@ -71,11 +71,11 @@ describe('Basket Items Effects', () => { describe('addProductToBasket$', () => { it('should accumulate AddProductToBasket to a single AddItemsToBasket action', () => { - store$.dispatch(new LoadProductSuccess({ product: { sku: 'SKU1', packingUnit: 'pcs.' } as Product })); - store$.dispatch(new LoadProductSuccess({ product: { sku: 'SKU2', packingUnit: 'pcs.' } as Product })); - const action1 = new AddProductToBasket({ sku: 'SKU1', quantity: 1 }); - const action2 = new AddProductToBasket({ sku: 'SKU2', quantity: 1 }); - const completion = new AddItemsToBasket({ + store$.dispatch(loadProductSuccess({ product: { sku: 'SKU1', packingUnit: 'pcs.' } as Product })); + store$.dispatch(loadProductSuccess({ product: { sku: 'SKU2', packingUnit: 'pcs.' } as Product })); + const action1 = addProductToBasket({ sku: 'SKU1', quantity: 1 }); + const action2 = addProductToBasket({ sku: 'SKU2', quantity: 1 }); + const completion = addItemsToBasket({ items: [ { sku: 'SKU2', quantity: 2, unit: 'pcs.' }, { sku: 'SKU1', quantity: 2, unit: 'pcs.' }, @@ -95,7 +95,7 @@ describe('Basket Items Effects', () => { it('should call the basketService for addItemsToBasket', done => { store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'BID', lineItems: [], @@ -104,7 +104,7 @@ describe('Basket Items Effects', () => { ); const items = [{ sku: 'SKU', quantity: 1, unit: 'pcs.' }]; - const action = new AddItemsToBasket({ items }); + const action = addItemsToBasket({ items }); actions$ = of(action); effects.addItemsToBasket$.subscribe(() => { @@ -115,7 +115,7 @@ describe('Basket Items Effects', () => { it('should call the basketService for addItemsToBasket with specific basketId when basketId set', done => { store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'BID', lineItems: [], @@ -125,7 +125,7 @@ describe('Basket Items Effects', () => { const items = [{ sku: 'SKU', quantity: 1, unit: 'pcs.' }]; const basketId = 'BID'; - const action = new AddItemsToBasket({ items, basketId }); + const action = addItemsToBasket({ items, basketId }); actions$ = of(action); effects.addItemsToBasket$.subscribe(() => { @@ -136,7 +136,7 @@ describe('Basket Items Effects', () => { it('should not call the basketService for addItemsToBasket if no basket in store', () => { const items = [{ sku: 'SKU', quantity: 1, unit: 'pcs.' }]; - const action = new AddItemsToBasket({ items }); + const action = addItemsToBasket({ items }); actions$ = of(action); effects.addItemsToBasket$.subscribe(fail, fail); @@ -148,7 +148,7 @@ describe('Basket Items Effects', () => { when(basketServiceMock.createBasket()).thenReturn(of({} as Basket)); const items = [{ sku: 'SKU', quantity: 1, unit: 'pcs.' }]; - const action = new AddItemsToBasket({ items }); + const action = addItemsToBasket({ items }); actions$ = of(action); effects.createBasketBeforeAddItemsToBasket$.subscribe(() => { @@ -159,7 +159,7 @@ describe('Basket Items Effects', () => { it('should map to action of type AddItemsToBasketSuccess', () => { store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'BID', lineItems: [], @@ -168,8 +168,8 @@ describe('Basket Items Effects', () => { ); const items = [{ sku: 'SKU', quantity: 1, unit: 'pcs.' }]; - const action = new AddItemsToBasket({ items }); - const completion = new AddItemsToBasketSuccess({ info: undefined }); + const action = addItemsToBasket({ items }); + const completion = addItemsToBasketSuccess({ info: undefined }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -180,7 +180,7 @@ describe('Basket Items Effects', () => { when(basketServiceMock.addItemsToBasket(anyString(), anything())).thenReturn(throwError({ message: 'invalid' })); store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'BID', lineItems: [], @@ -189,8 +189,8 @@ describe('Basket Items Effects', () => { ); const items = [{ sku: 'invalid', quantity: 1, unit: 'pcs.' }]; - const action = new AddItemsToBasket({ items }); - const completion = new AddItemsToBasketFail({ error: { message: 'invalid' } as HttpError }); + const action = addItemsToBasket({ items }); + const completion = addItemsToBasketFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -203,9 +203,9 @@ describe('Basket Items Effects', () => { when(basketServiceMock.getBasket(anything())).thenReturn(of()); const items = [{ sku: 'SKU', quantity: 1, unit: 'pcs.' }]; - const action = new AddItemsToBasket({ items }); + const action = addItemsToBasket({ items }); - const completion = new LoadProduct({ sku: 'SKU' }); + const completion = loadProduct({ sku: 'SKU' }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -215,8 +215,8 @@ describe('Basket Items Effects', () => { describe('loadBasketAfterAddItemsToBasket$', () => { it('should map to action of type LoadBasket if AddItemsToBasketSuccess action triggered', () => { - const action = new AddItemsToBasketSuccess({ info: undefined }); - const completion = new LoadBasket(); + const action = addItemsToBasketSuccess({ info: undefined }); + const completion = loadBasket(); actions$ = hot('-a', { a: action }); const expected$ = cold('-c', { c: completion }); @@ -229,7 +229,7 @@ describe('Basket Items Effects', () => { when(basketServiceMock.updateBasketItem(anyString(), anyString(), anything())).thenReturn(of([{} as BasketInfo])); store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'BID', lineItems: [ @@ -263,7 +263,7 @@ describe('Basket Items Effects', () => { }, ], }; - const action = new UpdateBasketItems(payload); + const action = updateBasketItems(payload); actions$ = of(action); effects.updateBasketItems$.subscribe(() => { @@ -322,7 +322,7 @@ describe('Basket Items Effects', () => { }, ], }; - const action = new UpdateBasketItems(payload); + const action = updateBasketItems(payload); actions$ = of(action); effects.updateBasketItems$.subscribe(() => { @@ -340,9 +340,9 @@ describe('Basket Items Effects', () => { }, ], }; - const action = new UpdateBasketItems(payload); + const action = updateBasketItems(payload); // tslint:disable-next-line: no-null-keyword - const completion = new UpdateBasketItemsSuccess({ info: null }); + const completion = updateBasketItemsSuccess({ info: null }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -362,8 +362,8 @@ describe('Basket Items Effects', () => { }, ], }; - const action = new UpdateBasketItems(payload); - const completion = new UpdateBasketItemsFail({ error: { message: 'invalid' } as HttpError }); + const action = updateBasketItems(payload); + const completion = updateBasketItemsFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -373,8 +373,8 @@ describe('Basket Items Effects', () => { describe('loadBasketAfterUpdateBasketItem$', () => { it('should map to action of type LoadBasket if UpdateBasketItemSuccess action triggered', () => { - const action = new UpdateBasketItemsSuccess({ info: undefined }); - const completion = new LoadBasket(); + const action = updateBasketItemsSuccess({ info: undefined }); + const completion = loadBasket(); actions$ = hot('-a', { a: action }); const expected$ = cold('-c', { c: completion }); @@ -384,8 +384,8 @@ describe('Basket Items Effects', () => { describe('validateBasketAfterUpdateFailure$', () => { it('should map to action of type ValidateBasket if UpdateBasketItemFail action triggered', () => { - const action = new UpdateBasketItemsFail({ error: { message: 'invalid' } as HttpError }); - const completion = new ValidateBasket({ scopes: ['Products'] }); + const action = updateBasketItemsFail({ error: { message: 'invalid' } as HttpError }); + const completion = validateBasket({ scopes: ['Products'] }); actions$ = hot('-a', { a: action }); const expected$ = cold('-c', { c: completion }); @@ -398,7 +398,7 @@ describe('Basket Items Effects', () => { when(basketServiceMock.deleteBasketItem(anyString(), anyString())).thenReturn(of(undefined)); store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'BID', lineItems: [], @@ -409,7 +409,7 @@ describe('Basket Items Effects', () => { it('should call the basketService for DeleteBasketItem action', done => { const itemId = 'BIID'; - const action = new DeleteBasketItem({ itemId }); + const action = deleteBasketItem({ itemId }); actions$ = of(action); effects.deleteBasketItem$.subscribe(() => { @@ -420,8 +420,8 @@ describe('Basket Items Effects', () => { it('should map to action of type DeleteBasketItemSuccess', () => { const itemId = 'BIID'; - const action = new DeleteBasketItem({ itemId }); - const completion = new DeleteBasketItemSuccess({ info: undefined }); + const action = deleteBasketItem({ itemId }); + const completion = deleteBasketItemSuccess({ info: undefined }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -432,8 +432,8 @@ describe('Basket Items Effects', () => { when(basketServiceMock.deleteBasketItem(anyString(), anyString())).thenReturn(throwError({ message: 'invalid' })); const itemId = 'BIID'; - const action = new DeleteBasketItem({ itemId }); - const completion = new DeleteBasketItemFail({ error: { message: 'invalid' } as HttpError }); + const action = deleteBasketItem({ itemId }); + const completion = deleteBasketItemFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -443,8 +443,8 @@ describe('Basket Items Effects', () => { describe('loadBasketAfterDeleteBasketItem$', () => { it('should map to action of type LoadBasket if DeleteBasketItemSuccess action triggered', () => { - const action = new DeleteBasketItemSuccess({ info: undefined }); - const completion = new LoadBasket(); + const action = deleteBasketItemSuccess({ info: undefined }); + const completion = loadBasket(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); diff --git a/src/app/core/store/customer/basket/basket-items.effects.ts b/src/app/core/store/customer/basket/basket-items.effects.ts index ee2aea20e0..0e8ebe6f7e 100644 --- a/src/app/core/store/customer/basket/basket-items.effects.ts +++ b/src/app/core/store/customer/basket/basket-items.effects.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { concat } from 'rxjs'; import { @@ -23,23 +23,22 @@ import { LineItemUpdateHelperItem, } from 'ish-core/models/line-item-update/line-item-update.helper'; import { BasketService } from 'ish-core/services/basket/basket.service'; -import { LoadProduct, getProductEntities } from 'ish-core/store/shopping/products'; +import { getProductEntities, loadProduct } from 'ish-core/store/shopping/products'; import { mapErrorToAction, mapToPayload, mapToPayloadProperty, mapToProperty } from 'ish-core/utils/operators'; import { - AddItemsToBasket, - AddItemsToBasketFail, - AddItemsToBasketSuccess, - AddProductToBasket, - BasketActionTypes, - DeleteBasketItem, - DeleteBasketItemFail, - DeleteBasketItemSuccess, - LoadBasket, - UpdateBasketItems, - UpdateBasketItemsFail, - UpdateBasketItemsSuccess, - ValidateBasket, + addItemsToBasket, + addItemsToBasketFail, + addItemsToBasketSuccess, + addProductToBasket, + deleteBasketItem, + deleteBasketItemFail, + deleteBasketItemSuccess, + loadBasket, + updateBasketItems, + updateBasketItemsFail, + updateBasketItemsSuccess, + validateBasket, } from './basket.actions'; import { getCurrentBasket, getCurrentBasketId } from './basket.selectors'; @@ -56,26 +55,27 @@ export class BasketItemsEffects { * Add a product to the current basket. * Triggers the internal AddItemsToBasket action that handles the actual adding of the product to the basket. */ - @Effect() - addProductToBasket$ = this.actions$.pipe( - ofType(BasketActionTypes.AddProductToBasket), - mapToPayload(), - // accumulate all actions - window(this.actions$.pipe(ofType(BasketActionTypes.AddProductToBasket), debounceTime(1000))), - mergeMap(window$ => - window$.pipe( - withLatestFrom(this.store.pipe(select(getProductEntities))), - // accumulate changes - reduce((acc, [val, entities]) => { - const element = acc.find(x => x.sku === val.sku); - if (element) { - element.quantity += val.quantity; - } else { - acc.push({ ...val, unit: entities[val.sku] && entities[val.sku].packingUnit }); - } - return acc; - }, []), - map(items => new AddItemsToBasket({ items })) + addProductToBasket$ = createEffect(() => + this.actions$.pipe( + ofType(addProductToBasket), + mapToPayload(), + // accumulate all actions + window(this.actions$.pipe(ofType(addProductToBasket), debounceTime(1000))), + mergeMap(window$ => + window$.pipe( + withLatestFrom(this.store.pipe(select(getProductEntities))), + // accumulate changes + reduce((acc, [val, entities]) => { + const element = acc.find(x => x.sku === val.sku); + if (element) { + element.quantity += val.quantity; + } else { + acc.push({ ...val, unit: entities[val.sku] && entities[val.sku].packingUnit }); + } + return acc; + }, []), + map(items => addItemsToBasket({ items })) + ) ) ) ); @@ -84,47 +84,50 @@ export class BasketItemsEffects { * Add items to the current basket. * Only triggers if basket is set or action payload contains basketId. */ - @Effect() - addItemsToBasket$ = this.actions$.pipe( - ofType(BasketActionTypes.AddItemsToBasket), - mapToPayload(), - withLatestFrom(this.store.pipe(select(getCurrentBasketId))), - filter(([{ basketId }, currentBasketId]) => !!currentBasketId || !!basketId), - concatMap(([payload, currentBasketId]) => { - // get basket id from AddItemsToBasket action if set, otherwise use current basket id - const basketId = payload.basketId || currentBasketId; + addItemsToBasket$ = createEffect(() => + this.actions$.pipe( + ofType(addItemsToBasket), + mapToPayload(), + withLatestFrom(this.store.pipe(select(getCurrentBasketId))), + filter(([{ basketId }, currentBasketId]) => !!currentBasketId || !!basketId), + concatMap(([payload, currentBasketId]) => { + // get basket id from AddItemsToBasket action if set, otherwise use current basket id + const basketId = payload.basketId || currentBasketId; - return this.basketService.addItemsToBasket(basketId, payload.items).pipe( - map(info => new AddItemsToBasketSuccess({ info })), - mapErrorToAction(AddItemsToBasketFail) - ); - }) + return this.basketService.addItemsToBasket(basketId, payload.items).pipe( + map(info => addItemsToBasketSuccess({ info })), + mapErrorToAction(addItemsToBasketFail) + ); + }) + ) ); /** * Reload products when they are added to basket to update price and inStock information */ - @Effect() - loadProductsForAddItemsToBasket$ = this.actions$.pipe( - ofType(BasketActionTypes.AddItemsToBasket), - mapToPayload(), - concatMap(payload => [...payload.items.map(item => new LoadProduct({ sku: item.sku }))]) + loadProductsForAddItemsToBasket$ = createEffect(() => + this.actions$.pipe( + ofType(addItemsToBasket), + mapToPayload(), + concatMap(payload => [...payload.items.map(item => loadProduct({ sku: item.sku }))]) + ) ); /** * Creates a basket if missing and call AddItemsToBasketAction * Only triggers if basket is unset set and action payload does not contain basketId. */ - @Effect() - createBasketBeforeAddItemsToBasket$ = this.actions$.pipe( - ofType(BasketActionTypes.AddItemsToBasket), - mapToPayload(), - withLatestFrom(this.store.pipe(select(getCurrentBasketId))), - filter(([payload, basketId]) => !basketId && !payload.basketId), - mergeMap(([{ items }]) => - this.basketService.createBasket().pipe( - mapToProperty('id'), - map(basketId => new AddItemsToBasket({ items, basketId })) + createBasketBeforeAddItemsToBasket$ = createEffect(() => + this.actions$.pipe( + ofType(addItemsToBasket), + mapToPayload(), + withLatestFrom(this.store.pipe(select(getCurrentBasketId))), + filter(([payload, basketId]) => !basketId && !payload.basketId), + mergeMap(([{ items }]) => + this.basketService.createBasket().pipe( + mapToProperty('id'), + map(basketId => addItemsToBasket({ items, basketId })) + ) ) ) ); @@ -134,34 +137,35 @@ export class BasketItemsEffects { * Triggers update item request if item quantity has changed and is greater zero * Triggers delete item request if item quantity set to zero */ - @Effect() - updateBasketItems$ = this.actions$.pipe( - ofType(BasketActionTypes.UpdateBasketItems), - mapToPayload(), - withLatestFrom(this.store.pipe(select(getCurrentBasket))), - filter(([payload, basket]) => !!basket.lineItems && !!payload.lineItemUpdates), - map(([{ lineItemUpdates }, { lineItems }]) => - LineItemUpdateHelper.filterUpdatesByItems(lineItemUpdates, lineItems as LineItemUpdateHelperItem[]) - ), + updateBasketItems$ = createEffect(() => + this.actions$.pipe( + ofType(updateBasketItems), + mapToPayload(), + withLatestFrom(this.store.pipe(select(getCurrentBasket))), + filter(([payload, basket]) => !!basket.lineItems && !!payload.lineItemUpdates), + map(([{ lineItemUpdates }, { lineItems }]) => + LineItemUpdateHelper.filterUpdatesByItems(lineItemUpdates, lineItems as LineItemUpdateHelperItem[]) + ), - withLatestFrom(this.store.pipe(select(getCurrentBasketId))), - concatMap(([updates, basketId]) => - concat( - ...updates.map(update => { - if (update.quantity === 0) { - return this.basketService.deleteBasketItem(basketId, update.itemId); - } else { - return this.basketService.updateBasketItem(basketId, update.itemId, { - quantity: update.quantity > 0 ? { value: update.quantity, unit: update.unit } : undefined, - product: update.sku, - }); - } - }) - ).pipe( - defaultIfEmpty(), - last(), - map(info => new UpdateBasketItemsSuccess({ info })), - mapErrorToAction(UpdateBasketItemsFail) + withLatestFrom(this.store.pipe(select(getCurrentBasketId))), + concatMap(([updates, basketId]) => + concat( + ...updates.map(update => { + if (update.quantity === 0) { + return this.basketService.deleteBasketItem(basketId, update.itemId); + } else { + return this.basketService.updateBasketItem(basketId, update.itemId, { + quantity: update.quantity > 0 ? { value: update.quantity, unit: update.unit } : undefined, + product: update.sku, + }); + } + }) + ).pipe( + defaultIfEmpty(), + last(), + map(info => updateBasketItemsSuccess({ info })), + mapErrorToAction(updateBasketItemsFail) + ) ) ) ); @@ -169,26 +173,28 @@ export class BasketItemsEffects { /** * Validates the basket after an update item error occurred */ - @Effect() - validateBasketAfterUpdateFailure$ = this.actions$.pipe( - ofType(BasketActionTypes.UpdateBasketItemsFail), - mapToPayload(), - withLatestFrom(this.store.pipe(select(getCurrentBasket))), - mapTo(new ValidateBasket({ scopes: ['Products'] })) + validateBasketAfterUpdateFailure$ = createEffect(() => + this.actions$.pipe( + ofType(updateBasketItemsFail), + mapToPayload(), + withLatestFrom(this.store.pipe(select(getCurrentBasket))), + mapTo(validateBasket({ scopes: ['Products'] })) + ) ); /** * Delete basket item effect. */ - @Effect() - deleteBasketItem$ = this.actions$.pipe( - ofType(BasketActionTypes.DeleteBasketItem), - mapToPayloadProperty('itemId'), - withLatestFrom(this.store.pipe(select(getCurrentBasketId))), - concatMap(([itemId, basketId]) => - this.basketService.deleteBasketItem(basketId, itemId).pipe( - map(info => new DeleteBasketItemSuccess({ info })), - mapErrorToAction(DeleteBasketItemFail) + deleteBasketItem$ = createEffect(() => + this.actions$.pipe( + ofType(deleteBasketItem), + mapToPayloadProperty('itemId'), + withLatestFrom(this.store.pipe(select(getCurrentBasketId))), + concatMap(([itemId, basketId]) => + this.basketService.deleteBasketItem(basketId, itemId).pipe( + map(info => deleteBasketItemSuccess({ info })), + mapErrorToAction(deleteBasketItemFail) + ) ) ) ); @@ -196,19 +202,16 @@ export class BasketItemsEffects { /** * Triggers a LoadBasket action after successful interaction with the Basket API. */ - @Effect() - loadBasketAfterBasketItemsChangeSuccess$ = this.actions$.pipe( - ofType( - BasketActionTypes.AddItemsToBasketSuccess, - BasketActionTypes.UpdateBasketItemsSuccess, - BasketActionTypes.DeleteBasketItemSuccess - ), - mapToPayloadProperty('info'), - tap(info => - info && info.length && info[0].message - ? this.router.navigate(['/basket'], { queryParams: { error: true } }) - : undefined - ), - mapTo(new LoadBasket()) + loadBasketAfterBasketItemsChangeSuccess$ = createEffect(() => + this.actions$.pipe( + ofType(addItemsToBasketSuccess, updateBasketItemsSuccess, deleteBasketItemSuccess), + mapToPayloadProperty('info'), + tap(info => + info && info.length && info[0].message + ? this.router.navigate(['/basket'], { queryParams: { error: true } }) + : undefined + ), + mapTo(loadBasket()) + ) ); } diff --git a/src/app/core/store/customer/basket/basket-payment.effects.spec.ts b/src/app/core/store/customer/basket/basket-payment.effects.spec.ts index b18377e9d7..afc2d6c5a4 100644 --- a/src/app/core/store/customer/basket/basket-payment.effects.spec.ts +++ b/src/app/core/store/customer/basket/basket-payment.effects.spec.ts @@ -17,29 +17,29 @@ import { Payment } from 'ish-core/models/payment/payment.model'; import { PaymentService } from 'ish-core/services/payment/payment.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; -import { LoginUserSuccess } from 'ish-core/store/customer/user'; +import { loginUserSuccess } from 'ish-core/store/customer/user'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { BasketMockData } from 'ish-core/utils/dev/basket-mock-data'; import { BasketPaymentEffects } from './basket-payment.effects'; import { - CreateBasketPayment, - CreateBasketPaymentFail, - CreateBasketPaymentSuccess, - DeleteBasketPayment, - DeleteBasketPaymentFail, - DeleteBasketPaymentSuccess, - LoadBasket, - LoadBasketEligiblePaymentMethods, - LoadBasketEligiblePaymentMethodsFail, - LoadBasketEligiblePaymentMethodsSuccess, - LoadBasketSuccess, - SetBasketPayment, - SetBasketPaymentFail, - SetBasketPaymentSuccess, - UpdateBasketPayment, - UpdateBasketPaymentFail, - UpdateBasketPaymentSuccess, + createBasketPayment, + createBasketPaymentFail, + createBasketPaymentSuccess, + deleteBasketPayment, + deleteBasketPaymentFail, + deleteBasketPaymentSuccess, + loadBasket, + loadBasketEligiblePaymentMethods, + loadBasketEligiblePaymentMethodsFail, + loadBasketEligiblePaymentMethodsSuccess, + loadBasketSuccess, + setBasketPayment, + setBasketPaymentFail, + setBasketPaymentSuccess, + updateBasketPayment, + updateBasketPaymentFail, + updateBasketPaymentSuccess, } from './basket.actions'; describe('Basket Payment Effects', () => { @@ -82,7 +82,7 @@ describe('Basket Payment Effects', () => { ); store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'BID', lineItems: [], @@ -92,7 +92,7 @@ describe('Basket Payment Effects', () => { }); it('should call the paymentService for loadBasketEligiblePaymentMethods', done => { - const action = new LoadBasketEligiblePaymentMethods(); + const action = loadBasketEligiblePaymentMethods(); actions$ = of(action); effects.loadBasketEligiblePaymentMethods$.subscribe(() => { @@ -102,8 +102,8 @@ describe('Basket Payment Effects', () => { }); it('should map to action of type loadBasketEligiblePaymentMethodsSuccess', () => { - const action = new LoadBasketEligiblePaymentMethods(); - const completion = new LoadBasketEligiblePaymentMethodsSuccess({ + const action = loadBasketEligiblePaymentMethods(); + const completion = loadBasketEligiblePaymentMethodsSuccess({ paymentMethods: [BasketMockData.getPaymentMethod()], }); actions$ = hot('-a-a-a', { a: action }); @@ -116,8 +116,8 @@ describe('Basket Payment Effects', () => { when(paymentServiceMock.getBasketEligiblePaymentMethods(anyString())).thenReturn( throwError({ message: 'invalid' }) ); - const action = new LoadBasketEligiblePaymentMethods(); - const completion = new LoadBasketEligiblePaymentMethodsFail({ + const action = loadBasketEligiblePaymentMethods(); + const completion = loadBasketEligiblePaymentMethodsFail({ error: { message: 'invalid', } as HttpError, @@ -134,7 +134,7 @@ describe('Basket Payment Effects', () => { when(paymentServiceMock.setBasketPayment(anyString(), anyString())).thenReturn(of(undefined)); store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'BID', lineItems: [], @@ -146,7 +146,7 @@ describe('Basket Payment Effects', () => { it('should call the paymentService for setPaymentAtBasket', done => { const id = 'newPayment'; - const action = new SetBasketPayment({ id }); + const action = setBasketPayment({ id }); actions$ = of(action); effects.setPaymentAtBasket$.subscribe(() => { @@ -157,8 +157,8 @@ describe('Basket Payment Effects', () => { it('should map to action of type SetBasketPaymentSuccess', () => { const id = 'newPayment'; - const action = new SetBasketPayment({ id }); - const completion = new SetBasketPaymentSuccess(); + const action = setBasketPayment({ id }); + const completion = setBasketPaymentSuccess(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -169,8 +169,8 @@ describe('Basket Payment Effects', () => { when(paymentServiceMock.setBasketPayment(anyString(), anyString())).thenReturn( throwError({ message: 'invalid' }) ); - const action = new SetBasketPayment({ id: 'newPayment' }); - const completion = new SetBasketPaymentFail({ error: { message: 'invalid' } as HttpError }); + const action = setBasketPayment({ id: 'newPayment' }); + const completion = setBasketPaymentFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -182,12 +182,12 @@ describe('Basket Payment Effects', () => { beforeEach(() => { when(paymentServiceMock.setBasketPayment(anyString(), anyString())).thenReturn(of(undefined)); - store$.dispatch(new LoadBasketSuccess({ basket: BasketMockData.getBasket() })); + store$.dispatch(loadBasketSuccess({ basket: BasketMockData.getBasket() })); }); it('should call the paymentService for setPaymentAtBasket', done => { const id = 'newPayment'; - const action = new SetBasketPayment({ id }); + const action = setBasketPayment({ id }); actions$ = of(action); effects.setPaymentAtBasket$.subscribe(() => { @@ -198,8 +198,8 @@ describe('Basket Payment Effects', () => { it('should map to action of type SetBasketPaymentSuccess', () => { const id = 'newPayment'; - const action = new SetBasketPayment({ id }); - const completion = new SetBasketPaymentSuccess(); + const action = setBasketPayment({ id }); + const completion = setBasketPaymentSuccess(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -210,8 +210,8 @@ describe('Basket Payment Effects', () => { when(paymentServiceMock.setBasketPayment(anyString(), anyString())).thenReturn( throwError({ message: 'invalid' }) ); - const action = new SetBasketPayment({ id: 'newPayment' }); - const completion = new SetBasketPaymentFail({ error: { message: 'invalid' } as HttpError }); + const action = setBasketPayment({ id: 'newPayment' }); + const completion = setBasketPaymentFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -245,7 +245,7 @@ describe('Basket Payment Effects', () => { ); store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'BID', lineItems: [], @@ -253,11 +253,11 @@ describe('Basket Payment Effects', () => { } as Basket, }) ); - store$.dispatch(new LoginUserSuccess({ customer })); + store$.dispatch(loginUserSuccess({ customer })); }); it('should call the paymentService if payment instrument is saved at basket', done => { - const action = new CreateBasketPayment({ paymentInstrument, saveForLater: false }); + const action = createBasketPayment({ paymentInstrument, saveForLater: false }); actions$ = of(action); effects.createBasketPaymentInstrument$.subscribe(() => { @@ -267,7 +267,7 @@ describe('Basket Payment Effects', () => { }); it('should call the paymentService if payment instrument is saved at user', done => { - const action = new CreateBasketPayment({ paymentInstrument, saveForLater: true }); + const action = createBasketPayment({ paymentInstrument, saveForLater: true }); actions$ = of(action); effects.createBasketPaymentInstrument$.subscribe(() => { @@ -276,9 +276,9 @@ describe('Basket Payment Effects', () => { }); }); it('should map to action of type SetBasketPayment and CreateBasketPaymentSuccess', () => { - const action = new CreateBasketPayment({ paymentInstrument, saveForLater: false }); - const completion1 = new SetBasketPayment({ id: 'newPaymentInstrumentId' }); - const completion2 = new CreateBasketPaymentSuccess(); + const action = createBasketPayment({ paymentInstrument, saveForLater: false }); + const completion1 = setBasketPayment({ id: 'newPaymentInstrumentId' }); + const completion2 = createBasketPaymentSuccess(); actions$ = hot('-a', { a: action }); const expected$ = cold('-(cd)', { c: completion1, d: completion2 }); @@ -289,8 +289,8 @@ describe('Basket Payment Effects', () => { when(paymentServiceMock.createBasketPayment(anyString(), anything())).thenReturn( throwError({ message: 'invalid' }) ); - const action = new CreateBasketPayment({ paymentInstrument, saveForLater: false }); - const completion = new CreateBasketPaymentFail({ error: { message: 'invalid' } as HttpError }); + const action = createBasketPayment({ paymentInstrument, saveForLater: false }); + const completion = createBasketPaymentFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -298,8 +298,8 @@ describe('Basket Payment Effects', () => { }); it('should map to action of type LoadEligibleBasketMethod in case of success', () => { - const action = new CreateBasketPaymentSuccess(); - const completion = new LoadBasketEligiblePaymentMethods(); + const action = createBasketPaymentSuccess(); + const completion = loadBasketEligiblePaymentMethods(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -310,7 +310,7 @@ describe('Basket Payment Effects', () => { describe('sendPaymentRedirectData$', () => { beforeEach(() => { store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'BID', lineItems: [], @@ -365,7 +365,7 @@ describe('Basket Payment Effects', () => { when(paymentServiceMock.updateBasketPayment(anyString(), anything())).thenReturn(of(payment)); store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'BID', lineItems: [], @@ -376,7 +376,7 @@ describe('Basket Payment Effects', () => { }); it('should call the paymentService for updateBasketPayment', done => { - const action = new UpdateBasketPayment({ params }); + const action = updateBasketPayment({ params }); actions$ = of(action); effects.updateBasketPayment$.subscribe(() => { @@ -386,8 +386,8 @@ describe('Basket Payment Effects', () => { }); it('should map to action of type UpdateBasketPaymentSuccess', () => { - const action = new UpdateBasketPayment({ params }); - const completion = new UpdateBasketPaymentSuccess(); + const action = updateBasketPayment({ params }); + const completion = updateBasketPaymentSuccess(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -399,8 +399,8 @@ describe('Basket Payment Effects', () => { when(paymentServiceMock.updateBasketPayment(anyString(), anything())).thenReturn( throwError({ message: 'invalid' }) ); - const action = new UpdateBasketPayment({ params }); - const completion = new UpdateBasketPaymentFail({ error: { message: 'invalid' } as HttpError }); + const action = updateBasketPayment({ params }); + const completion = updateBasketPaymentFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -408,8 +408,8 @@ describe('Basket Payment Effects', () => { }); it('should map to action of type LoadBasket in case of success', () => { - const action = new UpdateBasketPaymentSuccess(); - const completion = new LoadBasket(); + const action = updateBasketPaymentSuccess(); + const completion = loadBasket(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -442,14 +442,14 @@ describe('Basket Payment Effects', () => { when(paymentServiceMock.deleteBasketPaymentInstrument(anything(), anything())).thenReturn(of(undefined)); store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket, }) ); }); it('should call the paymentService for deleteBasketPayment', done => { - const action = new DeleteBasketPayment({ paymentInstrument }); + const action = deleteBasketPayment({ paymentInstrument }); actions$ = of(action); effects.deleteBasketPaymentInstrument$.subscribe(() => { @@ -459,8 +459,8 @@ describe('Basket Payment Effects', () => { }); it('should map to action of type DeleteBasketPaymentSuccess', () => { - const action = new DeleteBasketPayment({ paymentInstrument }); - const completion = new DeleteBasketPaymentSuccess(); + const action = deleteBasketPayment({ paymentInstrument }); + const completion = deleteBasketPaymentSuccess(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -471,8 +471,8 @@ describe('Basket Payment Effects', () => { when(paymentServiceMock.deleteBasketPaymentInstrument(anything(), anything())).thenReturn( throwError({ message: 'invalid' }) ); - const action = new DeleteBasketPayment({ paymentInstrument }); - const completion = new DeleteBasketPaymentFail({ error: { message: 'invalid' } as HttpError }); + const action = deleteBasketPayment({ paymentInstrument }); + const completion = deleteBasketPaymentFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -480,8 +480,8 @@ describe('Basket Payment Effects', () => { }); it('should map to action of type GetEligiblePaymentMethods in case of success', () => { - const action = new DeleteBasketPaymentSuccess(); - const completion = new LoadBasketEligiblePaymentMethods(); + const action = deleteBasketPaymentSuccess(); + const completion = loadBasketEligiblePaymentMethods(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -489,8 +489,8 @@ describe('Basket Payment Effects', () => { }); it('should map to action of type LoadBasket in case of success', () => { - const action = new DeleteBasketPaymentSuccess(); - const completion = new LoadBasket(); + const action = deleteBasketPaymentSuccess(); + const completion = loadBasket(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); diff --git a/src/app/core/store/customer/basket/basket-payment.effects.ts b/src/app/core/store/customer/basket/basket-payment.effects.ts index d380198f4c..454be1f727 100644 --- a/src/app/core/store/customer/basket/basket-payment.effects.ts +++ b/src/app/core/store/customer/basket/basket-payment.effects.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { concatMap, filter, map, mapTo, switchMap, take, withLatestFrom } from 'rxjs/operators'; @@ -9,23 +9,22 @@ import { getLoggedInCustomer } from 'ish-core/store/customer/user'; import { mapErrorToAction, mapToPayload, mapToPayloadProperty, whenTruthy } from 'ish-core/utils/operators'; import { - BasketActionTypes, - CreateBasketPayment, - CreateBasketPaymentFail, - CreateBasketPaymentSuccess, - DeleteBasketPayment, - DeleteBasketPaymentFail, - DeleteBasketPaymentSuccess, - LoadBasket, - LoadBasketEligiblePaymentMethods, - LoadBasketEligiblePaymentMethodsFail, - LoadBasketEligiblePaymentMethodsSuccess, - SetBasketPayment, - SetBasketPaymentFail, - SetBasketPaymentSuccess, - UpdateBasketPayment, - UpdateBasketPaymentFail, - UpdateBasketPaymentSuccess, + createBasketPayment, + createBasketPaymentFail, + createBasketPaymentSuccess, + deleteBasketPayment, + deleteBasketPaymentFail, + deleteBasketPaymentSuccess, + loadBasket, + loadBasketEligiblePaymentMethods, + loadBasketEligiblePaymentMethodsFail, + loadBasketEligiblePaymentMethodsSuccess, + setBasketPayment, + setBasketPaymentFail, + setBasketPaymentSuccess, + updateBasketPayment, + updateBasketPaymentFail, + updateBasketPaymentSuccess, } from './basket.actions'; import { getCurrentBasket, getCurrentBasketId } from './basket.selectors'; @@ -36,14 +35,15 @@ export class BasketPaymentEffects { /** * The load basket eligible payment methods effect. */ - @Effect() - loadBasketEligiblePaymentMethods$ = this.actions$.pipe( - ofType(BasketActionTypes.LoadBasketEligiblePaymentMethods), - withLatestFrom(this.store.pipe(select(getCurrentBasketId))), - concatMap(([, basketid]) => - this.paymentService.getBasketEligiblePaymentMethods(basketid).pipe( - map(result => new LoadBasketEligiblePaymentMethodsSuccess({ paymentMethods: result })), - mapErrorToAction(LoadBasketEligiblePaymentMethodsFail) + loadBasketEligiblePaymentMethods$ = createEffect(() => + this.actions$.pipe( + ofType(loadBasketEligiblePaymentMethods), + withLatestFrom(this.store.pipe(select(getCurrentBasketId))), + concatMap(([, basketid]) => + this.paymentService.getBasketEligiblePaymentMethods(basketid).pipe( + map(result => loadBasketEligiblePaymentMethodsSuccess({ paymentMethods: result })), + mapErrorToAction(loadBasketEligiblePaymentMethodsFail) + ) ) ) ); @@ -51,60 +51,63 @@ export class BasketPaymentEffects { /** * Sets a payment at the current basket. */ - @Effect() - setPaymentAtBasket$ = this.actions$.pipe( - ofType(BasketActionTypes.SetBasketPayment), - mapToPayloadProperty('id'), - withLatestFrom(this.store.pipe(select(getCurrentBasketId))), - concatMap(([paymentInstrumentId, basketid]) => - this.paymentService - .setBasketPayment(basketid, paymentInstrumentId) - .pipe(mapTo(new SetBasketPaymentSuccess()), mapErrorToAction(SetBasketPaymentFail)) + setPaymentAtBasket$ = createEffect(() => + this.actions$.pipe( + ofType(setBasketPayment), + mapToPayloadProperty('id'), + withLatestFrom(this.store.pipe(select(getCurrentBasketId))), + concatMap(([paymentInstrumentId, basketid]) => + this.paymentService + .setBasketPayment(basketid, paymentInstrumentId) + .pipe(mapTo(setBasketPaymentSuccess()), mapErrorToAction(setBasketPaymentFail)) + ) ) ); /** * Creates a payment instrument at the current basket or user respectively - and saves it as payment at basket. */ - @Effect() - createBasketPaymentInstrument$ = this.actions$.pipe( - ofType(BasketActionTypes.CreateBasketPayment), - mapToPayload(), - withLatestFrom(this.store.pipe(select(getLoggedInCustomer))), - map(([payload, customer]) => ({ - saveForLater: payload.saveForLater, - paymentInstrument: payload.paymentInstrument, - customerNo: customer && customer.customerNo, - })), - withLatestFrom(this.store.pipe(select(getCurrentBasketId))), - concatMap(([payload, basketid]) => { - const createPayment$ = - payload.customerNo && payload.saveForLater - ? this.paymentService.createUserPayment(payload.customerNo, payload.paymentInstrument) - : this.paymentService.createBasketPayment(basketid, payload.paymentInstrument); + createBasketPaymentInstrument$ = createEffect(() => + this.actions$.pipe( + ofType(createBasketPayment), + mapToPayload(), + withLatestFrom(this.store.pipe(select(getLoggedInCustomer))), + map(([payload, customer]) => ({ + saveForLater: payload.saveForLater, + paymentInstrument: payload.paymentInstrument, + customerNo: customer && customer.customerNo, + })), + withLatestFrom(this.store.pipe(select(getCurrentBasketId))), + concatMap(([payload, basketid]) => { + const createPayment$ = + payload.customerNo && payload.saveForLater + ? this.paymentService.createUserPayment(payload.customerNo, payload.paymentInstrument) + : this.paymentService.createBasketPayment(basketid, payload.paymentInstrument); - return createPayment$.pipe( - concatMap(pi => [new SetBasketPayment({ id: pi.id }), new CreateBasketPaymentSuccess()]), - mapErrorToAction(CreateBasketPaymentFail) - ); - }) + return createPayment$.pipe( + concatMap(pi => [setBasketPayment({ id: pi.id }), createBasketPaymentSuccess()]), + mapErrorToAction(createBasketPaymentFail) + ); + }) + ) ); /** * Checks, if the page is called with redirect query params and sends them to the server ( only RedirectBeforeCheckout) */ - @Effect() - sendPaymentRedirectData$ = this.store.pipe( - ofUrl(/\/checkout\/(payment|review).*/), - select(selectQueryParams), - // don't do anything in case of RedirectAfterCheckout - filter(({ redirect, orderId }) => redirect && !orderId), - switchMap(queryParams => - this.store.pipe( - select(getCurrentBasketId), - whenTruthy(), - take(1), - mapTo(new UpdateBasketPayment({ params: queryParams })) + sendPaymentRedirectData$ = createEffect(() => + this.store.pipe( + ofUrl(/\/checkout\/(payment|review).*/), + select(selectQueryParams), + // don't do anything in case of RedirectAfterCheckout + filter(({ redirect, orderId }) => redirect && !orderId), + switchMap(queryParams => + this.store.pipe( + select(getCurrentBasketId), + whenTruthy(), + take(1), + mapTo(updateBasketPayment({ params: queryParams })) + ) ) ) ); @@ -112,53 +115,52 @@ export class BasketPaymentEffects { /** * Updates a basket payment concerning redirect data. */ - @Effect() - updateBasketPayment$ = this.actions$.pipe( - ofType(BasketActionTypes.UpdateBasketPayment), - mapToPayloadProperty('params'), - withLatestFrom(this.store.pipe(select(getCurrentBasketId))), - concatMap(([params, basketid]) => - this.paymentService - .updateBasketPayment(basketid, params) - .pipe(mapTo(new UpdateBasketPaymentSuccess()), mapErrorToAction(UpdateBasketPaymentFail)) + updateBasketPayment$ = createEffect(() => + this.actions$.pipe( + ofType(updateBasketPayment), + mapToPayloadProperty('params'), + withLatestFrom(this.store.pipe(select(getCurrentBasketId))), + concatMap(([params, basketid]) => + this.paymentService + .updateBasketPayment(basketid, params) + .pipe(mapTo(updateBasketPaymentSuccess()), mapErrorToAction(updateBasketPaymentFail)) + ) ) ); /** * Deletes a payment instrument and the related payment at the current basket. */ - @Effect() - deleteBasketPaymentInstrument$ = this.actions$.pipe( - ofType(BasketActionTypes.DeleteBasketPayment), - mapToPayloadProperty('paymentInstrument'), - withLatestFrom(this.store.pipe(select(getCurrentBasket))), - concatMap(([paymentInstrument, basket]) => - this.paymentService - .deleteBasketPaymentInstrument(basket, paymentInstrument) - .pipe(mapTo(new DeleteBasketPaymentSuccess()), mapErrorToAction(DeleteBasketPaymentFail)) + deleteBasketPaymentInstrument$ = createEffect(() => + this.actions$.pipe( + ofType(deleteBasketPayment), + mapToPayloadProperty('paymentInstrument'), + withLatestFrom(this.store.pipe(select(getCurrentBasket))), + concatMap(([paymentInstrument, basket]) => + this.paymentService + .deleteBasketPaymentInstrument(basket, paymentInstrument) + .pipe(mapTo(deleteBasketPaymentSuccess()), mapErrorToAction(deleteBasketPaymentFail)) + ) ) ); /** * Triggers a LoadBasket action after successful interaction with the Basket API. */ - @Effect() - loadBasketAfterBasketChangeSuccess$ = this.actions$.pipe( - ofType( - BasketActionTypes.SetBasketPaymentSuccess, - BasketActionTypes.SetBasketPaymentFail, - BasketActionTypes.UpdateBasketPaymentSuccess, - BasketActionTypes.DeleteBasketPaymentSuccess - ), - mapTo(new LoadBasket()) + loadBasketAfterBasketChangeSuccess$ = createEffect(() => + this.actions$.pipe( + ofType(setBasketPaymentSuccess, setBasketPaymentFail, updateBasketPaymentSuccess, deleteBasketPaymentSuccess), + mapTo(loadBasket()) + ) ); /** * Triggers a LoadEligiblePaymentMethods action after successful delete a eligible Payment Instrument. */ - @Effect() - loadBasketEligiblePaymentMethodsAfterChange$ = this.actions$.pipe( - ofType(BasketActionTypes.DeleteBasketPaymentSuccess, BasketActionTypes.CreateBasketPaymentSuccess), - mapTo(new LoadBasketEligiblePaymentMethods()) + loadBasketEligiblePaymentMethodsAfterChange$ = createEffect(() => + this.actions$.pipe( + ofType(deleteBasketPaymentSuccess, createBasketPaymentSuccess), + mapTo(loadBasketEligiblePaymentMethods()) + ) ); } diff --git a/src/app/core/store/customer/basket/basket-promotion-code.effects.spec.ts b/src/app/core/store/customer/basket/basket-promotion-code.effects.spec.ts index b94221bf8e..1fc8d6a496 100644 --- a/src/app/core/store/customer/basket/basket-promotion-code.effects.spec.ts +++ b/src/app/core/store/customer/basket/basket-promotion-code.effects.spec.ts @@ -14,14 +14,14 @@ import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.modu import { BasketPromotionCodeEffects } from './basket-promotion-code.effects'; import { - AddPromotionCodeToBasket, - AddPromotionCodeToBasketFail, - AddPromotionCodeToBasketSuccess, - LoadBasket, - LoadBasketSuccess, - RemovePromotionCodeFromBasket, - RemovePromotionCodeFromBasketFail, - RemovePromotionCodeFromBasketSuccess, + addPromotionCodeToBasket, + addPromotionCodeToBasketFail, + addPromotionCodeToBasketSuccess, + loadBasket, + loadBasketSuccess, + removePromotionCodeFromBasket, + removePromotionCodeFromBasketFail, + removePromotionCodeFromBasketSuccess, } from './basket.actions'; describe('Basket Promotion Code Effects', () => { @@ -51,8 +51,8 @@ describe('Basket Promotion Code Effects', () => { describe('loadBasketAfterAddPromotionCodeToBasket$', () => { it('should map to action of type LoadBasket if AddPromotionCodeToBasketSuccess action triggered', () => { - const action = new AddPromotionCodeToBasketSuccess(); - const completion = new LoadBasket(); + const action = addPromotionCodeToBasketSuccess(); + const completion = loadBasket(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -65,7 +65,7 @@ describe('Basket Promotion Code Effects', () => { when(basketServiceMock.addPromotionCodeToBasket(anyString(), anyString())).thenReturn(of(undefined)); store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'BID', lineItems: [], @@ -76,7 +76,7 @@ describe('Basket Promotion Code Effects', () => { it('should call the basketService for AddPromotionCodeToBasket action', done => { const code = 'CODE'; - const action = new AddPromotionCodeToBasket({ code }); + const action = addPromotionCodeToBasket({ code }); actions$ = of(action); effects.addPromotionCodeToBasket$.subscribe(() => { @@ -87,8 +87,8 @@ describe('Basket Promotion Code Effects', () => { it('should map to action of type AddPromotionCodeToBasketSuccess', () => { const code = 'CODE'; - const action = new AddPromotionCodeToBasket({ code }); - const completion = new AddPromotionCodeToBasketSuccess(); + const action = addPromotionCodeToBasket({ code }); + const completion = addPromotionCodeToBasketSuccess(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -101,8 +101,8 @@ describe('Basket Promotion Code Effects', () => { ); const code = 'CODE'; - const action = new AddPromotionCodeToBasket({ code }); - const completion = new AddPromotionCodeToBasketFail({ error: { message: 'invalid' } as HttpError }); + const action = addPromotionCodeToBasket({ code }); + const completion = addPromotionCodeToBasketFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -115,7 +115,7 @@ describe('Basket Promotion Code Effects', () => { when(basketServiceMock.removePromotionCodeFromBasket(anyString(), anyString())).thenReturn(of(undefined)); store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'BID', lineItems: [], @@ -126,7 +126,7 @@ describe('Basket Promotion Code Effects', () => { it('should call the basketService for RemovePromotionCodeFromBasket action', done => { const code = 'CODE'; - const action = new RemovePromotionCodeFromBasket({ code }); + const action = removePromotionCodeFromBasket({ code }); actions$ = of(action); effects.removePromotionCodeFromBasket$.subscribe(() => { @@ -137,8 +137,8 @@ describe('Basket Promotion Code Effects', () => { it('should map to action of type RemovePromotionCodeFromBasketSuccess', () => { const code = 'CODE'; - const action = new RemovePromotionCodeFromBasket({ code }); - const completion = new RemovePromotionCodeFromBasketSuccess(); + const action = removePromotionCodeFromBasket({ code }); + const completion = removePromotionCodeFromBasketSuccess(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -151,8 +151,8 @@ describe('Basket Promotion Code Effects', () => { ); const code = 'CODE'; - const action = new RemovePromotionCodeFromBasket({ code }); - const completion = new RemovePromotionCodeFromBasketFail({ + const action = removePromotionCodeFromBasket({ code }); + const completion = removePromotionCodeFromBasketFail({ error: { message: 'invalid' } as HttpError, }); actions$ = hot('-a-a-a', { a: action }); @@ -164,8 +164,8 @@ describe('Basket Promotion Code Effects', () => { describe('loadBasketAfterRemovePromotionCodeFromBasket$', () => { it('should map to action of type LoadBasket if RemovePromotionCodeFromBasketSuccess action triggered', () => { - const action = new RemovePromotionCodeFromBasketSuccess(); - const completion = new LoadBasket(); + const action = removePromotionCodeFromBasketSuccess(); + const completion = loadBasket(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); diff --git a/src/app/core/store/customer/basket/basket-promotion-code.effects.ts b/src/app/core/store/customer/basket/basket-promotion-code.effects.ts index b4548e223a..1c993d817a 100644 --- a/src/app/core/store/customer/basket/basket-promotion-code.effects.ts +++ b/src/app/core/store/customer/basket/basket-promotion-code.effects.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { concatMap, mapTo, withLatestFrom } from 'rxjs/operators'; @@ -7,14 +7,13 @@ import { BasketService } from 'ish-core/services/basket/basket.service'; import { mapErrorToAction, mapToPayloadProperty } from 'ish-core/utils/operators'; import { - AddPromotionCodeToBasket, - AddPromotionCodeToBasketFail, - AddPromotionCodeToBasketSuccess, - BasketActionTypes, - LoadBasket, - RemovePromotionCodeFromBasket, - RemovePromotionCodeFromBasketFail, - RemovePromotionCodeFromBasketSuccess, + addPromotionCodeToBasket, + addPromotionCodeToBasketFail, + addPromotionCodeToBasketSuccess, + loadBasket, + removePromotionCodeFromBasket, + removePromotionCodeFromBasketFail, + removePromotionCodeFromBasketSuccess, } from './basket.actions'; import { getCurrentBasketId } from './basket.selectors'; @@ -25,48 +24,46 @@ export class BasketPromotionCodeEffects { /** * Add promotion code to the current basket. */ - @Effect() - addPromotionCodeToBasket$ = this.actions$.pipe( - ofType(BasketActionTypes.AddPromotionCodeToBasket), - mapToPayloadProperty('code'), - withLatestFrom(this.store.pipe(select(getCurrentBasketId))), - concatMap(([code, basketId]) => - this.basketService - .addPromotionCodeToBasket(basketId, code) - .pipe(mapTo(new AddPromotionCodeToBasketSuccess()), mapErrorToAction(AddPromotionCodeToBasketFail)) + addPromotionCodeToBasket$ = createEffect(() => + this.actions$.pipe( + ofType(addPromotionCodeToBasket), + mapToPayloadProperty('code'), + withLatestFrom(this.store.pipe(select(getCurrentBasketId))), + concatMap(([code, basketId]) => + this.basketService + .addPromotionCodeToBasket(basketId, code) + .pipe(mapTo(addPromotionCodeToBasketSuccess()), mapErrorToAction(addPromotionCodeToBasketFail)) + ) ) ); /** * Reload basket after successfully adding a promo code */ - @Effect() - loadBasketAfterAddPromotionCodeToBasketChangeSuccess$ = this.actions$.pipe( - ofType(BasketActionTypes.AddPromotionCodeToBasketSuccess), - mapTo(new LoadBasket()) + loadBasketAfterAddPromotionCodeToBasketChangeSuccess$ = createEffect(() => + this.actions$.pipe(ofType(addPromotionCodeToBasketSuccess), mapTo(loadBasket())) ); /** * Remove promotion code from the current basket. */ - @Effect() - removePromotionCodeFromBasket$ = this.actions$.pipe( - ofType(BasketActionTypes.RemovePromotionCodeFromBasket), - mapToPayloadProperty('code'), - withLatestFrom(this.store.pipe(select(getCurrentBasketId))), - concatMap(([code, basketId]) => - this.basketService - .removePromotionCodeFromBasket(basketId, code) - .pipe(mapTo(new RemovePromotionCodeFromBasketSuccess()), mapErrorToAction(RemovePromotionCodeFromBasketFail)) + removePromotionCodeFromBasket$ = createEffect(() => + this.actions$.pipe( + ofType(removePromotionCodeFromBasket), + mapToPayloadProperty('code'), + withLatestFrom(this.store.pipe(select(getCurrentBasketId))), + concatMap(([code, basketId]) => + this.basketService + .removePromotionCodeFromBasket(basketId, code) + .pipe(mapTo(removePromotionCodeFromBasketSuccess()), mapErrorToAction(removePromotionCodeFromBasketFail)) + ) ) ); /** * Reload basket after successfully removing a promo code */ - @Effect() - loadBasketAfterRemovePromotionCodeFromBasketChangeSuccess$ = this.actions$.pipe( - ofType(BasketActionTypes.RemovePromotionCodeFromBasketSuccess), - mapTo(new LoadBasket()) + loadBasketAfterRemovePromotionCodeFromBasketChangeSuccess$ = createEffect(() => + this.actions$.pipe(ofType(removePromotionCodeFromBasketSuccess), mapTo(loadBasket())) ); } diff --git a/src/app/core/store/customer/basket/basket-validation.effects.spec.ts b/src/app/core/store/customer/basket/basket-validation.effects.spec.ts index 722bd22c45..6732766004 100644 --- a/src/app/core/store/customer/basket/basket-validation.effects.spec.ts +++ b/src/app/core/store/customer/basket/basket-validation.effects.spec.ts @@ -15,18 +15,18 @@ import { BasketService } from 'ish-core/services/basket/basket.service'; import { OrderService } from 'ish-core/services/order/order.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; -import { CreateOrder } from 'ish-core/store/customer/orders'; -import { LoadProductSuccess } from 'ish-core/store/shopping/products'; +import { createOrder } from 'ish-core/store/customer/orders'; +import { loadProductSuccess } from 'ish-core/store/shopping/products'; import { BasketMockData } from 'ish-core/utils/dev/basket-mock-data'; import { BasketValidationEffects } from './basket-validation.effects'; import { - ContinueCheckout, - ContinueCheckoutFail, - ContinueCheckoutSuccess, - ContinueCheckoutWithIssues, - LoadBasketSuccess, - ValidateBasket, + continueCheckout, + continueCheckoutFail, + continueCheckoutSuccess, + continueCheckoutWithIssues, + loadBasketSuccess, + validateBasket, } from './basket.actions'; describe('Basket Validation Effects', () => { @@ -79,15 +79,15 @@ describe('Basket Validation Effects', () => { when(basketServiceMock.validateBasket(anything(), anything())).thenReturn(of(basketValidation)); store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: BasketMockData.getBasket(), }) ); - store$.dispatch(new LoadProductSuccess({ product: { sku: 'SKU' } as Product })); + store$.dispatch(loadProductSuccess({ product: { sku: 'SKU' } as Product })); }); it('should call the basketService for validateBasket', done => { - const action = new ValidateBasket({ scopes: ['Products'] }); + const action = validateBasket({ scopes: ['Products'] }); actions$ = of(action); effects.validateBasket$.subscribe(() => { @@ -97,8 +97,8 @@ describe('Basket Validation Effects', () => { }); it('should map to action of type ContinueCheckoutSuccess', () => { - const action = new ValidateBasket({ scopes: ['Products'] }); - const completion = new ContinueCheckoutSuccess({ + const action = validateBasket({ scopes: ['Products'] }); + const completion = continueCheckoutSuccess({ targetRoute: undefined, basketValidation, }); @@ -111,8 +111,8 @@ describe('Basket Validation Effects', () => { it('should map invalid request to action of type ContinueCheckoutFail', () => { when(basketServiceMock.validateBasket(anyString(), anything())).thenReturn(throwError({ message: 'invalid' })); - const action = new ValidateBasket({ scopes: ['Products'] }); - const completion = new ContinueCheckoutFail({ error: { message: 'invalid' } as HttpError }); + const action = validateBasket({ scopes: ['Products'] }); + const completion = continueCheckoutFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a', { a: action }); const expected$ = cold('-c', { c: completion }); @@ -120,9 +120,9 @@ describe('Basket Validation Effects', () => { }); it('should map to action of type ContinueCheckoutWithIssues if basket is not valid', () => { - const action = new ValidateBasket({ scopes: ['Products'] }); + const action = validateBasket({ scopes: ['Products'] }); basketValidation.results.valid = false; - const completion = new ContinueCheckoutWithIssues({ + const completion = continueCheckoutWithIssues({ targetRoute: undefined, basketValidation, }); @@ -146,15 +146,15 @@ describe('Basket Validation Effects', () => { when(basketServiceMock.validateBasket(anything(), anything())).thenReturn(of(basketValidation)); store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: BasketMockData.getBasket(), }) ); - store$.dispatch(new LoadProductSuccess({ product: { sku: 'SKU' } as Product })); + store$.dispatch(loadProductSuccess({ product: { sku: 'SKU' } as Product })); }); it('should call the basketService for validateBasketAndContinueCheckout', done => { - const action = new ContinueCheckout({ targetStep: 1 }); + const action = continueCheckout({ targetStep: 1 }); actions$ = of(action); effects.validateBasketAndContinueCheckout$.subscribe(() => { @@ -164,8 +164,8 @@ describe('Basket Validation Effects', () => { }); it('should map to action of type ContinueCheckoutSuccess if targetStep is not 5 (order creation)', () => { - const action = new ContinueCheckout({ targetStep: 1 }); - const completion = new ContinueCheckoutSuccess({ + const action = continueCheckout({ targetStep: 1 }); + const completion = continueCheckoutSuccess({ targetRoute: '/checkout/address', basketValidation, }); @@ -176,9 +176,9 @@ describe('Basket Validation Effects', () => { }); it('should map to action of type CreateOrder if targetStep is 5 (order creation)', () => { - const action = new ContinueCheckout({ targetStep: 5 }); - const completion1 = new CreateOrder({ basketId: BasketMockData.getBasket().id }); - const completion2 = new ContinueCheckoutSuccess({ targetRoute: undefined, basketValidation }); + const action = continueCheckout({ targetStep: 5 }); + const completion1 = createOrder({ basketId: BasketMockData.getBasket().id }); + const completion2 = continueCheckoutSuccess({ targetRoute: undefined, basketValidation }); actions$ = hot('-a----a----a', { a: action }); const expected$ = cold('-(cd)-(cd)-(cd)', { c: completion1, d: completion2 }); @@ -188,8 +188,8 @@ describe('Basket Validation Effects', () => { it('should map invalid request to action of type ContinueCheckoutFail', () => { when(basketServiceMock.validateBasket(anyString(), anything())).thenReturn(throwError({ message: 'invalid' })); - const action = new ContinueCheckout({ targetStep: 1 }); - const completion = new ContinueCheckoutFail({ error: { message: 'invalid' } as HttpError }); + const action = continueCheckout({ targetStep: 1 }); + const completion = continueCheckoutFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a', { a: action }); const expected$ = cold('-c', { c: completion }); @@ -197,7 +197,7 @@ describe('Basket Validation Effects', () => { }); it('should navigate to the next checkout route after ContinueCheckoutSuccess if the basket is valid', fakeAsync(() => { - const action = new ContinueCheckoutSuccess({ targetRoute: '/checkout/address', basketValidation }); + const action = continueCheckoutSuccess({ targetRoute: '/checkout/address', basketValidation }); actions$ = of(action); effects.jumpToNextCheckoutStep$.subscribe(noop, fail, noop); @@ -216,7 +216,7 @@ describe('Basket Validation Effects', () => { errors: [{ code: '1234', message: 'error', parameters: { scopes: 'Addresses' } }], }, }; - const action = new ContinueCheckoutWithIssues({ + const action = continueCheckoutWithIssues({ targetRoute: 'auto', basketValidation: basketValidationWithIssue, }); @@ -230,9 +230,9 @@ describe('Basket Validation Effects', () => { })); it('should map to action of type ContinueCheckoutWithIssues if basket is not valid', () => { - const action = new ContinueCheckout({ targetStep: 1 }); + const action = continueCheckout({ targetStep: 1 }); basketValidation.results.valid = false; - const completion = new ContinueCheckoutWithIssues({ + const completion = continueCheckoutWithIssues({ targetRoute: '/checkout/address', basketValidation, }); diff --git a/src/app/core/store/customer/basket/basket-validation.effects.ts b/src/app/core/store/customer/basket/basket-validation.effects.ts index f16c59b975..925561ab63 100644 --- a/src/app/core/store/customer/basket/basket-validation.effects.ts +++ b/src/app/core/store/customer/basket/basket-validation.effects.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { concatMap, filter, map, tap, withLatestFrom } from 'rxjs/operators'; @@ -9,19 +9,18 @@ import { BasketValidationScopeType, } from 'ish-core/models/basket-validation/basket-validation.model'; import { BasketService } from 'ish-core/services/basket/basket.service'; -import { CreateOrder } from 'ish-core/store/customer/orders'; -import { LoadProduct } from 'ish-core/store/shopping/products'; +import { createOrder } from 'ish-core/store/customer/orders'; +import { loadProduct } from 'ish-core/store/shopping/products'; import { mapErrorToAction, mapToPayload, mapToPayloadProperty, whenTruthy } from 'ish-core/utils/operators'; import { - BasketActionTypes, - ContinueCheckout, - ContinueCheckoutFail, - ContinueCheckoutSuccess, - ContinueCheckoutWithIssues, - LoadBasketEligiblePaymentMethods, - LoadBasketEligibleShippingMethods, - ValidateBasket, + continueCheckout, + continueCheckoutFail, + continueCheckoutSuccess, + continueCheckoutWithIssues, + loadBasketEligiblePaymentMethods, + loadBasketEligibleShippingMethods, + validateBasket, } from './basket.actions'; import { getCurrentBasketId } from './basket.selectors'; @@ -37,20 +36,21 @@ export class BasketValidationEffects { /** * validates the basket but doesn't change the route */ - @Effect() - validateBasket$ = this.actions$.pipe( - ofType(BasketActionTypes.ValidateBasket), - mapToPayloadProperty('scopes'), - withLatestFrom(this.store.pipe(select(getCurrentBasketId))), - whenTruthy(), - concatMap(([scopes, basketId]) => - this.basketService.validateBasket(basketId, scopes).pipe( - map(basketValidation => - basketValidation.results.valid - ? new ContinueCheckoutSuccess({ targetRoute: undefined, basketValidation }) - : new ContinueCheckoutWithIssues({ targetRoute: undefined, basketValidation }) - ), - mapErrorToAction(ContinueCheckoutFail) + validateBasket$ = createEffect(() => + this.actions$.pipe( + ofType(validateBasket), + mapToPayloadProperty('scopes'), + withLatestFrom(this.store.pipe(select(getCurrentBasketId))), + whenTruthy(), + concatMap(([scopes, basketId]) => + this.basketService.validateBasket(basketId, scopes).pipe( + map(basketValidation => + basketValidation.results.valid + ? continueCheckoutSuccess({ targetRoute: undefined, basketValidation }) + : continueCheckoutWithIssues({ targetRoute: undefined, basketValidation }) + ), + mapErrorToAction(continueCheckoutFail) + ) ) ) ); @@ -58,94 +58,90 @@ export class BasketValidationEffects { /** * Validates the basket before the user is allowed to jump to the next basket step */ - @Effect() - validateBasketAndContinueCheckout$ = this.actions$.pipe( - ofType(BasketActionTypes.ContinueCheckout), - mapToPayloadProperty('targetStep'), - withLatestFrom(this.store.pipe(select(getCurrentBasketId))), - whenTruthy(), - concatMap(([targetStep, basketId]) => { - let scopes: BasketValidationScopeType[] = ['']; - let targetRoute = ''; - switch (targetStep) { - case 1: { - scopes = ['Products', 'Value']; - targetRoute = '/checkout/address'; - break; - } - case 2: { - scopes = ['InvoiceAddress', 'ShippingAddress', 'Addresses']; - targetRoute = '/checkout/shipping'; - break; - } - case 3: { - scopes = ['Shipping']; - targetRoute = '/checkout/payment'; - break; - } - case 4: { - scopes = ['Payment']; - targetRoute = '/checkout/review'; - break; + validateBasketAndContinueCheckout$ = createEffect(() => + this.actions$.pipe( + ofType(continueCheckout), + mapToPayloadProperty('targetStep'), + withLatestFrom(this.store.pipe(select(getCurrentBasketId))), + whenTruthy(), + concatMap(([targetStep, basketId]) => { + let scopes: BasketValidationScopeType[] = ['']; + let targetRoute = ''; + switch (targetStep) { + case 1: { + scopes = ['Products', 'Value']; + targetRoute = '/checkout/address'; + break; + } + case 2: { + scopes = ['InvoiceAddress', 'ShippingAddress', 'Addresses']; + targetRoute = '/checkout/shipping'; + break; + } + case 3: { + scopes = ['Shipping']; + targetRoute = '/checkout/payment'; + break; + } + case 4: { + scopes = ['Payment']; + targetRoute = '/checkout/review'; + break; + } + // before order creation the whole basket is validated again + case 5: { + scopes = ['All']; + targetRoute = 'auto'; // targetRoute will be calculated in dependence of the validation result + break; + } } - // before order creation the whole basket is validated again - case 5: { - scopes = ['All']; - targetRoute = 'auto'; // targetRoute will be calculated in dependence of the validation result - break; - } - } - return this.basketService.validateBasket(basketId, scopes).pipe( - concatMap(basketValidation => - basketValidation.results.valid - ? targetStep === 5 && !basketValidation.results.adjusted - ? [ - new CreateOrder({ basketId }), - new ContinueCheckoutSuccess({ targetRoute: undefined, basketValidation }), - ] - : [new ContinueCheckoutSuccess({ targetRoute, basketValidation })] - : [new ContinueCheckoutWithIssues({ targetRoute, basketValidation })] - ), - mapErrorToAction(ContinueCheckoutFail) - ); - }) + return this.basketService.validateBasket(basketId, scopes).pipe( + concatMap(basketValidation => + basketValidation.results.valid + ? targetStep === 5 && !basketValidation.results.adjusted + ? [createOrder({ basketId }), continueCheckoutSuccess({ targetRoute: undefined, basketValidation })] + : [continueCheckoutSuccess({ targetRoute, basketValidation })] + : [continueCheckoutWithIssues({ targetRoute, basketValidation })] + ), + mapErrorToAction(continueCheckoutFail) + ); + }) + ) ); /** * Jumps to the next checkout step after basket validation. In case of adjustments related data like product data, eligible shipping methods etc. are loaded. */ - @Effect() - jumpToNextCheckoutStep$ = this.actions$.pipe( - ofType( - BasketActionTypes.ContinueCheckoutSuccess, - BasketActionTypes.ContinueCheckoutWithIssues - ), - mapToPayload(), - tap(payload => { - this.jumpToTargetRoute(payload.targetRoute, payload.basketValidation && payload.basketValidation.results); - }), + jumpToNextCheckoutStep$ = createEffect(() => + this.actions$.pipe( + ofType(continueCheckoutSuccess, continueCheckoutWithIssues), + mapToPayload(), + tap(payload => { + this.jumpToTargetRoute(payload.targetRoute, payload.basketValidation && payload.basketValidation.results); + }), - filter( - payload => - payload.basketValidation && - payload.basketValidation.results.adjusted && - !!payload.basketValidation.results.infos - ), - map(payload => payload.basketValidation), - concatMap(validation => { - // Load eligible shipping methods if shipping infos are available - if (validation.scopes.includes('Shipping')) { - return [new LoadBasketEligibleShippingMethods()]; - // Load eligible payment methods if payment infos are available - } else if (validation.scopes.includes('Payment')) { - return [new LoadBasketEligiblePaymentMethods()]; - } else { - // Load products if product related infos are available - return validation.results.infos - .filter(info => info.parameters && info.parameters.productSku) - .map(info => new LoadProduct({ sku: info.parameters.productSku })); - } - }) + filter( + payload => + payload.basketValidation && + payload.basketValidation.results.adjusted && + !!payload.basketValidation.results.infos + ), + map(payload => payload.basketValidation), + concatMap(validation => { + // Load eligible shipping methods if shipping infos are available + if (validation.scopes.includes('Shipping')) { + return [loadBasketEligibleShippingMethods()]; + // Load eligible payment methods if payment infos are available + } else if (validation.scopes.includes('Payment')) { + return [loadBasketEligiblePaymentMethods()]; + } else { + // Load products if product related infos are available + return validation.results.infos + .filter(info => info.parameters && info.parameters.productSku) + .map(info => loadProduct({ sku: info.parameters.productSku })); + } + }) + ) ); /** diff --git a/src/app/core/store/customer/basket/basket.actions.ts b/src/app/core/store/customer/basket/basket.actions.ts index fdca26c913..0517b0b4da 100644 --- a/src/app/core/store/customer/basket/basket.actions.ts +++ b/src/app/core/store/customer/basket/basket.actions.ts @@ -1,392 +1,206 @@ import { Params } from '@angular/router'; -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; import { Address } from 'ish-core/models/address/address.model'; import { BasketInfo } from 'ish-core/models/basket-info/basket-info.model'; import { BasketValidation, BasketValidationScopeType } from 'ish-core/models/basket-validation/basket-validation.model'; import { Basket } from 'ish-core/models/basket/basket.model'; -import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { LineItemUpdate } from 'ish-core/models/line-item-update/line-item-update.model'; import { PaymentInstrument } from 'ish-core/models/payment-instrument/payment-instrument.model'; import { PaymentMethod } from 'ish-core/models/payment-method/payment-method.model'; import { ShippingMethod } from 'ish-core/models/shipping-method/shipping-method.model'; import { BasketUpdateType } from 'ish-core/services/basket/basket.service'; +import { httpError, payload } from 'ish-core/utils/ngrx-creators'; -export enum BasketActionTypes { - LoadBasket = '[Basket Internal] Load Basket', - LoadBasketByAPIToken = '[Basket Internal] Load Basket by API Token', - LoadBasketFail = '[Basket API] Load Basket Fail', - LoadBasketSuccess = '[Basket API] Load Basket Success', - CreateBasketAddress = '[Basket] Create Basket Address', - CreateBasketAddressSuccess = '[Basket Internal] Create Basket Address Success', - AssignBasketAddress = '[Basket] Assign an Address to the Basket', - UpdateBasketAddress = '[Basket] Update an Address at Basket', - UpdateBasketShippingMethod = '[Basket] Update Baskets Shipping Method', - UpdateBasket = '[Basket Internal] Update Basket', - UpdateBasketFail = '[Basket API] Update Basket Fail', - DeleteBasketShippingAddress = '[Basket] Delete Basket Shipping Address', - AddProductToBasket = '[Basket] Add Product', - AddItemsToBasket = '[Basket Internal] Add Items To Basket', - AddItemsToBasketFail = '[Basket API] Add Items To Basket Fail', - AddItemsToBasketSuccess = '[Basket API] Add Items To Basket Success', - MergeBasket = '[Basket Internal] Merge two baskets', - MergeBasketFail = '[Basket API] Merge two baskets Fail', - MergeBasketSuccess = '[Basket API] Merge two baskets Success', - ValidateBasket = '[Basket] Validate Basket', - ContinueCheckout = '[Basket] Validate Basket and continue checkout', - ContinueCheckoutFail = '[Basket API] Validate Basket and continue checkout Fail', - ContinueCheckoutSuccess = '[Basket API] Validate Basket and continue with success', - ContinueCheckoutWithIssues = '[Basket API] Validate Basket and continue with issues', - AddPromotionCodeToBasket = '[Basket Internal] Add Promotion Code To Basket', - AddPromotionCodeToBasketFail = '[Basket API] Add Promotion Code To Basket Fail', - AddPromotionCodeToBasketSuccess = '[Basket API] Add Promotion Code To Basket Success', - RemovePromotionCodeFromBasket = '[Basket Internal] Remove Promotion Code From Basket', - RemovePromotionCodeFromBasketFail = '[Basket API] Remove Promotion Code From Basket Fail', - RemovePromotionCodeFromBasketSuccess = '[Basket API] Remove Promotion Code From Basket Success', - UpdateBasketItems = '[Basket] Update Basket Items', - UpdateBasketItemsFail = '[Basket API] Update Basket Items Fail', - UpdateBasketItemsSuccess = '[Basket API] Update Basket Items Success', - DeleteBasketItem = '[Basket] Delete Basket Item', - DeleteBasketItemFail = '[Basket API] Delete Basket Item Fail', - DeleteBasketItemSuccess = '[Basket API] Delete Basket Item Success', - LoadBasketEligibleShippingMethods = '[Basket] Load Basket Eligible Shipping Methods', - LoadBasketEligibleShippingMethodsFail = '[Basket API] Load Basket Eligible Shipping Methods Fail', - LoadBasketEligibleShippingMethodsSuccess = '[Basket API] Load Basket Eligible Shipping Methods Success', - LoadBasketEligiblePaymentMethods = '[Basket] Load Basket Eligible Payment Methods', - LoadBasketEligiblePaymentMethodsFail = '[Basket API] Load Basket Eligible Payment Methods Fail', - LoadBasketEligiblePaymentMethodsSuccess = '[Basket API] Load Basket Eligible Payment Methods Success', - SetBasketPayment = '[Basket] Set a Payment at Basket ', - SetBasketPaymentFail = '[Basket API] Set a Payment at Basket Fail', - SetBasketPaymentSuccess = '[Basket API] Set a Payment at Basket Success', - CreateBasketPayment = '[Basket] Create a Basket Payment', - CreateBasketPaymentFail = '[Basket API] Create a Basket Payment Fail', - CreateBasketPaymentSuccess = '[Basket API] Create a Basket Payment Success', - UpdateBasketPayment = '[Basket] Update a Basket Payment with Redirect Data', - UpdateBasketPaymentFail = '[Basket API] Update a Basket Payment Fail', - UpdateBasketPaymentSuccess = '[Basket API] Update a Basket Payment Success', - DeleteBasketPayment = '[Basket] Delete Basket Payment', - DeleteBasketPaymentFail = '[Basket API] Delete Basket Payment Fail', - DeleteBasketPaymentSuccess = '[Basket API] Delete Basket Payment Success', - ResetBasketErrors = '[Basket Internal] Reset Basket and Basket Promotion Errors', -} - -export class LoadBasket implements Action { - readonly type = BasketActionTypes.LoadBasket; -} - -export class LoadBasketByAPIToken implements Action { - readonly type = BasketActionTypes.LoadBasketByAPIToken; - constructor(public payload: { apiToken: string }) {} -} - -export class LoadBasketFail implements Action { - readonly type = BasketActionTypes.LoadBasketFail; - constructor(public payload: { error: HttpError }) {} -} - -export class LoadBasketSuccess implements Action { - readonly type = BasketActionTypes.LoadBasketSuccess; - constructor(public payload: { basket: Basket }) {} -} - -export class CreateBasketAddress implements Action { - readonly type = BasketActionTypes.CreateBasketAddress; - constructor(public payload: { address: Address; scope: 'invoice' | 'shipping' | 'any' }) {} -} - -export class CreateBasketAddressSuccess implements Action { - readonly type = BasketActionTypes.CreateBasketAddressSuccess; - constructor(public payload: { address: Address; scope: 'invoice' | 'shipping' | 'any' }) {} -} - -export class AssignBasketAddress implements Action { - readonly type = BasketActionTypes.AssignBasketAddress; - constructor(public payload: { addressId: string; scope: 'invoice' | 'shipping' | 'any' }) {} -} - -export class UpdateBasketAddress implements Action { - readonly type = BasketActionTypes.UpdateBasketAddress; - constructor(public payload: { address: Address }) {} -} - -export class UpdateBasketShippingMethod implements Action { - readonly type = BasketActionTypes.UpdateBasketShippingMethod; - constructor(public payload: { shippingId: string }) {} -} - -export class UpdateBasket implements Action { - readonly type = BasketActionTypes.UpdateBasket; - constructor(public payload: { update: BasketUpdateType }) {} -} - -export class UpdateBasketFail implements Action { - readonly type = BasketActionTypes.UpdateBasketFail; - constructor(public payload: { error: HttpError }) {} -} - -export class DeleteBasketShippingAddress implements Action { - readonly type = BasketActionTypes.DeleteBasketShippingAddress; - constructor(public payload: { addressId: string }) {} -} - -export class AddProductToBasket implements Action { - readonly type = BasketActionTypes.AddProductToBasket; - constructor(public payload: { sku: string; quantity: number }) {} -} - -export class AddItemsToBasket implements Action { - readonly type = BasketActionTypes.AddItemsToBasket; - constructor(public payload: { items: { sku: string; quantity: number; unit: string }[]; basketId?: string }) {} -} - -export class AddItemsToBasketFail implements Action { - readonly type = BasketActionTypes.AddItemsToBasketFail; - constructor(public payload: { error: HttpError }) {} -} - -export class AddItemsToBasketSuccess implements Action { - readonly type = BasketActionTypes.AddItemsToBasketSuccess; - constructor(public payload: { info: BasketInfo[] }) {} -} - -export class MergeBasket implements Action { - readonly type = BasketActionTypes.MergeBasket; -} - -export class MergeBasketFail implements Action { - readonly type = BasketActionTypes.MergeBasketFail; - constructor(public payload: { error: HttpError }) {} -} - -export class MergeBasketSuccess implements Action { - readonly type = BasketActionTypes.MergeBasketSuccess; - constructor(public payload: { basket: Basket }) {} -} - -export class ValidateBasket implements Action { - readonly type = BasketActionTypes.ValidateBasket; - constructor(public payload: { scopes: BasketValidationScopeType[] }) {} -} - -export class ContinueCheckout implements Action { - readonly type = BasketActionTypes.ContinueCheckout; - constructor(public payload: { targetStep: number }) {} -} - -export class ContinueCheckoutFail implements Action { - readonly type = BasketActionTypes.ContinueCheckoutFail; - constructor(public payload: { error: HttpError }) {} -} - -export class ContinueCheckoutSuccess implements Action { - readonly type = BasketActionTypes.ContinueCheckoutSuccess; - constructor(public payload: { targetRoute: string; basketValidation: BasketValidation }) {} -} - -export class ContinueCheckoutWithIssues implements Action { - readonly type = BasketActionTypes.ContinueCheckoutWithIssues; - constructor(public payload: { targetRoute: string; basketValidation: BasketValidation }) {} -} - -export class UpdateBasketItems implements Action { - readonly type = BasketActionTypes.UpdateBasketItems; - constructor(public payload: { lineItemUpdates: LineItemUpdate[] }) {} -} - -export class UpdateBasketItemsFail implements Action { - readonly type = BasketActionTypes.UpdateBasketItemsFail; - constructor(public payload: { error: HttpError }) {} -} - -export class UpdateBasketItemsSuccess implements Action { - readonly type = BasketActionTypes.UpdateBasketItemsSuccess; - constructor(public payload: { info: BasketInfo[] }) {} -} - -export class DeleteBasketItem implements Action { - readonly type = BasketActionTypes.DeleteBasketItem; - constructor(public payload: { itemId: string }) {} -} - -export class DeleteBasketItemFail implements Action { - readonly type = BasketActionTypes.DeleteBasketItemFail; - constructor(public payload: { error: HttpError }) {} -} - -export class DeleteBasketItemSuccess implements Action { - readonly type = BasketActionTypes.DeleteBasketItemSuccess; - constructor(public payload: { info: BasketInfo[] }) {} -} - -export class RemovePromotionCodeFromBasket implements Action { - readonly type = BasketActionTypes.RemovePromotionCodeFromBasket; - constructor(public payload: { code: string }) {} -} - -export class RemovePromotionCodeFromBasketFail implements Action { - readonly type = BasketActionTypes.RemovePromotionCodeFromBasketFail; - constructor(public payload: { error: HttpError }) {} -} - -export class RemovePromotionCodeFromBasketSuccess implements Action { - readonly type = BasketActionTypes.RemovePromotionCodeFromBasketSuccess; -} - -export class AddPromotionCodeToBasket implements Action { - readonly type = BasketActionTypes.AddPromotionCodeToBasket; - constructor(public payload: { code: string }) {} -} - -export class AddPromotionCodeToBasketFail implements Action { - readonly type = BasketActionTypes.AddPromotionCodeToBasketFail; - constructor(public payload: { error: HttpError }) {} -} - -export class AddPromotionCodeToBasketSuccess implements Action { - readonly type = BasketActionTypes.AddPromotionCodeToBasketSuccess; -} - -export class LoadBasketEligibleShippingMethods implements Action { - readonly type = BasketActionTypes.LoadBasketEligibleShippingMethods; -} - -export class LoadBasketEligibleShippingMethodsFail implements Action { - readonly type = BasketActionTypes.LoadBasketEligibleShippingMethodsFail; - constructor(public payload: { error: HttpError }) {} -} - -export class LoadBasketEligibleShippingMethodsSuccess implements Action { - readonly type = BasketActionTypes.LoadBasketEligibleShippingMethodsSuccess; - constructor(public payload: { shippingMethods: ShippingMethod[] }) {} -} - -export class LoadBasketEligiblePaymentMethods implements Action { - readonly type = BasketActionTypes.LoadBasketEligiblePaymentMethods; -} - -export class LoadBasketEligiblePaymentMethodsFail implements Action { - readonly type = BasketActionTypes.LoadBasketEligiblePaymentMethodsFail; - constructor(public payload: { error: HttpError }) {} -} - -export class LoadBasketEligiblePaymentMethodsSuccess implements Action { - readonly type = BasketActionTypes.LoadBasketEligiblePaymentMethodsSuccess; - constructor(public payload: { paymentMethods: PaymentMethod[] }) {} -} - -export class SetBasketPayment implements Action { - readonly type = BasketActionTypes.SetBasketPayment; - constructor(public payload: { id: string }) {} -} - -export class SetBasketPaymentFail implements Action { - readonly type = BasketActionTypes.SetBasketPaymentFail; - constructor(public payload: { error: HttpError }) {} -} - -export class SetBasketPaymentSuccess implements Action { - readonly type = BasketActionTypes.SetBasketPaymentSuccess; -} - -export class CreateBasketPayment implements Action { - readonly type = BasketActionTypes.CreateBasketPayment; - constructor(public payload: { paymentInstrument: PaymentInstrument; saveForLater: boolean }) {} -} - -export class CreateBasketPaymentFail implements Action { - readonly type = BasketActionTypes.CreateBasketPaymentFail; - constructor(public payload: { error: HttpError }) {} -} - -export class CreateBasketPaymentSuccess implements Action { - readonly type = BasketActionTypes.CreateBasketPaymentSuccess; -} - -export class UpdateBasketPayment implements Action { - readonly type = BasketActionTypes.UpdateBasketPayment; - constructor(public payload: { params: Params }) {} -} - -export class UpdateBasketPaymentFail implements Action { - readonly type = BasketActionTypes.UpdateBasketPaymentFail; - constructor(public payload: { error: HttpError }) {} -} - -export class UpdateBasketPaymentSuccess implements Action { - readonly type = BasketActionTypes.UpdateBasketPaymentSuccess; -} - -export class DeleteBasketPayment implements Action { - readonly type = BasketActionTypes.DeleteBasketPayment; - constructor(public payload: { paymentInstrument: PaymentInstrument }) {} -} - -export class DeleteBasketPaymentFail implements Action { - readonly type = BasketActionTypes.DeleteBasketPaymentFail; - constructor(public payload: { error: HttpError }) {} -} - -export class DeleteBasketPaymentSuccess implements Action { - readonly type = BasketActionTypes.DeleteBasketPaymentSuccess; -} - -export class ResetBasketErrors implements Action { - readonly type = BasketActionTypes.ResetBasketErrors; -} - -export type BasketAction = - | LoadBasket - | LoadBasketByAPIToken - | LoadBasketFail - | LoadBasketSuccess - | CreateBasketAddress - | CreateBasketAddressSuccess - | AssignBasketAddress - | UpdateBasketAddress - | UpdateBasketShippingMethod - | UpdateBasket - | UpdateBasketFail - | DeleteBasketShippingAddress - | AddProductToBasket - | AddItemsToBasket - | AddItemsToBasketFail - | AddItemsToBasketSuccess - | MergeBasket - | MergeBasketFail - | MergeBasketSuccess - | ValidateBasket - | ContinueCheckout - | ContinueCheckoutFail - | ContinueCheckoutSuccess - | ContinueCheckoutWithIssues - | AddPromotionCodeToBasket - | AddPromotionCodeToBasketFail - | AddPromotionCodeToBasketSuccess - | RemovePromotionCodeFromBasket - | RemovePromotionCodeFromBasketFail - | RemovePromotionCodeFromBasketSuccess - | UpdateBasketItems - | UpdateBasketItemsFail - | UpdateBasketItemsSuccess - | DeleteBasketItem - | DeleteBasketItemFail - | DeleteBasketItemSuccess - | LoadBasketEligibleShippingMethods - | LoadBasketEligibleShippingMethodsFail - | LoadBasketEligibleShippingMethodsSuccess - | LoadBasketEligiblePaymentMethods - | LoadBasketEligiblePaymentMethodsFail - | LoadBasketEligiblePaymentMethodsSuccess - | SetBasketPayment - | SetBasketPaymentFail - | SetBasketPaymentSuccess - | CreateBasketPayment - | CreateBasketPaymentFail - | CreateBasketPaymentSuccess - | UpdateBasketPayment - | UpdateBasketPaymentFail - | UpdateBasketPaymentSuccess - | DeleteBasketPayment - | DeleteBasketPaymentFail - | DeleteBasketPaymentSuccess - | ResetBasketErrors; +export const loadBasket = createAction('[Basket Internal] Load Basket'); + +export const loadBasketByAPIToken = createAction( + '[Basket Internal] Load Basket by API Token', + payload<{ apiToken: string }>() +); + +export const loadBasketFail = createAction('[Basket API] Load Basket Fail', httpError()); + +export const loadBasketSuccess = createAction('[Basket API] Load Basket Success', payload<{ basket: Basket }>()); + +export const createBasketAddress = createAction( + '[Basket] Create Basket Address', + payload<{ address: Address; scope: 'invoice' | 'shipping' | 'any' }>() +); + +export const createBasketAddressSuccess = createAction( + '[Basket Internal] Create Basket Address Success', + payload<{ address: Address; scope: 'invoice' | 'shipping' | 'any' }>() +); + +export const assignBasketAddress = createAction( + '[Basket] Assign an Address to the Basket', + payload<{ addressId: string; scope: 'invoice' | 'shipping' | 'any' }>() +); + +export const updateBasketAddress = createAction( + '[Basket] Update an Address at Basket', + payload<{ address: Address }>() +); + +export const updateBasketShippingMethod = createAction( + '[Basket] Update Baskets Shipping Method', + payload<{ shippingId: string }>() +); + +export const updateBasket = createAction('[Basket Internal] Update Basket', payload<{ update: BasketUpdateType }>()); + +export const updateBasketFail = createAction('[Basket API] Update Basket Fail', httpError()); + +export const deleteBasketShippingAddress = createAction( + '[Basket] Delete Basket Shipping Address', + payload<{ addressId: string }>() +); + +export const addProductToBasket = createAction('[Basket] Add Product', payload<{ sku: string; quantity: number }>()); + +export const addItemsToBasket = createAction( + '[Basket Internal] Add Items To Basket', + payload<{ items: { sku: string; quantity: number; unit: string }[]; basketId?: string }>() +); + +export const addItemsToBasketFail = createAction('[Basket API] Add Items To Basket Fail', httpError()); + +export const addItemsToBasketSuccess = createAction( + '[Basket API] Add Items To Basket Success', + payload<{ info: BasketInfo[] }>() +); + +export const mergeBasket = createAction('[Basket Internal] Merge two baskets'); + +export const mergeBasketFail = createAction('[Basket API] Merge two baskets Fail', httpError()); + +export const mergeBasketSuccess = createAction('[Basket API] Merge two baskets Success', payload<{ basket: Basket }>()); + +export const validateBasket = createAction( + '[Basket] Validate Basket', + payload<{ scopes: BasketValidationScopeType[] }>() +); + +export const continueCheckout = createAction( + '[Basket] Validate Basket and continue checkout', + payload<{ targetStep: number }>() +); + +export const continueCheckoutFail = createAction( + '[Basket API] Validate Basket and continue checkout Fail', + httpError() +); + +export const continueCheckoutSuccess = createAction( + '[Basket API] Validate Basket and continue with success', + payload<{ targetRoute: string; basketValidation: BasketValidation }>() +); + +export const continueCheckoutWithIssues = createAction( + '[Basket API] Validate Basket and continue with issues', + payload<{ targetRoute: string; basketValidation: BasketValidation }>() +); + +export const updateBasketItems = createAction( + '[Basket] Update Basket Items', + payload<{ lineItemUpdates: LineItemUpdate[] }>() +); + +export const updateBasketItemsFail = createAction('[Basket API] Update Basket Items Fail', httpError()); + +export const updateBasketItemsSuccess = createAction( + '[Basket API] Update Basket Items Success', + payload<{ info: BasketInfo[] }>() +); + +export const deleteBasketItem = createAction('[Basket] Delete Basket Item', payload<{ itemId: string }>()); + +export const deleteBasketItemFail = createAction('[Basket API] Delete Basket Item Fail', httpError()); + +export const deleteBasketItemSuccess = createAction( + '[Basket API] Delete Basket Item Success', + payload<{ info: BasketInfo[] }>() +); + +export const removePromotionCodeFromBasket = createAction( + '[Basket Internal] Remove Promotion Code From Basket', + payload<{ code: string }>() +); + +export const removePromotionCodeFromBasketFail = createAction( + '[Basket API] Remove Promotion Code From Basket Fail', + httpError() +); + +export const removePromotionCodeFromBasketSuccess = createAction( + '[Basket API] Remove Promotion Code From Basket Success' +); + +export const addPromotionCodeToBasket = createAction( + '[Basket Internal] Add Promotion Code To Basket', + payload<{ code: string }>() +); + +export const addPromotionCodeToBasketFail = createAction('[Basket API] Add Promotion Code To Basket Fail', httpError()); + +export const addPromotionCodeToBasketSuccess = createAction('[Basket API] Add Promotion Code To Basket Success'); + +export const loadBasketEligibleShippingMethods = createAction('[Basket] Load Basket Eligible Shipping Methods'); + +export const loadBasketEligibleShippingMethodsFail = createAction( + '[Basket API] Load Basket Eligible Shipping Methods Fail', + httpError() +); + +export const loadBasketEligibleShippingMethodsSuccess = createAction( + '[Basket API] Load Basket Eligible Shipping Methods Success', + payload<{ shippingMethods: ShippingMethod[] }>() +); + +export const loadBasketEligiblePaymentMethods = createAction('[Basket] Load Basket Eligible Payment Methods'); + +export const loadBasketEligiblePaymentMethodsFail = createAction( + '[Basket API] Load Basket Eligible Payment Methods Fail', + httpError() +); + +export const loadBasketEligiblePaymentMethodsSuccess = createAction( + '[Basket API] Load Basket Eligible Payment Methods Success', + payload<{ paymentMethods: PaymentMethod[] }>() +); + +export const setBasketPayment = createAction('[Basket] Set a Payment at Basket ', payload<{ id: string }>()); + +export const setBasketPaymentFail = createAction('[Basket API] Set a Payment at Basket Fail', httpError()); + +export const setBasketPaymentSuccess = createAction('[Basket API] Set a Payment at Basket Success'); + +export const createBasketPayment = createAction( + '[Basket] Create a Basket Payment', + payload<{ paymentInstrument: PaymentInstrument; saveForLater: boolean }>() +); + +export const createBasketPaymentFail = createAction('[Basket API] Create a Basket Payment Fail', httpError()); + +export const createBasketPaymentSuccess = createAction('[Basket API] Create a Basket Payment Success'); + +export const updateBasketPayment = createAction( + '[Basket] Update a Basket Payment with Redirect Data', + payload<{ params: Params }>() +); + +export const updateBasketPaymentFail = createAction('[Basket API] Update a Basket Payment Fail', httpError()); + +export const updateBasketPaymentSuccess = createAction('[Basket API] Update a Basket Payment Success'); + +export const deleteBasketPayment = createAction( + '[Basket] Delete Basket Payment', + payload<{ paymentInstrument: PaymentInstrument }>() +); + +export const deleteBasketPaymentFail = createAction('[Basket API] Delete Basket Payment Fail', httpError()); + +export const deleteBasketPaymentSuccess = createAction('[Basket API] Delete Basket Payment Success'); + +export const resetBasketErrors = createAction('[Basket Internal] Reset Basket and Basket Promotion Errors'); diff --git a/src/app/core/store/customer/basket/basket.effects.spec.ts b/src/app/core/store/customer/basket/basket.effects.spec.ts index 4549b8165f..e1308e9875 100644 --- a/src/app/core/store/customer/basket/basket.effects.spec.ts +++ b/src/app/core/store/customer/basket/basket.effects.spec.ts @@ -18,26 +18,26 @@ import { Product, ProductCompletenessLevel } from 'ish-core/models/product/produ import { BasketService } from 'ish-core/services/basket/basket.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; -import { LoginUser, LoginUserSuccess, SetAPIToken } from 'ish-core/store/customer/user'; -import { LoadProductIfNotLoaded, LoadProductSuccess } from 'ish-core/store/shopping/products'; +import { loginUser, loginUserSuccess, setAPIToken } from 'ish-core/store/customer/user'; +import { loadProductIfNotLoaded, loadProductSuccess } from 'ish-core/store/shopping/products'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { BasketMockData } from 'ish-core/utils/dev/basket-mock-data'; import { - LoadBasket, - LoadBasketByAPIToken, - LoadBasketEligibleShippingMethods, - LoadBasketEligibleShippingMethodsFail, - LoadBasketEligibleShippingMethodsSuccess, - LoadBasketFail, - LoadBasketSuccess, - MergeBasket, - MergeBasketFail, - MergeBasketSuccess, - ResetBasketErrors, - UpdateBasket, - UpdateBasketFail, - UpdateBasketShippingMethod, + loadBasket, + loadBasketByAPIToken, + loadBasketEligibleShippingMethods, + loadBasketEligibleShippingMethodsFail, + loadBasketEligibleShippingMethodsSuccess, + loadBasketFail, + loadBasketSuccess, + mergeBasket, + mergeBasketFail, + mergeBasketSuccess, + resetBasketErrors, + updateBasket, + updateBasketFail, + updateBasketShippingMethod, } from './basket.actions'; import { BasketEffects } from './basket.effects'; @@ -80,7 +80,7 @@ describe('Basket Effects', () => { }); it('should call the basketService for loadBasket', done => { - const action = new LoadBasket(); + const action = loadBasket(); actions$ = of(action); effects.loadBasket$.subscribe(() => { @@ -91,8 +91,8 @@ describe('Basket Effects', () => { it('should map to action of type LoadBasketSuccess', () => { const id = 'BID'; - const action = new LoadBasket(); - const completion = new LoadBasketSuccess({ basket: { id } as Basket }); + const action = loadBasket(); + const completion = loadBasketSuccess({ basket: { id } as Basket }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -101,8 +101,8 @@ describe('Basket Effects', () => { it('should map invalid request to action of type LoadBasketFail', () => { when(basketServiceMock.getBasket()).thenReturn(throwError({ message: 'invalid' })); - const action = new LoadBasket(); - const completion = new LoadBasketFail({ error: { message: 'invalid' } as HttpError }); + const action = loadBasket(); + const completion = loadBasketFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -114,7 +114,7 @@ describe('Basket Effects', () => { it('should call the basket service on LoadUserByAPIToken action and load user on success', done => { when(basketServiceMock.getBasketByToken('dummy')).thenReturn(of({ id: 'basket' } as Basket)); - actions$ = of(new LoadBasketByAPIToken({ apiToken: 'dummy' })); + actions$ = of(loadBasketByAPIToken({ apiToken: 'dummy' })); effects.loadBasketByAPIToken$.subscribe(action => { verify(basketServiceMock.getBasketByToken('dummy')).once(); @@ -129,7 +129,7 @@ describe('Basket Effects', () => { it('should call the basket service on LoadUserByAPIToken action and do nothing when failing', () => { when(basketServiceMock.getBasketByToken('dummy')).thenReturn(EMPTY); - actions$ = hot('a-a-a-', { a: new LoadBasketByAPIToken({ apiToken: 'dummy' }) }); + actions$ = hot('a-a-a-', { a: loadBasketByAPIToken({ apiToken: 'dummy' }) }); expect(effects.loadBasketByAPIToken$).toBeObservable(cold('------')); }); @@ -139,7 +139,7 @@ describe('Basket Effects', () => { it('should trigger product loading actions for line items if LoadBasketSuccess action triggered', () => { when(basketServiceMock.getBasket()).thenReturn(of()); - const action = new LoadBasketSuccess({ + const action = loadBasketSuccess({ basket: { id: 'BID', lineItems: [ @@ -156,7 +156,7 @@ describe('Basket Effects', () => { } as Basket, }); - const completion = new LoadProductIfNotLoaded({ sku: 'SKU', level: ProductCompletenessLevel.List }); + const completion = loadProductIfNotLoaded({ sku: 'SKU', level: ProductCompletenessLevel.List }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -165,7 +165,7 @@ describe('Basket Effects', () => { it('should trigger product loading actions for line items if MergeBasketSuccess action triggered', () => { when(basketServiceMock.getBasket()).thenReturn(of()); - const action = new MergeBasketSuccess({ + const action = mergeBasketSuccess({ basket: { id: 'BID', lineItems: [ @@ -182,7 +182,7 @@ describe('Basket Effects', () => { } as Basket, }); - const completion = new LoadProductIfNotLoaded({ sku: 'SKU', level: ProductCompletenessLevel.List }); + const completion = loadProductIfNotLoaded({ sku: 'SKU', level: ProductCompletenessLevel.List }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -197,7 +197,7 @@ describe('Basket Effects', () => { ); store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'BID', lineItems: [], @@ -207,7 +207,7 @@ describe('Basket Effects', () => { }); it('should call the basketService for loadBasketEligibleShippingMethods', done => { - const action = new LoadBasketEligibleShippingMethods(); + const action = loadBasketEligibleShippingMethods(); actions$ = of(action); effects.loadBasketEligibleShippingMethods$.subscribe(() => { @@ -217,8 +217,8 @@ describe('Basket Effects', () => { }); it('should map to action of type loadBasketEligibleShippingMethodsSuccess', () => { - const action = new LoadBasketEligibleShippingMethods(); - const completion = new LoadBasketEligibleShippingMethodsSuccess({ + const action = loadBasketEligibleShippingMethods(); + const completion = loadBasketEligibleShippingMethodsSuccess({ shippingMethods: [BasketMockData.getShippingMethod()], }); actions$ = hot('-a-a-a', { a: action }); @@ -231,8 +231,8 @@ describe('Basket Effects', () => { when(basketServiceMock.getBasketEligibleShippingMethods(anyString(), anything())).thenReturn( throwError({ message: 'invalid' }) ); - const action = new LoadBasketEligibleShippingMethods(); - const completion = new LoadBasketEligibleShippingMethodsFail({ + const action = loadBasketEligibleShippingMethods(); + const completion = loadBasketEligibleShippingMethodsFail({ error: { message: 'invalid', } as HttpError, @@ -249,7 +249,7 @@ describe('Basket Effects', () => { when(basketServiceMock.updateBasket(anyString(), anything())).thenReturn(of(BasketMockData.getBasket())); store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'BID', lineItems: [], @@ -261,7 +261,7 @@ describe('Basket Effects', () => { it('should call the basketService for updateBasket', done => { const basketId = 'BID'; const update = { invoiceToAddress: '7654' }; - const action = new UpdateBasket({ update }); + const action = updateBasket({ update }); actions$ = of(action); effects.updateBasket$.subscribe(() => { @@ -272,9 +272,9 @@ describe('Basket Effects', () => { it('should map to action of type LoadBasketSuccess and ResetBasketErrors', () => { const update = { commonShippingMethod: 'shippingId' }; - const action = new UpdateBasket({ update }); - const completion1 = new LoadBasketSuccess({ basket: BasketMockData.getBasket() }); - const completion2 = new ResetBasketErrors(); + const action = updateBasket({ update }); + const completion1 = loadBasketSuccess({ basket: BasketMockData.getBasket() }); + const completion2 = resetBasketErrors(); actions$ = hot('-a', { a: action }); const expected$ = cold('-(cd)', { c: completion1, d: completion2 }); @@ -285,8 +285,8 @@ describe('Basket Effects', () => { const update = { commonShippingMethod: 'shippingId' }; when(basketServiceMock.updateBasket(anyString(), anything())).thenReturn(throwError({ message: 'invalid' })); - const action = new UpdateBasket({ update }); - const completion = new UpdateBasketFail({ error: { message: 'invalid' } as HttpError }); + const action = updateBasket({ update }); + const completion = updateBasketFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -297,8 +297,8 @@ describe('Basket Effects', () => { describe('updateBasketShippingMethod$', () => { it('should trigger the updateBasket action if called', () => { const shippingId = 'shippingId'; - const action = new UpdateBasketShippingMethod({ shippingId }); - const completion = new UpdateBasket({ + const action = updateBasketShippingMethod({ shippingId }); + const completion = updateBasket({ update: { commonShippingMethod: shippingId }, }); actions$ = hot('-a-a-a', { a: action }); @@ -313,7 +313,7 @@ describe('Basket Effects', () => { when(basketServiceMock.getBaskets()).thenReturn(of([{ id: 'BIDNEW' } as BasketBaseData])); store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'BID', lineItems: [ @@ -328,10 +328,10 @@ describe('Basket Effects', () => { } as Basket, }) ); - store$.dispatch(new LoadProductSuccess({ product: { sku: 'SKU' } as Product })); + store$.dispatch(loadProductSuccess({ product: { sku: 'SKU' } as Product })); - const action = new LoginUserSuccess({ customer: {} as Customer }); - const completion = new MergeBasket(); + const action = loginUserSuccess({ customer: {} as Customer }); + const completion = mergeBasket(); actions$ = hot('-a', { a: action }); const expected$ = cold('-c', { c: completion }); @@ -347,7 +347,7 @@ describe('Basket Effects', () => { when(basketServiceMock.mergeBasket(anyString(), anyString())).thenReturn(of(BasketMockData.getBasket())); store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'BID', lineItems: [ @@ -362,13 +362,13 @@ describe('Basket Effects', () => { } as Basket, }) ); - store$.dispatch(new LoadProductSuccess({ product: { sku: 'SKU' } as Product })); - store$.dispatch(new SetAPIToken({ apiToken: sourceAuthToken })); - store$.dispatch(new LoginUser({ credentials: {} as Credentials })); + store$.dispatch(loadProductSuccess({ product: { sku: 'SKU' } as Product })); + store$.dispatch(setAPIToken({ apiToken: sourceAuthToken })); + store$.dispatch(loginUser({ credentials: {} as Credentials })); }); it('should call the basketService for mergeBasket', done => { - const action = new MergeBasket(); + const action = mergeBasket(); actions$ = of(action); effects.mergeBasket$.subscribe(() => { @@ -378,8 +378,8 @@ describe('Basket Effects', () => { }); it('should map to action of type MergeBasketSuccess', () => { - const action = new MergeBasket(); - const completion = new MergeBasketSuccess({ basket: BasketMockData.getBasket() }); + const action = mergeBasket(); + const completion = mergeBasketSuccess({ basket: BasketMockData.getBasket() }); actions$ = hot('-a', { a: action }); const expected$ = cold('-c', { c: completion }); @@ -389,8 +389,8 @@ describe('Basket Effects', () => { it('should map invalid request to action of type MergeBasketFail', () => { when(basketServiceMock.mergeBasket(anyString(), anyString())).thenReturn(throwError({ message: 'invalid' })); - const action = new MergeBasket(); - const completion = new MergeBasketFail({ error: { message: 'invalid' } as HttpError }); + const action = mergeBasket(); + const completion = mergeBasketFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a', { a: action }); const expected$ = cold('-c', { c: completion }); @@ -402,8 +402,8 @@ describe('Basket Effects', () => { it('should map to action of type LoadBasket if pre login basket is empty', () => { when(basketServiceMock.getBaskets()).thenReturn(of([{ id: 'BIDNEW' } as BasketBaseData])); - const action = new LoginUserSuccess({ customer: {} as Customer }); - const completion = new LoadBasket(); + const action = loginUserSuccess({ customer: {} as Customer }); + const completion = loadBasket(); actions$ = hot('-a', { a: action }); const expected$ = cold('-c', { c: completion }); @@ -415,8 +415,8 @@ describe('Basket Effects', () => { it('should map to action of type LoadBasket if pre login basket is empty', () => { when(basketServiceMock.getBaskets()).thenReturn(of([{ id: 'BIDNEW' } as BasketBaseData])); - const action = new LoginUserSuccess({ customer: {} as Customer }); - const completion = new LoadBasket(); + const action = loginUserSuccess({ customer: {} as Customer }); + const completion = loadBasket(); actions$ = hot('-a', { a: action }); const expected$ = cold('-c', { c: completion }); diff --git a/src/app/core/store/customer/basket/basket.effects.ts b/src/app/core/store/customer/basket/basket.effects.ts index cfc6c3b3c7..bc6d935fb9 100644 --- a/src/app/core/store/customer/basket/basket.effects.ts +++ b/src/app/core/store/customer/basket/basket.effects.ts @@ -1,30 +1,30 @@ import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { concatMap, filter, map, mapTo, mergeMap, mergeMapTo, switchMap, take, withLatestFrom } from 'rxjs/operators'; import { ProductCompletenessLevel } from 'ish-core/models/product/product.model'; import { BasketService } from 'ish-core/services/basket/basket.service'; import { ofUrl, selectQueryParam } from 'ish-core/store/core/router'; -import { UserActionTypes, getLastAPITokenBeforeLogin } from 'ish-core/store/customer/user'; -import { LoadProductIfNotLoaded } from 'ish-core/store/shopping/products'; +import { getLastAPITokenBeforeLogin, loginUserSuccess } from 'ish-core/store/customer/user'; +import { loadProductIfNotLoaded } from 'ish-core/store/shopping/products'; import { mapErrorToAction, mapToPayloadProperty, whenFalsy } from 'ish-core/utils/operators'; import { - BasketActionTypes, - LoadBasket, - LoadBasketByAPIToken, - LoadBasketEligibleShippingMethodsFail, - LoadBasketEligibleShippingMethodsSuccess, - LoadBasketFail, - LoadBasketSuccess, - MergeBasket, - MergeBasketFail, - MergeBasketSuccess, - ResetBasketErrors, - UpdateBasket, - UpdateBasketFail, - UpdateBasketShippingMethod, + loadBasket, + loadBasketByAPIToken, + loadBasketEligibleShippingMethods, + loadBasketEligibleShippingMethodsFail, + loadBasketEligibleShippingMethodsSuccess, + loadBasketFail, + loadBasketSuccess, + mergeBasket, + mergeBasketFail, + mergeBasketSuccess, + resetBasketErrors, + updateBasket, + updateBasketFail, + updateBasketShippingMethod, } from './basket.actions'; import { getCurrentBasket, getCurrentBasketId } from './basket.selectors'; @@ -35,23 +35,25 @@ export class BasketEffects { /** * The load basket effect. */ - @Effect() - loadBasket$ = this.actions$.pipe( - ofType(BasketActionTypes.LoadBasket), - mergeMap(() => - this.basketService.getBasket().pipe( - map(basket => new LoadBasketSuccess({ basket })), - mapErrorToAction(LoadBasketFail) + loadBasket$ = createEffect(() => + this.actions$.pipe( + ofType(loadBasket), + mergeMap(() => + this.basketService.getBasket().pipe( + map(basket => loadBasketSuccess({ basket })), + mapErrorToAction(loadBasketFail) + ) ) ) ); - @Effect() - loadBasketByAPIToken$ = this.actions$.pipe( - ofType(BasketActionTypes.LoadBasketByAPIToken), - mapToPayloadProperty('apiToken'), - concatMap(apiToken => - this.basketService.getBasketByToken(apiToken).pipe(map(basket => new LoadBasketSuccess({ basket }))) + loadBasketByAPIToken$ = createEffect(() => + this.actions$.pipe( + ofType(loadBasketByAPIToken), + mapToPayloadProperty('apiToken'), + concatMap(apiToken => + this.basketService.getBasketByToken(apiToken).pipe(map(basket => loadBasketSuccess({ basket }))) + ) ) ); @@ -59,28 +61,30 @@ export class BasketEffects { * After successfully loading the basket, trigger a LoadProduct action * for each product that is missing in the current product entities state. */ - @Effect() - loadProductsForBasket$ = this.actions$.pipe( - ofType(BasketActionTypes.LoadBasketSuccess, BasketActionTypes.MergeBasketSuccess), - mapToPayloadProperty('basket'), - switchMap(basket => [ - ...basket.lineItems.map( - ({ productSKU }) => new LoadProductIfNotLoaded({ sku: productSKU, level: ProductCompletenessLevel.List }) - ), - ]) + loadProductsForBasket$ = createEffect(() => + this.actions$.pipe( + ofType(loadBasketSuccess, mergeBasketSuccess), + mapToPayloadProperty('basket'), + switchMap(basket => [ + ...basket.lineItems.map(({ productSKU }) => + loadProductIfNotLoaded({ sku: productSKU, level: ProductCompletenessLevel.List }) + ), + ]) + ) ); /** * The load basket eligible shipping methods effect. */ - @Effect() - loadBasketEligibleShippingMethods$ = this.actions$.pipe( - ofType(BasketActionTypes.LoadBasketEligibleShippingMethods), - withLatestFrom(this.store.pipe(select(getCurrentBasket))), - concatMap(([, basket]) => - this.basketService.getBasketEligibleShippingMethods(basket.id, basket.bucketId).pipe( - map(result => new LoadBasketEligibleShippingMethodsSuccess({ shippingMethods: result })), - mapErrorToAction(LoadBasketEligibleShippingMethodsFail) + loadBasketEligibleShippingMethods$ = createEffect(() => + this.actions$.pipe( + ofType(loadBasketEligibleShippingMethods), + withLatestFrom(this.store.pipe(select(getCurrentBasket))), + concatMap(([, basket]) => + this.basketService.getBasketEligibleShippingMethods(basket.id, basket.bucketId).pipe( + map(result => loadBasketEligibleShippingMethodsSuccess({ shippingMethods: result })), + mapErrorToAction(loadBasketEligibleShippingMethodsFail) + ) ) ) ); @@ -88,15 +92,16 @@ export class BasketEffects { /** * Update basket effect. */ - @Effect() - updateBasket$ = this.actions$.pipe( - ofType(BasketActionTypes.UpdateBasket), - mapToPayloadProperty('update'), - withLatestFrom(this.store.pipe(select(getCurrentBasketId))), - concatMap(([update, currentBasketId]) => - this.basketService.updateBasket(currentBasketId, update).pipe( - concatMap(basket => [new LoadBasketSuccess({ basket }), new ResetBasketErrors()]), - mapErrorToAction(UpdateBasketFail) + updateBasket$ = createEffect(() => + this.actions$.pipe( + ofType(updateBasket), + mapToPayloadProperty('update'), + withLatestFrom(this.store.pipe(select(getCurrentBasketId))), + concatMap(([update, currentBasketId]) => + this.basketService.updateBasket(currentBasketId, update).pipe( + concatMap(basket => [loadBasketSuccess({ basket }), resetBasketErrors()]), + mapErrorToAction(updateBasketFail) + ) ) ) ); @@ -105,37 +110,40 @@ export class BasketEffects { * Updates the common shipping method of the basket. * Works currently only if the basket has one bucket */ - @Effect() - updateBasketShippingMethod$ = this.actions$.pipe( - ofType(BasketActionTypes.UpdateBasketShippingMethod), - mapToPayloadProperty('shippingId'), - map(commonShippingMethod => new UpdateBasket({ update: { commonShippingMethod } })) + updateBasketShippingMethod$ = createEffect(() => + this.actions$.pipe( + ofType(updateBasketShippingMethod), + mapToPayloadProperty('shippingId'), + map(commonShippingMethod => updateBasket({ update: { commonShippingMethod } })) + ) ); /** * After a user logged in a merge basket action is triggered if there are already items in the anonymous user's basket */ - @Effect() - mergeBasketAfterLogin$ = this.actions$.pipe( - ofType(UserActionTypes.LoginUserSuccess), - mergeMapTo(this.store.pipe(select(getCurrentBasket), take(1))), - filter(currentBasket => currentBasket && currentBasket.lineItems && currentBasket.lineItems.length > 0), - mapTo(new MergeBasket()) + mergeBasketAfterLogin$ = createEffect(() => + this.actions$.pipe( + ofType(loginUserSuccess), + mergeMapTo(this.store.pipe(select(getCurrentBasket), take(1))), + filter(currentBasket => currentBasket && currentBasket.lineItems && currentBasket.lineItems.length > 0), + mapTo(mergeBasket()) + ) ); /** * Merge basket into current basket of a registered user. * If the user has not yet a basket a new basket is created before the merge */ - @Effect() - mergeBasket$ = this.actions$.pipe( - ofType(BasketActionTypes.MergeBasket), - mergeMapTo(this.store.pipe(select(getCurrentBasket), take(1))), - withLatestFrom(this.store.pipe(select(getLastAPITokenBeforeLogin))), - concatMap(([sourceBasket, authToken]) => - this.basketService.mergeBasket(sourceBasket.id, authToken).pipe( - map(basket => new MergeBasketSuccess({ basket })), - mapErrorToAction(MergeBasketFail) + mergeBasket$ = createEffect(() => + this.actions$.pipe( + ofType(mergeBasket), + mergeMapTo(this.store.pipe(select(getCurrentBasket), take(1))), + withLatestFrom(this.store.pipe(select(getLastAPITokenBeforeLogin))), + concatMap(([sourceBasket, authToken]) => + this.basketService.mergeBasket(sourceBasket.id, authToken).pipe( + map(basket => mergeBasketSuccess({ basket })), + mapErrorToAction(mergeBasketFail) + ) ) ) ); @@ -143,16 +151,17 @@ export class BasketEffects { /** * Trigger LoadBasket action after LoginUserSuccess, if no pre login state basket items are present. */ - @Effect() - loadBasketAfterLogin$ = this.actions$.pipe( - ofType(UserActionTypes.LoginUserSuccess), - switchMap(() => this.basketService.getBaskets()) /* prevent 404 error by checking on existing basket */, - withLatestFrom(this.store.pipe(select(getCurrentBasket))), - filter( - ([newBaskets, currentBasket]) => - (!currentBasket || !currentBasket.lineItems || currentBasket.lineItems.length === 0) && newBaskets.length > 0 - ), - mapTo(new LoadBasket()) + loadBasketAfterLogin$ = createEffect(() => + this.actions$.pipe( + ofType(loginUserSuccess), + switchMap(() => this.basketService.getBaskets()) /* prevent 404 error by checking on existing basket */, + withLatestFrom(this.store.pipe(select(getCurrentBasket))), + filter( + ([newBaskets, currentBasket]) => + (!currentBasket || !currentBasket.lineItems || currentBasket.lineItems.length === 0) && newBaskets.length > 0 + ), + mapTo(loadBasket()) + ) ); /** @@ -160,11 +169,12 @@ export class BasketEffects { * Add queryParam error=true to the route to prevent resetting errors. * */ - @Effect() - routeListenerForResettingBasketErrors$ = this.store.pipe( - ofUrl(/^\/(basket|checkout.*)/), - select(selectQueryParam('error')), - whenFalsy(), - mapTo(new ResetBasketErrors()) + routeListenerForResettingBasketErrors$ = createEffect(() => + this.store.pipe( + ofUrl(/^\/(basket|checkout.*)/), + select(selectQueryParam('error')), + whenFalsy(), + mapTo(resetBasketErrors()) + ) ); } diff --git a/src/app/core/store/customer/basket/basket.reducer.spec.ts b/src/app/core/store/customer/basket/basket.reducer.spec.ts index d6503e84f9..eca0f754f7 100644 --- a/src/app/core/store/customer/basket/basket.reducer.spec.ts +++ b/src/app/core/store/customer/basket/basket.reducer.spec.ts @@ -8,57 +8,57 @@ import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { LineItem } from 'ish-core/models/line-item/line-item.model'; import { Order } from 'ish-core/models/order/order.model'; import { PaymentInstrument } from 'ish-core/models/payment-instrument/payment-instrument.model'; -import { CreateOrderSuccess } from 'ish-core/store/customer/orders'; +import { createOrderSuccess } from 'ish-core/store/customer/orders'; import { BasketMockData } from 'ish-core/utils/dev/basket-mock-data'; import { - AddItemsToBasketFail, - AddItemsToBasketSuccess, - AddProductToBasket, - AddPromotionCodeToBasket, - AddPromotionCodeToBasketFail, - AddPromotionCodeToBasketSuccess, - ContinueCheckout, - ContinueCheckoutFail, - ContinueCheckoutSuccess, - ContinueCheckoutWithIssues, - CreateBasketPayment, - CreateBasketPaymentFail, - CreateBasketPaymentSuccess, - DeleteBasketItem, - DeleteBasketItemFail, - DeleteBasketItemSuccess, - DeleteBasketPayment, - DeleteBasketPaymentFail, - DeleteBasketPaymentSuccess, - LoadBasket, - LoadBasketEligiblePaymentMethods, - LoadBasketEligiblePaymentMethodsFail, - LoadBasketEligiblePaymentMethodsSuccess, - LoadBasketEligibleShippingMethods, - LoadBasketEligibleShippingMethodsFail, - LoadBasketEligibleShippingMethodsSuccess, - LoadBasketFail, - LoadBasketSuccess, - MergeBasket, - MergeBasketFail, - MergeBasketSuccess, - RemovePromotionCodeFromBasket, - RemovePromotionCodeFromBasketFail, - RemovePromotionCodeFromBasketSuccess, - ResetBasketErrors, - SetBasketPayment, - SetBasketPaymentFail, - SetBasketPaymentSuccess, - UpdateBasket, - UpdateBasketFail, - UpdateBasketItems, - UpdateBasketItemsFail, - UpdateBasketItemsSuccess, - UpdateBasketPayment, - UpdateBasketPaymentFail, - UpdateBasketPaymentSuccess, - UpdateBasketShippingMethod, + addItemsToBasketFail, + addItemsToBasketSuccess, + addProductToBasket, + addPromotionCodeToBasket, + addPromotionCodeToBasketFail, + addPromotionCodeToBasketSuccess, + continueCheckout, + continueCheckoutFail, + continueCheckoutSuccess, + continueCheckoutWithIssues, + createBasketPayment, + createBasketPaymentFail, + createBasketPaymentSuccess, + deleteBasketItem, + deleteBasketItemFail, + deleteBasketItemSuccess, + deleteBasketPayment, + deleteBasketPaymentFail, + deleteBasketPaymentSuccess, + loadBasket, + loadBasketEligiblePaymentMethods, + loadBasketEligiblePaymentMethodsFail, + loadBasketEligiblePaymentMethodsSuccess, + loadBasketEligibleShippingMethods, + loadBasketEligibleShippingMethodsFail, + loadBasketEligibleShippingMethodsSuccess, + loadBasketFail, + loadBasketSuccess, + mergeBasket, + mergeBasketFail, + mergeBasketSuccess, + removePromotionCodeFromBasket, + removePromotionCodeFromBasketFail, + removePromotionCodeFromBasketSuccess, + resetBasketErrors, + setBasketPayment, + setBasketPaymentFail, + setBasketPaymentSuccess, + updateBasket, + updateBasketFail, + updateBasketItems, + updateBasketItemsFail, + updateBasketItemsSuccess, + updateBasketPayment, + updateBasketPaymentFail, + updateBasketPaymentSuccess, + updateBasketShippingMethod, } from './basket.actions'; import { basketReducer, initialState } from './basket.reducer'; @@ -66,7 +66,7 @@ describe('Basket Reducer', () => { describe('LoadBasket actions', () => { describe('LoadBasket action', () => { it('should set loading to true', () => { - const action = new LoadBasket(); + const action = loadBasket(); const state = basketReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -76,7 +76,7 @@ describe('Basket Reducer', () => { describe('LoadBasketFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new LoadBasketFail({ error }); + const action = loadBasketFail({ error }); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -90,7 +90,7 @@ describe('Basket Reducer', () => { id: 'test', } as Basket; - const action = new LoadBasketSuccess({ basket }); + const action = loadBasketSuccess({ basket }); const state = basketReducer(initialState, action); expect(state.basket).toEqual(basket); @@ -102,7 +102,7 @@ describe('Basket Reducer', () => { describe('MergeBasket actions', () => { describe('MergeBasket action', () => { it('should set loading to true', () => { - const action = new MergeBasket(); + const action = mergeBasket(); const state = basketReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -115,7 +115,7 @@ describe('Basket Reducer', () => { id: 'test', } as Basket; - const action = new MergeBasketSuccess({ basket }); + const action = mergeBasketSuccess({ basket }); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -126,7 +126,7 @@ describe('Basket Reducer', () => { describe('MergeBasketFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new MergeBasketFail({ error }); + const action = mergeBasketFail({ error }); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -138,7 +138,7 @@ describe('Basket Reducer', () => { describe('UpdateBasket actions', () => { describe('UpdateBasket action', () => { it('should set loading to true', () => { - const action = new UpdateBasket({ update: { invoiceToAddress: '1234' } }); + const action = updateBasket({ update: { invoiceToAddress: '1234' } }); const state = basketReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -147,7 +147,7 @@ describe('Basket Reducer', () => { describe('UpdateBasketShippingMethod action', () => { it('should set loading to true', () => { - const action = new UpdateBasketShippingMethod({ shippingId: '1234' }); + const action = updateBasketShippingMethod({ shippingId: '1234' }); const state = basketReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -157,7 +157,7 @@ describe('Basket Reducer', () => { describe('UpdateBasketFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new UpdateBasketFail({ error }); + const action = updateBasketFail({ error }); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -176,7 +176,7 @@ describe('Basket Reducer', () => { }; describe('ContinueCheckout action', () => { it('should set loading to true', () => { - const action = new ContinueCheckout({ targetStep: 1 }); + const action = continueCheckout({ targetStep: 1 }); const state = basketReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -186,7 +186,7 @@ describe('Basket Reducer', () => { describe('ContinueCheckoutSuccess action', () => { it('should save validationResults when called', () => { - const action = new ContinueCheckoutSuccess({ targetRoute: '/checkout/address', basketValidation }); + const action = continueCheckoutSuccess({ targetRoute: '/checkout/address', basketValidation }); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -196,7 +196,7 @@ describe('Basket Reducer', () => { describe('ContinueCheckoutWithIssues action', () => { it('should save validationResults when called', () => { - const action = new ContinueCheckoutWithIssues({ + const action = continueCheckoutWithIssues({ targetRoute: '/checkout/address', basketValidation, }); @@ -210,7 +210,7 @@ describe('Basket Reducer', () => { describe('ContinueCheckoutFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new ContinueCheckoutFail({ error }); + const action = continueCheckoutFail({ error }); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -222,7 +222,7 @@ describe('Basket Reducer', () => { describe('AddItemsToBasket actions', () => { describe('AddProductToBasket action', () => { it('should set loading to true', () => { - const action = new AddProductToBasket({ sku: 'test', quantity: 1 }); + const action = addProductToBasket({ sku: 'test', quantity: 1 }); const state = basketReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -233,7 +233,7 @@ describe('Basket Reducer', () => { describe('AddItemsToBasketFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new AddItemsToBasketFail({ error }); + const action = addItemsToBasketFail({ error }); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -243,7 +243,7 @@ describe('Basket Reducer', () => { describe('AddItemsToBasketSuccess action', () => { it('should set loading to false', () => { - const action = new AddItemsToBasketSuccess({ info: [{ message: 'info' } as BasketInfo] }); + const action = addItemsToBasketSuccess({ info: [{ message: 'info' } as BasketInfo] }); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -257,7 +257,7 @@ describe('Basket Reducer', () => { describe('UpdateBasketItems actions', () => { describe('UpdateBasketItems action', () => { it('should set loading to true', () => { - const action = new UpdateBasketItems({ + const action = updateBasketItems({ lineItemUpdates: [ { quantity: 2, @@ -274,7 +274,7 @@ describe('Basket Reducer', () => { describe('UpdateBasketItemsFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new UpdateBasketItemsFail({ error }); + const action = updateBasketItemsFail({ error }); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -284,7 +284,7 @@ describe('Basket Reducer', () => { describe('UpdateBasketItemsSuccess action', () => { it('should set loading to false', () => { - const action = new UpdateBasketItemsSuccess({ info: [{ message: 'info' } as BasketInfo] }); + const action = updateBasketItemsSuccess({ info: [{ message: 'info' } as BasketInfo] }); const state = basketReducer(initialState, action); expect(state.info[0].message).toEqual('info'); @@ -297,7 +297,7 @@ describe('Basket Reducer', () => { describe('DeleteBasketItem actions', () => { describe('DeleteBasketItem action', () => { it('should set loading to true', () => { - const action = new DeleteBasketItem({ itemId: 'test' }); + const action = deleteBasketItem({ itemId: 'test' }); const state = basketReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -307,7 +307,7 @@ describe('Basket Reducer', () => { describe('DeleteBasketItemFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new DeleteBasketItemFail({ error }); + const action = deleteBasketItemFail({ error }); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -317,7 +317,7 @@ describe('Basket Reducer', () => { describe('DeleteBasketItemSuccess action', () => { it('should set loading to false', () => { - const action = new DeleteBasketItemSuccess({ info: [{ message: 'info' } as BasketInfo] }); + const action = deleteBasketItemSuccess({ info: [{ message: 'info' } as BasketInfo] }); const state = basketReducer(initialState, action); expect(state.info[0].message).toEqual('info'); @@ -329,7 +329,7 @@ describe('Basket Reducer', () => { describe('AddPromotionCodeToBasket actions', () => { describe('AddPromotionCodeToBasket action', () => { it('should set loading to true', () => { - const action = new AddPromotionCodeToBasket({ code: 'test' }); + const action = addPromotionCodeToBasket({ code: 'test' }); const state = basketReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -339,7 +339,7 @@ describe('Basket Reducer', () => { describe('AddPromotionCodeToBasketFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new AddPromotionCodeToBasketFail({ error }); + const action = addPromotionCodeToBasketFail({ error }); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -349,7 +349,7 @@ describe('Basket Reducer', () => { describe('AddPromotionCodeToBasketSuccess action', () => { it('should set loading to false', () => { - const action = new AddPromotionCodeToBasketSuccess(); + const action = addPromotionCodeToBasketSuccess(); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -361,7 +361,7 @@ describe('Basket Reducer', () => { describe('RemovePromotionCodeFromBasket actions', () => { describe('RemovePromotionCodeFromBasket action', () => { it('should set loading to true', () => { - const action = new RemovePromotionCodeFromBasket({ code: 'test' }); + const action = removePromotionCodeFromBasket({ code: 'test' }); const state = basketReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -371,7 +371,7 @@ describe('Basket Reducer', () => { describe('RemovePromotionCodeFromBasketFail action', () => { it('should set loading to false', () => { const error = undefined as HttpError; - const action = new RemovePromotionCodeFromBasketFail({ error }); + const action = removePromotionCodeFromBasketFail({ error }); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -381,7 +381,7 @@ describe('Basket Reducer', () => { describe('RemovePromotionCodeFromBasketSuccess action', () => { it('should set loading to false', () => { - const action = new RemovePromotionCodeFromBasketSuccess(); + const action = removePromotionCodeFromBasketSuccess(); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -394,7 +394,7 @@ describe('Basket Reducer', () => { describe('LoadBasketEligibleShippingMethods actions', () => { describe('LoadBasketEligibleShippingMethods action', () => { it('should set loading to true', () => { - const action = new LoadBasketEligibleShippingMethods(); + const action = loadBasketEligibleShippingMethods(); const state = basketReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -404,7 +404,7 @@ describe('Basket Reducer', () => { describe('LoadBasketEligibleShippingMethodsFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new LoadBasketEligibleShippingMethodsFail({ error }); + const action = loadBasketEligibleShippingMethodsFail({ error }); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -419,8 +419,8 @@ describe('Basket Reducer', () => { } as Basket; const shippingMethods = [BasketMockData.getShippingMethod()]; - const basketAction = new LoadBasketSuccess({ basket }); - const basketShippingAction = new LoadBasketEligibleShippingMethodsSuccess({ shippingMethods }); + const basketAction = loadBasketSuccess({ basket }); + const basketShippingAction = loadBasketEligibleShippingMethodsSuccess({ shippingMethods }); let state = basketReducer(initialState, basketAction); state = basketReducer(state, basketShippingAction); @@ -434,7 +434,7 @@ describe('Basket Reducer', () => { describe('LoadBasketEligiblePaymentMethods actions', () => { describe('LoadBasketEligiblePaymentMethods action', () => { it('should set loading to true', () => { - const action = new LoadBasketEligiblePaymentMethods(); + const action = loadBasketEligiblePaymentMethods(); const state = basketReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -444,7 +444,7 @@ describe('Basket Reducer', () => { describe('LoadBasketEligiblePaymentMethodsFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new LoadBasketEligiblePaymentMethodsFail({ error }); + const action = loadBasketEligiblePaymentMethodsFail({ error }); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -459,8 +459,8 @@ describe('Basket Reducer', () => { } as Basket; const paymentMethods = [BasketMockData.getPaymentMethod()]; - const basketAction = new LoadBasketSuccess({ basket }); - const basketPaymentAction = new LoadBasketEligiblePaymentMethodsSuccess({ paymentMethods }); + const basketAction = loadBasketSuccess({ basket }); + const basketPaymentAction = loadBasketEligiblePaymentMethodsSuccess({ paymentMethods }); let state = basketReducer(initialState, basketAction); state = basketReducer(state, basketPaymentAction); @@ -474,7 +474,7 @@ describe('Basket Reducer', () => { describe('SetBasketPayment actions', () => { describe('SetBasketPayment action', () => { it('should set loading to true', () => { - const action = new SetBasketPayment({ id: 'testPayment' }); + const action = setBasketPayment({ id: 'testPayment' }); const state = basketReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -484,7 +484,7 @@ describe('Basket Reducer', () => { describe('SetBasketPaymentFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new SetBasketPaymentFail({ error }); + const action = setBasketPaymentFail({ error }); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -494,7 +494,7 @@ describe('Basket Reducer', () => { describe('SetBasketPaymentSuccess action', () => { it('should set loading to false', () => { - const action = new SetBasketPaymentSuccess(); + const action = setBasketPaymentSuccess(); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -520,7 +520,7 @@ describe('Basket Reducer', () => { }; describe('CreateBasketPayment action', () => { it('should set loading to true', () => { - const action = new CreateBasketPayment({ paymentInstrument, saveForLater: false }); + const action = createBasketPayment({ paymentInstrument, saveForLater: false }); const state = basketReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -530,7 +530,7 @@ describe('Basket Reducer', () => { describe('CreateBasketPaymentFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new CreateBasketPaymentFail({ error }); + const action = createBasketPaymentFail({ error }); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -540,7 +540,7 @@ describe('Basket Reducer', () => { describe('CreateBasketPaymentSuccess action', () => { it('should set loading to false', () => { - const action = new CreateBasketPaymentSuccess(); + const action = createBasketPaymentSuccess(); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -558,7 +558,7 @@ describe('Basket Reducer', () => { describe('UpdateBasketPayment action', () => { it('should set loading to true', () => { - const action = new UpdateBasketPayment({ params }); + const action = updateBasketPayment({ params }); const state = basketReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -568,7 +568,7 @@ describe('Basket Reducer', () => { describe('UpdateBasketPaymentFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new UpdateBasketPaymentFail({ error }); + const action = updateBasketPaymentFail({ error }); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -578,7 +578,7 @@ describe('Basket Reducer', () => { describe('UpdateBasketPaymentSuccess action', () => { it('should set loading to false', () => { - const action = new UpdateBasketPaymentSuccess(); + const action = updateBasketPaymentSuccess(); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -590,7 +590,7 @@ describe('Basket Reducer', () => { describe('DeleteBasketPayment actions', () => { describe('DeleteBasketPayment action', () => { it('should set loading to true', () => { - const action = new DeleteBasketPayment({ + const action = deleteBasketPayment({ paymentInstrument: { id: '12345', paymentMethod: 'ISH_DirectDebit', @@ -615,7 +615,7 @@ describe('Basket Reducer', () => { describe('DeleteBasketPaymentFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new DeleteBasketPaymentFail({ error }); + const action = deleteBasketPaymentFail({ error }); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -625,7 +625,7 @@ describe('Basket Reducer', () => { describe('DeleteBasketPaymentSuccess action', () => { it('should set loading to false', () => { - const action = new DeleteBasketPaymentSuccess(); + const action = deleteBasketPaymentSuccess(); const state = basketReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -641,7 +641,7 @@ describe('Basket Reducer', () => { loading: true, lineItems: [{ id: 'test' } as LineItem], }; - const action = new CreateOrderSuccess({ order: { id: '123' } as Order }); + const action = createOrderSuccess({ order: { id: '123' } as Order }); const state = basketReducer(oldState, action); expect(state).toEqual(initialState); @@ -654,7 +654,7 @@ describe('Basket Reducer', () => { ...initialState, error: { message: 'invalid' } as HttpError, }; - const action = new ResetBasketErrors(); + const action = resetBasketErrors(); const state = basketReducer(oldState, action); expect(state.error).toBeUndefined(); @@ -665,7 +665,7 @@ describe('Basket Reducer', () => { ...initialState, promotionError: { message: 'invalid' } as HttpError, }; - const action = new ResetBasketErrors(); + const action = resetBasketErrors(); const state = basketReducer(oldState, action); expect(state.promotionError).toBeUndefined(); @@ -676,7 +676,7 @@ describe('Basket Reducer', () => { ...initialState, validationResults: { errors: [{ message: 'errorMessage' }] } as BasketValidationResultType, }; - const action = new ResetBasketErrors(); + const action = resetBasketErrors(); const state = basketReducer(oldState, action); expect(state.validationResults.errors).toHaveLength(0); diff --git a/src/app/core/store/customer/basket/basket.reducer.ts b/src/app/core/store/customer/basket/basket.reducer.ts index e60f93dbc6..0b7334e271 100644 --- a/src/app/core/store/customer/basket/basket.reducer.ts +++ b/src/app/core/store/customer/basket/basket.reducer.ts @@ -1,12 +1,65 @@ +import { createReducer, on } from '@ngrx/store'; + import { BasketInfo } from 'ish-core/models/basket-info/basket-info.model'; import { BasketValidationResultType } from 'ish-core/models/basket-validation/basket-validation.model'; import { Basket } from 'ish-core/models/basket/basket.model'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { PaymentMethod } from 'ish-core/models/payment-method/payment-method.model'; import { ShippingMethod } from 'ish-core/models/shipping-method/shipping-method.model'; -import { OrdersAction, OrdersActionTypes } from 'ish-core/store/customer/orders'; - -import { BasketAction, BasketActionTypes } from './basket.actions'; +import { createOrderSuccess } from 'ish-core/store/customer/orders'; +import { setErrorOn, setLoadingOn } from 'ish-core/utils/ngrx-creators'; + +import { + addItemsToBasket, + addItemsToBasketFail, + addItemsToBasketSuccess, + addProductToBasket, + addPromotionCodeToBasket, + addPromotionCodeToBasketFail, + addPromotionCodeToBasketSuccess, + assignBasketAddress, + continueCheckout, + continueCheckoutFail, + continueCheckoutSuccess, + continueCheckoutWithIssues, + createBasketPayment, + createBasketPaymentFail, + createBasketPaymentSuccess, + deleteBasketItem, + deleteBasketItemFail, + deleteBasketItemSuccess, + deleteBasketPayment, + deleteBasketPaymentFail, + deleteBasketPaymentSuccess, + loadBasket, + loadBasketEligiblePaymentMethods, + loadBasketEligiblePaymentMethodsFail, + loadBasketEligiblePaymentMethodsSuccess, + loadBasketEligibleShippingMethods, + loadBasketEligibleShippingMethodsFail, + loadBasketEligibleShippingMethodsSuccess, + loadBasketFail, + loadBasketSuccess, + mergeBasket, + mergeBasketFail, + mergeBasketSuccess, + removePromotionCodeFromBasket, + removePromotionCodeFromBasketFail, + removePromotionCodeFromBasketSuccess, + resetBasketErrors, + setBasketPayment, + setBasketPaymentFail, + setBasketPaymentSuccess, + updateBasket, + updateBasketFail, + updateBasketItems, + updateBasketItemsFail, + updateBasketItemsSuccess, + updateBasketPayment, + updateBasketPaymentFail, + updateBasketPaymentSuccess, + updateBasketShippingMethod, +} from './basket.actions'; export interface BasketState { basket: Basket; @@ -38,167 +91,128 @@ export const initialState: BasketState = { validationResults: initialValidationResults, }; -export function basketReducer(state = initialState, action: BasketAction | OrdersAction): BasketState { - switch (action.type) { - case BasketActionTypes.LoadBasket: - case BasketActionTypes.AssignBasketAddress: - case BasketActionTypes.UpdateBasketShippingMethod: - case BasketActionTypes.UpdateBasket: - case BasketActionTypes.AddProductToBasket: - case BasketActionTypes.AddPromotionCodeToBasket: - case BasketActionTypes.RemovePromotionCodeFromBasket: - case BasketActionTypes.AddItemsToBasket: - case BasketActionTypes.MergeBasket: - case BasketActionTypes.ContinueCheckout: - case BasketActionTypes.UpdateBasketItems: - case BasketActionTypes.DeleteBasketItem: - case BasketActionTypes.LoadBasketEligibleShippingMethods: - case BasketActionTypes.LoadBasketEligiblePaymentMethods: - case BasketActionTypes.SetBasketPayment: - case BasketActionTypes.CreateBasketPayment: - case BasketActionTypes.UpdateBasketPayment: - case BasketActionTypes.DeleteBasketPayment: { - return { - ...state, - loading: true, - }; - } - - case BasketActionTypes.MergeBasketFail: - case BasketActionTypes.LoadBasketFail: - case BasketActionTypes.UpdateBasketFail: - case BasketActionTypes.ContinueCheckoutFail: - case BasketActionTypes.AddItemsToBasketFail: - case BasketActionTypes.RemovePromotionCodeFromBasketFail: - case BasketActionTypes.UpdateBasketItemsFail: - case BasketActionTypes.DeleteBasketItemFail: - case BasketActionTypes.LoadBasketEligibleShippingMethodsFail: - case BasketActionTypes.LoadBasketEligiblePaymentMethodsFail: - case BasketActionTypes.SetBasketPaymentFail: - case BasketActionTypes.CreateBasketPaymentFail: - case BasketActionTypes.UpdateBasketPaymentFail: - case BasketActionTypes.DeleteBasketPaymentFail: { - const { error } = action.payload; - - return { - ...state, - error, - loading: false, - }; - } - - case BasketActionTypes.AddPromotionCodeToBasketFail: { - const { error } = action.payload; - - return { - ...state, - promotionError: error, - loading: false, - }; - } - - case BasketActionTypes.AddPromotionCodeToBasketSuccess: { - return { - ...state, - loading: false, - promotionError: undefined, - }; - } - - case BasketActionTypes.UpdateBasketItemsSuccess: - case BasketActionTypes.DeleteBasketItemSuccess: { - return { - ...state, - loading: false, - error: undefined, - info: action.payload.info, - validationResults: initialValidationResults, - }; - } - - case BasketActionTypes.RemovePromotionCodeFromBasketSuccess: - case BasketActionTypes.SetBasketPaymentSuccess: - case BasketActionTypes.CreateBasketPaymentSuccess: - case BasketActionTypes.UpdateBasketPaymentSuccess: - case BasketActionTypes.DeleteBasketPaymentSuccess: { - return { - ...state, - loading: false, - error: undefined, - validationResults: initialValidationResults, - }; - } - - case BasketActionTypes.AddItemsToBasketSuccess: { - return { - ...state, - loading: false, - error: undefined, - info: action.payload.info, - lastTimeProductAdded: new Date().getTime(), - }; - } - - case BasketActionTypes.MergeBasketSuccess: - case BasketActionTypes.LoadBasketSuccess: { - const basket = { - ...action.payload.basket, - }; - - return { - ...state, - basket, - loading: false, - error: undefined, - }; - } - - case BasketActionTypes.ContinueCheckoutSuccess: - case BasketActionTypes.ContinueCheckoutWithIssues: { - const validation = action.payload.basketValidation; - const basket = validation && validation.results.adjusted && validation.basket ? validation.basket : state.basket; - - return { - ...state, - basket, - loading: false, - error: undefined, - info: undefined, - validationResults: validation && validation.results, - }; - } - - case BasketActionTypes.LoadBasketEligibleShippingMethodsSuccess: { - return { - ...state, - eligibleShippingMethods: action.payload.shippingMethods, - loading: false, - error: undefined, - }; - } - - case BasketActionTypes.LoadBasketEligiblePaymentMethodsSuccess: { - return { - ...state, - eligiblePaymentMethods: action.payload.paymentMethods, - loading: false, - error: undefined, - }; - } - - case OrdersActionTypes.CreateOrderSuccess: { - return initialState; - } - - case BasketActionTypes.ResetBasketErrors: { - return { - ...state, - error: undefined, - info: undefined, - promotionError: undefined, - validationResults: initialValidationResults, - }; - } - } - return state; -} +export const basketReducer = createReducer( + initialState, + setLoadingOn( + loadBasket, + assignBasketAddress, + updateBasketShippingMethod, + updateBasket, + addProductToBasket, + addPromotionCodeToBasket, + removePromotionCodeFromBasket, + addItemsToBasket, + mergeBasket, + continueCheckout, + updateBasketItems, + deleteBasketItem, + loadBasketEligibleShippingMethods, + loadBasketEligiblePaymentMethods, + setBasketPayment, + createBasketPayment, + updateBasketPayment, + deleteBasketPayment + ), + setErrorOn( + mergeBasketFail, + loadBasketFail, + updateBasketFail, + continueCheckoutFail, + addItemsToBasketFail, + removePromotionCodeFromBasketFail, + updateBasketItemsFail, + deleteBasketItemFail, + loadBasketEligibleShippingMethodsFail, + loadBasketEligiblePaymentMethodsFail, + setBasketPaymentFail, + createBasketPaymentFail, + updateBasketPaymentFail, + deleteBasketPaymentFail + ), + on(addPromotionCodeToBasketFail, (state: BasketState, action) => { + const { error } = action.payload; + + return { + ...state, + promotionError: error, + loading: false, + }; + }), + on(addPromotionCodeToBasketSuccess, (state: BasketState) => ({ + ...state, + loading: false, + promotionError: undefined, + })), + on(updateBasketItemsSuccess, deleteBasketItemSuccess, (state: BasketState, action) => ({ + ...state, + loading: false, + error: undefined, + info: action.payload.info, + validationResults: initialValidationResults, + })), + on( + removePromotionCodeFromBasketSuccess, + setBasketPaymentSuccess, + createBasketPaymentSuccess, + updateBasketPaymentSuccess, + deleteBasketPaymentSuccess, + (state: BasketState) => ({ + ...state, + loading: false, + error: undefined, + validationResults: initialValidationResults, + }) + ), + on(addItemsToBasketSuccess, (state: BasketState, action) => ({ + ...state, + loading: false, + error: undefined, + info: action.payload.info, + lastTimeProductAdded: new Date().getTime(), + })), + on(mergeBasketSuccess, loadBasketSuccess, (state: BasketState, action) => { + const basket = { + ...action.payload.basket, + }; + + return { + ...state, + basket, + loading: false, + error: undefined, + }; + }), + on(continueCheckoutSuccess, continueCheckoutWithIssues, (state: BasketState, action) => { + const validation = action.payload.basketValidation; + const basket = validation && validation.results.adjusted && validation.basket ? validation.basket : state.basket; + + return { + ...state, + basket, + loading: false, + error: undefined, + info: undefined, + validationResults: validation && validation.results, + }; + }), + on(loadBasketEligibleShippingMethodsSuccess, (state: BasketState, action) => ({ + ...state, + eligibleShippingMethods: action.payload.shippingMethods, + loading: false, + error: undefined, + })), + on(loadBasketEligiblePaymentMethodsSuccess, (state: BasketState, action) => ({ + ...state, + eligiblePaymentMethods: action.payload.paymentMethods, + loading: false, + error: undefined, + })), + on(createOrderSuccess, () => initialState), + on(resetBasketErrors, (state: BasketState) => ({ + ...state, + error: undefined, + info: undefined, + promotionError: undefined, + validationResults: initialValidationResults, + })) +); diff --git a/src/app/core/store/customer/basket/basket.selectors.spec.ts b/src/app/core/store/customer/basket/basket.selectors.spec.ts index 494fee5c7a..b3540022cd 100644 --- a/src/app/core/store/customer/basket/basket.selectors.spec.ts +++ b/src/app/core/store/customer/basket/basket.selectors.spec.ts @@ -9,25 +9,25 @@ import { LineItem } from 'ish-core/models/line-item/line-item.model'; import { Product, ProductCompletenessLevel } from 'ish-core/models/product/product.model'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; -import { LoginUserSuccess } from 'ish-core/store/customer/user'; -import { LoadProductSuccess } from 'ish-core/store/shopping/products'; +import { loginUserSuccess } from 'ish-core/store/customer/user'; +import { loadProductSuccess } from 'ish-core/store/shopping/products'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { BasketMockData } from 'ish-core/utils/dev/basket-mock-data'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; import { - AddItemsToBasketSuccess, - AddPromotionCodeToBasketFail, - ContinueCheckoutSuccess, - LoadBasket, - LoadBasketEligiblePaymentMethods, - LoadBasketEligiblePaymentMethodsFail, - LoadBasketEligiblePaymentMethodsSuccess, - LoadBasketEligibleShippingMethods, - LoadBasketEligibleShippingMethodsFail, - LoadBasketEligibleShippingMethodsSuccess, - LoadBasketFail, - LoadBasketSuccess, + addItemsToBasketSuccess, + addPromotionCodeToBasketFail, + continueCheckoutSuccess, + loadBasket, + loadBasketEligiblePaymentMethods, + loadBasketEligiblePaymentMethodsFail, + loadBasketEligiblePaymentMethodsSuccess, + loadBasketEligibleShippingMethods, + loadBasketEligibleShippingMethodsFail, + loadBasketEligibleShippingMethodsSuccess, + loadBasketFail, + loadBasketSuccess, } from './basket.actions'; import { getBasketEligiblePaymentMethods, @@ -82,9 +82,9 @@ describe('Basket Selectors', () => { describe('loading a basket', () => { beforeEach(() => { - store$.dispatch(new LoadBasket()); + store$.dispatch(loadBasket()); store$.dispatch( - new LoadProductSuccess({ + loadProductSuccess({ product: { sku: 'sku', completenessLevel: ProductCompletenessLevel.Detail } as Product, }) ); @@ -96,7 +96,7 @@ describe('Basket Selectors', () => { it('should set loading to false and set basket state', () => { store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'test', lineItems: [{ id: 'test', productSKU: 'sku', quantity: { value: 5 } } as LineItem], @@ -119,7 +119,7 @@ describe('Basket Selectors', () => { it('should change the product of the basket line item if the product is changing', () => { store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'test', lineItems: [{ id: 'test', productSKU: 'sku' } as LineItem] } as Basket, }) ); @@ -127,7 +127,7 @@ describe('Basket Selectors', () => { expect(currentBasket.lineItems[0].product).toHaveProperty('sku', 'sku'); store$.dispatch( - new LoadProductSuccess({ + loadProductSuccess({ product: { sku: 'sku', name: 'new name', completenessLevel: ProductCompletenessLevel.Detail } as Product, }) ); @@ -138,12 +138,12 @@ describe('Basket Selectors', () => { it('should set validation results to the lineitem if basket is not valid', () => { store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'test', lineItems: [{ id: 'test', productSKU: 'sku' } as LineItem] } as Basket, }) ); store$.dispatch( - new ContinueCheckoutSuccess({ + continueCheckoutSuccess({ targetRoute: '/checkout/address', basketValidation: { results: { @@ -161,7 +161,7 @@ describe('Basket Selectors', () => { }); it('should set loading to false and set error state', () => { - store$.dispatch(new LoadBasketFail({ error: { message: 'invalid' } as HttpError })); + store$.dispatch(loadBasketFail({ error: { message: 'invalid' } as HttpError })); expect(getBasketLoading(store$.state)).toBeFalse(); expect(getCurrentBasket(store$.state)).toBeUndefined(); expect(getCurrentBasketId(store$.state)).toBeUndefined(); @@ -171,7 +171,7 @@ describe('Basket Selectors', () => { describe('loading eligible shipping methods', () => { beforeEach(() => { - store$.dispatch(new LoadBasketEligibleShippingMethods()); + store$.dispatch(loadBasketEligibleShippingMethods()); }); it('should set the state to loading', () => { @@ -181,7 +181,7 @@ describe('Basket Selectors', () => { describe('and reporting success', () => { beforeEach(() => { store$.dispatch( - new LoadBasketEligibleShippingMethodsSuccess({ shippingMethods: [BasketMockData.getShippingMethod()] }) + loadBasketEligibleShippingMethodsSuccess({ shippingMethods: [BasketMockData.getShippingMethod()] }) ); }); @@ -193,7 +193,7 @@ describe('Basket Selectors', () => { describe('and reporting failure', () => { beforeEach(() => { - store$.dispatch(new LoadBasketEligibleShippingMethodsFail({ error: { message: 'error' } as HttpError })); + store$.dispatch(loadBasketEligibleShippingMethodsFail({ error: { message: 'error' } as HttpError })); }); it('should not have loaded shipping methods on error', () => { @@ -206,7 +206,7 @@ describe('Basket Selectors', () => { describe('loading eligible payment methods', () => { beforeEach(() => { - store$.dispatch(new LoadBasketEligiblePaymentMethods()); + store$.dispatch(loadBasketEligiblePaymentMethods()); }); it('should set the state to loading', () => { @@ -216,12 +216,12 @@ describe('Basket Selectors', () => { describe('and reporting success', () => { beforeEach(() => { store$.dispatch( - new LoadBasketEligiblePaymentMethodsSuccess({ paymentMethods: [BasketMockData.getPaymentMethod()] }) + loadBasketEligiblePaymentMethodsSuccess({ paymentMethods: [BasketMockData.getPaymentMethod()] }) ); }); it('should set load data when user is logged in', () => { - store$.dispatch(new LoginUserSuccess({ customer: {} as Customer })); + store$.dispatch(loginUserSuccess({ customer: {} as Customer })); expect(getBasketLoading(store$.state)).toBeFalse(); expect(getBasketEligiblePaymentMethods(store$.state)).toEqual([BasketMockData.getPaymentMethod()]); }); @@ -236,7 +236,7 @@ describe('Basket Selectors', () => { describe('and reporting failure', () => { beforeEach(() => { - store$.dispatch(new LoadBasketEligiblePaymentMethodsFail({ error: { message: 'error' } as HttpError })); + store$.dispatch(loadBasketEligiblePaymentMethodsFail({ error: { message: 'error' } as HttpError })); }); it('should not have loaded payment methods on error', () => { @@ -249,14 +249,14 @@ describe('Basket Selectors', () => { describe('loading last time and info when a product has been added to basket', () => { beforeEach(() => { - store$.dispatch(new AddItemsToBasketSuccess({ info: [{ message: 'info' } as BasketInfo] })); + store$.dispatch(addItemsToBasketSuccess({ info: [{ message: 'info' } as BasketInfo] })); }); it('should get the last time when a product was added', () => { const firstTimeAdded = new Date(getBasketLastTimeProductAdded(store$.state)); expect(firstTimeAdded).toBeDate(); - store$.dispatch(new AddItemsToBasketSuccess({ info: undefined })); + store$.dispatch(addItemsToBasketSuccess({ info: undefined })); expect(getBasketLastTimeProductAdded(store$.state)).not.toEqual(firstTimeAdded); }); @@ -267,7 +267,7 @@ describe('Basket Selectors', () => { describe('loading promotion error after adding a wrong promotion code', () => { beforeEach(() => { - store$.dispatch(new AddPromotionCodeToBasketFail({ error: { message: 'error' } as HttpError })); + store$.dispatch(addPromotionCodeToBasketFail({ error: { message: 'error' } as HttpError })); }); it('should reporting the failure in case of an error', () => { @@ -293,8 +293,8 @@ describe('Basket Selectors', () => { }, }; beforeEach(() => { - store$.dispatch(new LoadBasketSuccess({ basket: BasketMockData.getBasket() })); - store$.dispatch(new ContinueCheckoutSuccess({ targetRoute: '/checkout/address', basketValidation })); + store$.dispatch(loadBasketSuccess({ basket: BasketMockData.getBasket() })); + store$.dispatch(continueCheckoutSuccess({ targetRoute: '/checkout/address', basketValidation })); }); it('should reporting the validation results when called', () => { diff --git a/src/app/core/store/customer/customer-store.spec.ts b/src/app/core/store/customer/customer-store.spec.ts index 7f6ee8ee79..437b304c06 100644 --- a/src/app/core/store/customer/customer-store.spec.ts +++ b/src/app/core/store/customer/customer-store.spec.ts @@ -37,14 +37,14 @@ import { SuggestService } from 'ish-core/services/suggest/suggest.service'; import { UserService } from 'ish-core/services/user/user.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; -import { LoginUser } from 'ish-core/store/customer/user'; +import { loginUser } from 'ish-core/store/customer/user'; import { UserEffects } from 'ish-core/store/customer/user/user.effects'; -import { LoadProductSuccess } from 'ish-core/store/shopping/products'; +import { loadProductSuccess } from 'ish-core/store/shopping/products'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; import { categoryTree } from 'ish-core/utils/dev/test-data-utils'; -import { AddProductToBasket, LoadBasketSuccess } from './basket'; +import { addProductToBasket, loadBasketSuccess } from './basket'; describe('Customer Store', () => { let store: StoreWithSnapshots; @@ -203,11 +203,11 @@ describe('Customer Store', () => { describe('with anonymous user', () => { beforeEach(() => { store.dispatch( - new LoadProductSuccess({ + loadProductSuccess({ product: { sku: 'test', packingUnit: 'pcs.', completenessLevel: ProductCompletenessLevel.List } as Product, }) ); - store.dispatch(new AddProductToBasket({ sku: 'test', quantity: 1 })); + store.dispatch(addProductToBasket({ sku: 'test', quantity: 1 })); }); describe('and without basket', () => { @@ -252,10 +252,10 @@ describe('Customer Store', () => { describe('and with basket', () => { it('should merge basket on user login.', () => { - store.dispatch(new LoadBasketSuccess({ basket })); + store.dispatch(loadBasketSuccess({ basket })); store.reset(); - store.dispatch(new LoginUser({ credentials: {} as Credentials })); + store.dispatch(loginUser({ credentials: {} as Credentials })); expect(store.actionsArray()).toMatchInlineSnapshot(` [Account] Login User: diff --git a/src/app/core/store/customer/orders/orders.actions.ts b/src/app/core/store/customer/orders/orders.actions.ts index 2ca0416971..7b6783f7d3 100644 --- a/src/app/core/store/customer/orders/orders.actions.ts +++ b/src/app/core/store/customer/orders/orders.actions.ts @@ -1,100 +1,40 @@ import { Params } from '@angular/router'; -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; -import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { Order } from 'ish-core/models/order/order.model'; +import { httpError, payload } from 'ish-core/utils/ngrx-creators'; -export enum OrdersActionTypes { - CreateOrder = '[Order] Create Order', - CreateOrderFail = '[Order API] Create Order Fail', - CreateOrderSuccess = '[Order API] Create Order Success', - SelectOrder = '[Order] Select Order', - LoadOrders = '[Order] Load Orders', - LoadOrdersFail = '[Order API] Load Orders Fail', - LoadOrdersSuccess = '[Order API] Load Orders Success', - LoadOrder = '[Order] Load Order', - LoadOrderByAPIToken = '[Order Internal] Load Order using given API Token', - LoadOrderFail = '[Order API] Load Order Fail', - LoadOrderSuccess = '[Order API] Load Order Success', - SelectOrderAfterRedirect = '[Order Internal] Select Order After Checkout Redirect', - SelectOrderAfterRedirectFail = '[Order API] Select Order Fail After Checkout Redirect', -} +export const createOrder = createAction('[Order] Create Order', payload<{ basketId: string }>()); -export class CreateOrder implements Action { - readonly type = OrdersActionTypes.CreateOrder; - constructor(public payload: { basketId: string }) {} -} +export const createOrderFail = createAction('[Order API] Create Order Fail', httpError()); -export class CreateOrderFail implements Action { - readonly type = OrdersActionTypes.CreateOrderFail; - constructor(public payload: { error: HttpError }) {} -} +export const createOrderSuccess = createAction('[Order API] Create Order Success', payload<{ order: Order }>()); -export class CreateOrderSuccess implements Action { - readonly type = OrdersActionTypes.CreateOrderSuccess; - constructor(public payload: { order: Order }) {} -} +export const loadOrders = createAction('[Order] Load Orders'); -export class LoadOrders implements Action { - readonly type = OrdersActionTypes.LoadOrders; -} +export const loadOrdersFail = createAction('[Order API] Load Orders Fail', httpError()); -export class LoadOrdersFail implements Action { - readonly type = OrdersActionTypes.LoadOrdersFail; - constructor(public payload: { error: HttpError }) {} -} +export const loadOrdersSuccess = createAction('[Order API] Load Orders Success', payload<{ orders: Order[] }>()); -export class LoadOrdersSuccess implements Action { - readonly type = OrdersActionTypes.LoadOrdersSuccess; - constructor(public payload: { orders: Order[] }) {} -} +export const loadOrder = createAction('[Order] Load Order', payload<{ orderId: string }>()); -export class LoadOrder implements Action { - readonly type = OrdersActionTypes.LoadOrder; - constructor(public payload: { orderId: string }) {} -} +export const loadOrderByAPIToken = createAction( + '[Order Internal] Load Order using given API Token', + payload<{ apiToken: string; orderId: string }>() +); -export class LoadOrderByAPIToken implements Action { - readonly type = OrdersActionTypes.LoadOrderByAPIToken; - constructor(public payload: { apiToken: string; orderId: string }) {} -} +export const loadOrderFail = createAction('[Order API] Load Order Fail', httpError()); -export class LoadOrderFail implements Action { - readonly type = OrdersActionTypes.LoadOrderFail; - constructor(public payload: { error: HttpError }) {} -} +export const loadOrderSuccess = createAction('[Order API] Load Order Success', payload<{ order: Order }>()); -export class LoadOrderSuccess implements Action { - readonly type = OrdersActionTypes.LoadOrderSuccess; - constructor(public payload: { order: Order }) {} -} +export const selectOrder = createAction('[Order] Select Order', payload<{ orderId: string }>()); -export class SelectOrder implements Action { - readonly type = OrdersActionTypes.SelectOrder; - constructor(public payload: { orderId: string }) {} -} +export const selectOrderAfterRedirect = createAction( + '[Order Internal] Select Order After Checkout Redirect', + payload<{ params: Params }>() +); -export class SelectOrderAfterRedirect implements Action { - readonly type = OrdersActionTypes.SelectOrderAfterRedirect; - constructor(public payload: { params: Params }) {} // query params -} - -export class SelectOrderAfterRedirectFail implements Action { - readonly type = OrdersActionTypes.SelectOrderAfterRedirectFail; - constructor(public payload: { error: HttpError }) {} -} - -export type OrdersAction = - | CreateOrder - | CreateOrderFail - | CreateOrderSuccess - | LoadOrders - | LoadOrdersFail - | LoadOrdersSuccess - | LoadOrder - | LoadOrderByAPIToken - | LoadOrderFail - | LoadOrderSuccess - | SelectOrder - | SelectOrderAfterRedirect - | SelectOrderAfterRedirectFail; +export const selectOrderAfterRedirectFail = createAction( + '[Order API] Select Order Fail After Checkout Redirect', + httpError() +); diff --git a/src/app/core/store/customer/orders/orders.effects.spec.ts b/src/app/core/store/customer/orders/orders.effects.spec.ts index d5d872cbaa..41c4d15eff 100644 --- a/src/app/core/store/customer/orders/orders.effects.spec.ts +++ b/src/app/core/store/customer/orders/orders.effects.spec.ts @@ -17,26 +17,26 @@ import { Order } from 'ish-core/models/order/order.model'; import { User } from 'ish-core/models/user/user.model'; import { OrderService } from 'ish-core/services/order/order.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; -import { ContinueCheckoutWithIssues, LoadBasket } from 'ish-core/store/customer/basket'; +import { continueCheckoutWithIssues, loadBasket } from 'ish-core/store/customer/basket'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; -import { LoginUserSuccess } from 'ish-core/store/customer/user'; +import { loginUserSuccess } from 'ish-core/store/customer/user'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { BasketMockData } from 'ish-core/utils/dev/basket-mock-data'; import { - CreateOrder, - CreateOrderFail, - CreateOrderSuccess, - LoadOrder, - LoadOrderByAPIToken, - LoadOrderFail, - LoadOrderSuccess, - LoadOrders, - LoadOrdersFail, - LoadOrdersSuccess, - SelectOrder, - SelectOrderAfterRedirect, - SelectOrderAfterRedirectFail, + createOrder, + createOrderFail, + createOrderSuccess, + loadOrder, + loadOrderByAPIToken, + loadOrderFail, + loadOrderSuccess, + loadOrders, + loadOrdersFail, + loadOrdersSuccess, + selectOrder, + selectOrderAfterRedirect, + selectOrderAfterRedirectFail, } from './orders.actions'; import { OrdersEffects } from './orders.effects'; @@ -96,7 +96,7 @@ describe('Orders Effects', () => { it('should call the orderService for createOrder', done => { when(orderServiceMock.createOrder(anything(), anything())).thenReturn(of(undefined)); const payload = BasketMockData.getBasket().id; - const action = new CreateOrder({ basketId: payload }); + const action = createOrder({ basketId: payload }); actions$ = of(action); effects.createOrder$.subscribe(() => { @@ -111,8 +111,8 @@ describe('Orders Effects', () => { ); const basketId = BasketMockData.getBasket().id; const newOrder = { id: basketId } as Order; - const action = new CreateOrder({ basketId }); - const completion = new CreateOrderSuccess({ order: newOrder }); + const action = createOrder({ basketId }); + const completion = createOrderSuccess({ order: newOrder }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -122,8 +122,8 @@ describe('Orders Effects', () => { it('should map an invalid request to action of type CreateOrderFail', () => { when(orderServiceMock.createOrder(anything(), anything())).thenReturn(throwError({ message: 'invalid' })); const basketId = BasketMockData.getBasket().id; - const action = new CreateOrder({ basketId }); - const completion = new CreateOrderFail({ error: { message: 'invalid' } as HttpError }); + const action = createOrder({ basketId }); + const completion = createOrderFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -133,7 +133,7 @@ describe('Orders Effects', () => { describe('continueAfterOrderCreation', () => { it('should navigate to /checkout/receipt after CreateOrderSuccess if there is no redirect required', fakeAsync(() => { - const action = new CreateOrderSuccess({ order: { id: '123' } as Order }); + const action = createOrderSuccess({ order: { id: '123' } as Order }); actions$ = of(action); effects.continueAfterOrderCreation$.subscribe(noop, fail, noop); @@ -150,7 +150,7 @@ describe('Orders Effects', () => { writable: true, }); - const action = new CreateOrderSuccess({ + const action = createOrderSuccess({ order: { id: '123', orderCreation: { status: 'STOPPED', stopAction: { type: 'Redirect', redirectUrl: 'http://test' } }, @@ -168,7 +168,7 @@ describe('Orders Effects', () => { describe('rollbackAfterOrderCreation', () => { it('should navigate to /checkout/payment after CreateOrderSuccess if order creation was rolled back', () => { - const action = new CreateOrderSuccess({ + const action = createOrderSuccess({ order: { id: '123', orderCreation: { status: 'ROLLED_BACK' }, @@ -177,8 +177,8 @@ describe('Orders Effects', () => { }); actions$ = of(action); - const completion1 = new LoadBasket(); - const completion2 = new ContinueCheckoutWithIssues({ + const completion1 = loadBasket(); + const completion2 = continueCheckoutWithIssues({ targetRoute: undefined, basketValidation: { basket: undefined, @@ -198,7 +198,7 @@ describe('Orders Effects', () => { describe('loadOrders$', () => { it('should call the orderService for loadOrders', done => { - const action = new LoadOrders(); + const action = loadOrders(); actions$ = of(action); effects.loadOrders$.subscribe(() => { @@ -208,8 +208,8 @@ describe('Orders Effects', () => { }); it('should load all orders of a user and dispatch a LoadOrdersSuccess action', () => { - const action = new LoadOrders(); - const completion = new LoadOrdersSuccess({ orders }); + const action = loadOrders(); + const completion = loadOrdersSuccess({ orders }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -219,8 +219,8 @@ describe('Orders Effects', () => { it('should dispatch a LoadOrdersFail action if a load error occurs', () => { when(orderServiceMock.getOrders()).thenReturn(throwError({ message: 'error' })); - const action = new LoadOrders(); - const completion = new LoadOrdersFail({ error: { message: 'error' } as HttpError }); + const action = loadOrders(); + const completion = loadOrdersFail({ error: { message: 'error' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -230,7 +230,7 @@ describe('Orders Effects', () => { describe('loadOrder$', () => { it('should call the orderService for loadOrder', done => { - const action = new LoadOrder({ orderId: order.id }); + const action = loadOrder({ orderId: order.id }); actions$ = of(action); effects.loadOrder$.subscribe(() => { @@ -240,8 +240,8 @@ describe('Orders Effects', () => { }); it('should load an order of a user and dispatch a LoadOrderSuccess action', () => { - const action = new LoadOrder({ orderId: order.id }); - const completion = new LoadOrderSuccess({ order }); + const action = loadOrder({ orderId: order.id }); + const completion = loadOrderSuccess({ order }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -251,8 +251,8 @@ describe('Orders Effects', () => { it('should dispatch a LoadOrderFail action if a load error occurs', () => { when(orderServiceMock.getOrder(anyString())).thenReturn(throwError({ message: 'error' })); - const action = new LoadOrder({ orderId: order.id }); - const completion = new LoadOrderFail({ error: { message: 'error' } as HttpError }); + const action = loadOrder({ orderId: order.id }); + const completion = loadOrderFail({ error: { message: 'error' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -262,7 +262,7 @@ describe('Orders Effects', () => { describe('loadOrderByApiToken$', () => { it('should call the orderService for LoadOrderByAPIToken', done => { - const action = new LoadOrderByAPIToken({ apiToken: 'dummy', orderId: order.id }); + const action = loadOrderByAPIToken({ apiToken: 'dummy', orderId: order.id }); actions$ = of(action); effects.loadOrderByAPIToken$.subscribe(() => { @@ -272,8 +272,8 @@ describe('Orders Effects', () => { }); it('should load an order of a user and dispatch a LoadOrderSuccess action', () => { - const action = new LoadOrderByAPIToken({ apiToken: 'dummy', orderId: order.id }); - const completion = new LoadOrderSuccess({ order }); + const action = loadOrderByAPIToken({ apiToken: 'dummy', orderId: order.id }); + const completion = loadOrderSuccess({ order }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -283,8 +283,8 @@ describe('Orders Effects', () => { it('should dispatch a LoadOrderFail action if a load error occurs', () => { when(orderServiceMock.getOrderByToken(anyString(), anyString())).thenReturn(throwError({ message: 'error' })); - const action = new LoadOrderByAPIToken({ apiToken: 'dummy', orderId: order.id }); - const completion = new LoadOrderFail({ error: { message: 'error' } as HttpError }); + const action = loadOrderByAPIToken({ apiToken: 'dummy', orderId: order.id }); + const completion = loadOrderFail({ error: { message: 'error' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -295,8 +295,8 @@ describe('Orders Effects', () => { describe('loadOrderForSelectedOrder$', () => { it('should fire LoadOrder if an order is selected that is not yet loaded', () => { const orderId = '123'; - const action = new SelectOrder({ orderId }); - const completion = new LoadOrder({ orderId }); + const action = selectOrder({ orderId }); + const completion = loadOrder({ orderId }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -340,7 +340,7 @@ describe('Orders Effects', () => { it('should trigger SelectOrderAfterRedirect action if checkout payment/receipt page is called with query param "redirect" and a user is logged in', done => { const customer = { customerNo: 'patricia' } as Customer; const user = { firstName: 'patricia' } as User; - store$.dispatch(new LoginUserSuccess({ customer, user })); + store$.dispatch(loginUserSuccess({ customer, user })); router.navigate(['checkout', 'receipt'], { queryParams: { redirect: 'success', param1: 123, orderId: order.id }, @@ -356,7 +356,7 @@ describe('Orders Effects', () => { }); it('should trigger SelectOrderAfterRedirect action if checkout payment/receipt page is called with query param "redirect" and an order is available', done => { - store$.dispatch(new CreateOrderSuccess({ order })); + store$.dispatch(createOrderSuccess({ order })); router.navigate(['checkout', 'receipt'], { queryParams: { redirect: 'success', param1: 123, orderId: order.id }, @@ -377,7 +377,7 @@ describe('Orders Effects', () => { when(orderServiceMock.updateOrderPayment(order.id, anything())).thenReturn(of(undefined)); const params = { redirect: 'success', param1: 123, orderId: order.id }; - const action = new SelectOrderAfterRedirect({ params }); + const action = selectOrderAfterRedirect({ params }); actions$ = of(action); effects.selectOrderAfterRedirect$.subscribe(() => { @@ -390,8 +390,8 @@ describe('Orders Effects', () => { when(orderServiceMock.updateOrderPayment(order.id, anything())).thenReturn(of(order.id)); const params = { redirect: 'success', param1: 123, orderId: order.id }; - const action = new SelectOrderAfterRedirect({ params }); - const completion = new SelectOrder({ orderId: order.id }); + const action = selectOrderAfterRedirect({ params }); + const completion = selectOrder({ orderId: order.id }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -402,8 +402,8 @@ describe('Orders Effects', () => { when(orderServiceMock.updateOrderPayment(order.id, anything())).thenReturn(of(order.id)); const params = { redirect: 'cancel', param1: 123, orderId: order.id }; - const action = new SelectOrderAfterRedirect({ params }); - const completion = new LoadBasket(); + const action = selectOrderAfterRedirect({ params }); + const completion = loadBasket(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -414,8 +414,8 @@ describe('Orders Effects', () => { when(orderServiceMock.updateOrderPayment(order.id, anything())).thenReturn(throwError({ message: 'invalid' })); const params = { redirect: 'success', param1: 123, orderId: order.id }; - const action = new SelectOrderAfterRedirect({ params }); - const completion = new SelectOrderAfterRedirectFail({ error: { message: 'invalid' } as HttpError }); + const action = selectOrderAfterRedirect({ params }); + const completion = selectOrderAfterRedirectFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -425,7 +425,7 @@ describe('Orders Effects', () => { describe('selectOrderAfterRedirectFailed', () => { it('should navigate to /checkout/payment if order creation failed after redirect', fakeAsync(() => { - const action = new SelectOrderAfterRedirectFail(undefined); + const action = selectOrderAfterRedirectFail(undefined); actions$ = of(action); effects.selectOrderAfterRedirectFailed$.subscribe(noop, fail, noop); @@ -436,8 +436,8 @@ describe('Orders Effects', () => { })); it('should map to action of type LoadBasket', () => { - const action = new SelectOrderAfterRedirectFail(undefined); - const completion = new LoadBasket(); + const action = selectOrderAfterRedirectFail(undefined); + const completion = loadBasket(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -447,8 +447,8 @@ describe('Orders Effects', () => { describe('setOrderBreadcrumb$', () => { beforeEach(() => { - store$.dispatch(new LoadOrdersSuccess({ orders })); - store$.dispatch(new SelectOrder({ orderId: orders[0].id })); + store$.dispatch(loadOrdersSuccess({ orders })); + store$.dispatch(selectOrder({ orderId: orders[0].id })); }); it('should set the breadcrumb of the selected order', done => { diff --git a/src/app/core/store/customer/orders/orders.effects.ts b/src/app/core/store/customer/orders/orders.effects.ts index e6bf45366c..dbc43d9d41 100644 --- a/src/app/core/store/customer/orders/orders.effects.ts +++ b/src/app/core/store/customer/orders/orders.effects.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; import { race } from 'rxjs'; @@ -20,26 +20,26 @@ import { import { ProductCompletenessLevel } from 'ish-core/models/product/product.model'; import { OrderService } from 'ish-core/services/order/order.service'; import { ofUrl, selectQueryParams, selectRouteParam } from 'ish-core/store/core/router'; -import { SetBreadcrumbData } from 'ish-core/store/core/viewconf'; -import { ContinueCheckoutWithIssues, LoadBasket } from 'ish-core/store/customer/basket'; +import { setBreadcrumbData } from 'ish-core/store/core/viewconf'; +import { continueCheckoutWithIssues, loadBasket } from 'ish-core/store/customer/basket'; import { getLoggedInUser } from 'ish-core/store/customer/user'; -import { LoadProductIfNotLoaded } from 'ish-core/store/shopping/products'; +import { loadProductIfNotLoaded } from 'ish-core/store/shopping/products'; import { mapErrorToAction, mapToPayload, mapToPayloadProperty, whenTruthy } from 'ish-core/utils/operators'; import { - CreateOrder, - CreateOrderFail, - CreateOrderSuccess, - LoadOrder, - LoadOrderByAPIToken, - LoadOrderFail, - LoadOrderSuccess, - LoadOrdersFail, - LoadOrdersSuccess, - OrdersActionTypes, - SelectOrder, - SelectOrderAfterRedirect, - SelectOrderAfterRedirectFail, + createOrder, + createOrderFail, + createOrderSuccess, + loadOrder, + loadOrderByAPIToken, + loadOrderFail, + loadOrderSuccess, + loadOrders, + loadOrdersFail, + loadOrdersSuccess, + selectOrder, + selectOrderAfterRedirect, + selectOrderAfterRedirectFail, } from './orders.actions'; import { getOrder, getSelectedOrder, getSelectedOrderId } from './orders.selectors'; @@ -56,14 +56,15 @@ export class OrdersEffects { /** * Creates an order based on the given basket. */ - @Effect() - createOrder$ = this.actions$.pipe( - ofType(OrdersActionTypes.CreateOrder), - mapToPayloadProperty('basketId'), - mergeMap(basketId => - this.orderService.createOrder(basketId, true).pipe( - map(order => new CreateOrderSuccess({ order })), - mapErrorToAction(CreateOrderFail) + createOrder$ = createEffect(() => + this.actions$.pipe( + ofType(createOrder), + mapToPayloadProperty('basketId'), + mergeMap(basketId => + this.orderService.createOrder(basketId, true).pipe( + map(order => createOrderSuccess({ order })), + mapErrorToAction(createOrderFail) + ) ) ) ); @@ -71,66 +72,72 @@ export class OrdersEffects { /** * After order creation either redirect to a payment provider or show checkout receipt page. */ - @Effect({ dispatch: false }) - continueAfterOrderCreation$ = this.actions$.pipe( - ofType(OrdersActionTypes.CreateOrderSuccess), - mapToPayloadProperty('order'), - filter(order => !order || !order.orderCreation || order.orderCreation.status !== 'ROLLED_BACK'), - tap(order => { - if ( - order.orderCreation && - order.orderCreation.status === 'STOPPED' && - order.orderCreation.stopAction.type === 'Redirect' && - order.orderCreation.stopAction.redirectUrl - ) { - location.assign(order.orderCreation.stopAction.redirectUrl); - } else { - this.router.navigate(['/checkout/receipt']); - } - }) + continueAfterOrderCreation$ = createEffect( + () => + this.actions$.pipe( + ofType(createOrderSuccess), + mapToPayloadProperty('order'), + filter(order => !order || !order.orderCreation || order.orderCreation.status !== 'ROLLED_BACK'), + tap(order => { + if ( + order.orderCreation && + order.orderCreation.status === 'STOPPED' && + order.orderCreation.stopAction.type === 'Redirect' && + order.orderCreation.stopAction.redirectUrl + ) { + location.assign(order.orderCreation.stopAction.redirectUrl); + } else { + this.router.navigate(['/checkout/receipt']); + } + }) + ), + { dispatch: false } ); - @Effect() - rollbackAfterOrderCreation$ = this.actions$.pipe( - ofType(OrdersActionTypes.CreateOrderSuccess), - mapToPayloadProperty('order'), - filter(order => order.orderCreation && order.orderCreation.status === 'ROLLED_BACK'), - tap(() => this.router.navigate(['/checkout/payment'], { queryParams: { error: true } })), - concatMap(order => [ - new LoadBasket(), - new ContinueCheckoutWithIssues({ - targetRoute: undefined, - basketValidation: { - basket: undefined, - results: { - valid: false, - adjusted: false, - errors: order.infos, + rollbackAfterOrderCreation$ = createEffect(() => + this.actions$.pipe( + ofType(createOrderSuccess), + mapToPayloadProperty('order'), + filter(order => order.orderCreation && order.orderCreation.status === 'ROLLED_BACK'), + tap(() => this.router.navigate(['/checkout/payment'], { queryParams: { error: true } })), + concatMap(order => [ + loadBasket(), + continueCheckoutWithIssues({ + targetRoute: undefined, + basketValidation: { + basket: undefined, + results: { + valid: false, + adjusted: false, + errors: order.infos, + }, }, - }, - }), - ]) + }), + ]) + ) ); - @Effect() - loadOrders$ = this.actions$.pipe( - ofType(OrdersActionTypes.LoadOrders), - concatMap(() => - this.orderService.getOrders().pipe( - map(orders => new LoadOrdersSuccess({ orders })), - mapErrorToAction(LoadOrdersFail) + loadOrders$ = createEffect(() => + this.actions$.pipe( + ofType(loadOrders), + concatMap(() => + this.orderService.getOrders().pipe( + map(orders => loadOrdersSuccess({ orders })), + mapErrorToAction(loadOrdersFail) + ) ) ) ); - @Effect() - loadOrder$ = this.actions$.pipe( - ofType(OrdersActionTypes.LoadOrder), - mapToPayloadProperty('orderId'), - concatMap(orderId => - this.orderService.getOrder(orderId).pipe( - map(order => new LoadOrderSuccess({ order })), - mapErrorToAction(LoadOrderFail) + loadOrder$ = createEffect(() => + this.actions$.pipe( + ofType(loadOrder), + mapToPayloadProperty('orderId'), + concatMap(orderId => + this.orderService.getOrder(orderId).pipe( + map(order => loadOrderSuccess({ order })), + mapErrorToAction(loadOrderFail) + ) ) ) ); @@ -138,14 +145,15 @@ export class OrdersEffects { /** * Loads an anonymous user`s order using the given api token and orderId. */ - @Effect() - loadOrderByAPIToken$ = this.actions$.pipe( - ofType(OrdersActionTypes.LoadOrderByAPIToken), - mapToPayload(), - concatMap(payload => - this.orderService.getOrderByToken(payload.orderId, payload.apiToken).pipe( - map(order => new LoadOrderSuccess({ order })), - mapErrorToAction(LoadOrderFail) + loadOrderByAPIToken$ = createEffect(() => + this.actions$.pipe( + ofType(loadOrderByAPIToken), + mapToPayload(), + concatMap(payload => + this.orderService.getOrderByToken(payload.orderId, payload.apiToken).pipe( + map(order => loadOrderSuccess({ order })), + mapErrorToAction(loadOrderFail) + ) ) ) ); @@ -153,56 +161,60 @@ export class OrdersEffects { /** * Selects and loads an order. */ - @Effect() - loadOrderForSelectedOrder$ = this.actions$.pipe( - ofType(OrdersActionTypes.SelectOrder), - mapToPayloadProperty('orderId'), - whenTruthy(), - map(orderId => new LoadOrder({ orderId })) + loadOrderForSelectedOrder$ = createEffect(() => + this.actions$.pipe( + ofType(selectOrder), + mapToPayloadProperty('orderId'), + whenTruthy(), + map(orderId => loadOrder({ orderId })) + ) ); /** * Triggers a SelectOrder action if route contains orderId parameter ( for order detail page ). */ - @Effect() - routeListenerForSelectingOrder$ = this.store.pipe( - ofUrl(/^\/(account\/orders.*|checkout\/receipt)/), - select(selectRouteParam('orderId')), - withLatestFrom(this.store.pipe(select(getSelectedOrderId))), - filter(([fromAction, selectedOrderId]) => fromAction && fromAction !== selectedOrderId), - map(([orderId]) => new SelectOrder({ orderId })) + routeListenerForSelectingOrder$ = createEffect(() => + this.store.pipe( + ofUrl(/^\/(account\/orders.*|checkout\/receipt)/), + select(selectRouteParam('orderId')), + withLatestFrom(this.store.pipe(select(getSelectedOrderId))), + filter(([fromAction, selectedOrderId]) => fromAction && fromAction !== selectedOrderId), + map(([orderId]) => selectOrder({ orderId })) + ) ); /** * After selecting and successfully loading an order, triggers a LoadProduct action * for each product that is missing in the current product entities state. */ - @Effect() - loadProductsForSelectedOrder$ = this.actions$.pipe( - ofType(OrdersActionTypes.LoadOrderSuccess), - mapToPayloadProperty('order'), - switchMap(order => [ - ...order.lineItems.map( - ({ productSKU }) => new LoadProductIfNotLoaded({ sku: productSKU, level: ProductCompletenessLevel.List }) - ), - ]) + loadProductsForSelectedOrder$ = createEffect(() => + this.actions$.pipe( + ofType(loadOrderSuccess), + mapToPayloadProperty('order'), + switchMap(order => [ + ...order.lineItems.map(({ productSKU }) => + loadProductIfNotLoaded({ sku: productSKU, level: ProductCompletenessLevel.List }) + ), + ]) + ) ); /** * Returning from redirect after checkout (before customer is logged in). * Waits until the customer is logged in and triggers the handleOrderAfterRedirect action afterwards. */ - @Effect() - returnFromRedirectAfterOrderCreation$ = this.store.pipe( - ofUrl(/^\/checkout\/(receipt|payment)/), - select(selectQueryParams), - filter(({ redirect, orderId }) => redirect && orderId), - switchMap(queryParams => - // SelectOrderAfterRedirect will be triggered either after a user is logged in or after the paid order is loaded (anonymous user) - race([ - this.store.pipe(select(getLoggedInUser), whenTruthy(), take(1)), - this.store.pipe(select(getOrder, { orderId: queryParams.orderId }), whenTruthy(), take(1)), - ]).pipe(mapTo(new SelectOrderAfterRedirect({ params: queryParams }))) + returnFromRedirectAfterOrderCreation$ = createEffect(() => + this.store.pipe( + ofUrl(/^\/checkout\/(receipt|payment)/), + select(selectQueryParams), + filter(({ redirect, orderId }) => redirect && orderId), + switchMap(queryParams => + // SelectOrderAfterRedirect will be triggered either after a user is logged in or after the paid order is loaded (anonymous user) + race([ + this.store.pipe(select(getLoggedInUser), whenTruthy(), take(1)), + this.store.pipe(select(getOrder, { orderId: queryParams.orderId }), whenTruthy(), take(1)), + ]).pipe(mapTo(selectOrderAfterRedirect({ params: queryParams }))) + ) ) ); @@ -210,49 +222,51 @@ export class OrdersEffects { * Returning from redirect after checkout success case (after customer is logged in). * Sends success state with payment query params to the server and selects/loads order. */ - @Effect() - selectOrderAfterRedirect$ = this.actions$.pipe( - ofType(OrdersActionTypes.SelectOrderAfterRedirect), - mapToPayloadProperty('params'), - concatMap(params => - this.orderService.updateOrderPayment(params.orderId, params).pipe( - map(orderId => { - if (params.redirect === 'success') { - return new SelectOrder({ orderId }); - } else { - return new LoadBasket(); - } - }), - mapErrorToAction(SelectOrderAfterRedirectFail) // ToDo: display error message on receipt page + selectOrderAfterRedirect$ = createEffect(() => + this.actions$.pipe( + ofType(selectOrderAfterRedirect), + mapToPayloadProperty('params'), + concatMap(params => + this.orderService.updateOrderPayment(params.orderId, params).pipe( + map(orderId => { + if (params.redirect === 'success') { + return selectOrder({ orderId }); + } else { + return loadBasket(); + } + }), + mapErrorToAction(selectOrderAfterRedirectFail) // ToDo: display error message on receipt page + ) ) ) ); - @Effect() - selectOrderAfterRedirectFailed$ = this.actions$.pipe( - ofType(OrdersActionTypes.SelectOrderAfterRedirectFail), - tap(() => - this.router.navigate(['/checkout/payment'], { - queryParams: { redirect: 'failure' }, - }) - ), - mapTo(new LoadBasket()) + selectOrderAfterRedirectFailed$ = createEffect(() => + this.actions$.pipe( + ofType(selectOrderAfterRedirectFail), + tap(() => + this.router.navigate(['/checkout/payment'], { + queryParams: { redirect: 'failure' }, + }) + ), + mapTo(loadBasket()) + ) ); - @Effect() - setOrderBreadcrumb$ = this.store.pipe( - select(getSelectedOrder), - whenTruthy(), - debounceTime(0), - withLatestFrom(this.translateService.get('account.orderdetails.breadcrumb')), - map( - ([order, x]) => - new SetBreadcrumbData({ + setOrderBreadcrumb$ = createEffect(() => + this.store.pipe( + select(getSelectedOrder), + whenTruthy(), + debounceTime(0), + withLatestFrom(this.translateService.get('account.orderdetails.breadcrumb')), + map(([order, x]) => + setBreadcrumbData({ breadcrumbData: [ { key: 'account.order_history.link', link: '/account/orders' }, { text: `${x} - ${order.documentNo}` }, ], }) + ) ) ); } diff --git a/src/app/core/store/customer/orders/orders.reducer.spec.ts b/src/app/core/store/customer/orders/orders.reducer.spec.ts index fd21bf34e5..5713f42d44 100644 --- a/src/app/core/store/customer/orders/orders.reducer.spec.ts +++ b/src/app/core/store/customer/orders/orders.reducer.spec.ts @@ -3,23 +3,40 @@ import { Order } from 'ish-core/models/order/order.model'; import { BasketMockData } from 'ish-core/utils/dev/basket-mock-data'; import { - CreateOrder, - CreateOrderFail, - CreateOrderSuccess, - LoadOrder, - LoadOrderSuccess, - LoadOrders, - LoadOrdersFail, - LoadOrdersSuccess, - OrdersAction, - SelectOrder, + createOrder, + createOrderFail, + createOrderSuccess, + loadOrder, + loadOrderByAPIToken, + loadOrderFail, + loadOrderSuccess, + loadOrders, + loadOrdersFail, + loadOrdersSuccess, + selectOrder, + selectOrderAfterRedirect, + selectOrderAfterRedirectFail, } from './orders.actions'; import { initialState, ordersReducer } from './orders.reducer'; describe('Orders Reducer', () => { describe('undefined action', () => { it('should return the default state when previous state is undefined', () => { - const action = {} as OrdersAction; + const action = {} as ReturnType< + | typeof createOrder + | typeof createOrderFail + | typeof createOrderSuccess + | typeof loadOrders + | typeof loadOrdersFail + | typeof loadOrdersSuccess + | typeof loadOrder + | typeof loadOrderByAPIToken + | typeof loadOrderFail + | typeof loadOrderSuccess + | typeof selectOrder + | typeof selectOrderAfterRedirect + | typeof selectOrderAfterRedirectFail + >; const state = ordersReducer(undefined, action); expect(state).toBe(initialState); @@ -29,7 +46,7 @@ describe('Orders Reducer', () => { describe('CreateOrder actions', () => { describe('CreateOrder action', () => { it('should set loading to true', () => { - const action = new CreateOrder({ basketId: BasketMockData.getBasket().id }); + const action = createOrder({ basketId: BasketMockData.getBasket().id }); const state = ordersReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -39,7 +56,7 @@ describe('Orders Reducer', () => { describe('CreateOrderSuccess action', () => { it('should add new order to initial state and select it', () => { const order = { id: 'orderid' } as Order; - const action = new CreateOrderSuccess({ order }); + const action = createOrderSuccess({ order }); const state = ordersReducer(initialState, action); expect(state.entities[order.id]).toEqual(order); @@ -50,7 +67,7 @@ describe('Orders Reducer', () => { describe('CreateOrderFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new CreateOrderFail({ error }); + const action = createOrderFail({ error }); const state = ordersReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -62,7 +79,7 @@ describe('Orders Reducer', () => { describe('LoadOrders actions', () => { describe('LoadOrders action', () => { it('should set loading to true', () => { - const action = new LoadOrders(); + const action = loadOrders(); const state = ordersReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -72,7 +89,7 @@ describe('Orders Reducer', () => { describe('LoadOrdersFail action', () => { it('should set loading to false', () => { - const action = new LoadOrdersFail({ error: {} as HttpError }); + const action = loadOrdersFail({ error: {} as HttpError }); const state = ordersReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -97,7 +114,7 @@ describe('Orders Reducer', () => { }); it('should insert orders if not exist', () => { - const action = new LoadOrdersSuccess({ orders }); + const action = loadOrdersSuccess({ orders }); const state = ordersReducer(initialState, action); expect(state.ids).toHaveLength(2); @@ -109,7 +126,7 @@ describe('Orders Reducer', () => { describe('LoadOrder actions', () => { describe('LoadOrder action', () => { it('should set loading to true', () => { - const action = new LoadOrder({ orderId: '12345' }); + const action = loadOrder({ orderId: '12345' }); const state = ordersReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -119,7 +136,7 @@ describe('Orders Reducer', () => { describe('LoadOrderFail action', () => { it('should set loading to false', () => { - const action = new LoadOrdersFail({ error: {} as HttpError }); + const action = loadOrdersFail({ error: {} as HttpError }); const state = ordersReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -138,7 +155,7 @@ describe('Orders Reducer', () => { }); it('should insert order if not exist', () => { - const action = new LoadOrderSuccess({ order }); + const action = loadOrderSuccess({ order }); const state = ordersReducer(initialState, action); expect(state.ids).toHaveLength(1); @@ -150,7 +167,7 @@ describe('Orders Reducer', () => { describe('SelectOrder action', () => { it('should write the selected order to the state', () => { const order = { id: 'orderid' } as Order; - const action = new SelectOrder({ orderId: order.id }); + const action = selectOrder({ orderId: order.id }); const state = ordersReducer(initialState, action); expect(state.selected).toEqual(order.id); diff --git a/src/app/core/store/customer/orders/orders.reducer.ts b/src/app/core/store/customer/orders/orders.reducer.ts index 1377627338..73f42d100b 100644 --- a/src/app/core/store/customer/orders/orders.reducer.ts +++ b/src/app/core/store/customer/orders/orders.reducer.ts @@ -1,9 +1,22 @@ import { EntityState, createEntityAdapter } from '@ngrx/entity'; +import { createReducer, on } from '@ngrx/store'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { Order } from 'ish-core/models/order/order.model'; - -import { OrdersAction, OrdersActionTypes } from './orders.actions'; +import { setErrorOn, setLoadingOn } from 'ish-core/utils/ngrx-creators'; + +import { + createOrder, + createOrderFail, + createOrderSuccess, + loadOrder, + loadOrderFail, + loadOrderSuccess, + loadOrders, + loadOrdersFail, + loadOrdersSuccess, + selectOrder, +} from './orders.actions'; export const orderAdapter = createEntityAdapter({ selectId: order => order.id, @@ -21,56 +34,30 @@ export const initialState: OrdersState = orderAdapter.getInitialState({ error: undefined, }); -export function ordersReducer(state = initialState, action: OrdersAction): OrdersState { - switch (action.type) { - case OrdersActionTypes.SelectOrder: { - return { - ...state, - selected: action.payload.orderId, - }; - } - - case OrdersActionTypes.LoadOrders: - case OrdersActionTypes.LoadOrder: - case OrdersActionTypes.CreateOrder: { - return { - ...state, - loading: true, - }; - } - - case OrdersActionTypes.CreateOrderSuccess: - case OrdersActionTypes.LoadOrderSuccess: { - const { order } = action.payload; - - return { - ...orderAdapter.upsertOne(order, state), - selected: order.id, - loading: false, - error: undefined, - }; - } - - case OrdersActionTypes.LoadOrdersSuccess: { - const { orders } = action.payload; - return { - ...orderAdapter.setAll(orders, state), - loading: false, - error: undefined, - }; - } - - case OrdersActionTypes.LoadOrdersFail: - case OrdersActionTypes.LoadOrderFail: - case OrdersActionTypes.CreateOrderFail: { - const { error } = action.payload; - return { - ...state, - error, - loading: false, - }; - } - } - - return state; -} +export const ordersReducer = createReducer( + initialState, + on(selectOrder, (state: OrdersState, action) => ({ + ...state, + selected: action.payload.orderId, + })), + setLoadingOn(loadOrders, loadOrder, createOrder), + on(createOrderSuccess, loadOrderSuccess, (state: OrdersState, action) => { + const { order } = action.payload; + + return { + ...orderAdapter.upsertOne(order, state), + selected: order.id, + loading: false, + error: undefined, + }; + }), + on(loadOrdersSuccess, (state: OrdersState, action) => { + const { orders } = action.payload; + return { + ...orderAdapter.setAll(orders, state), + loading: false, + error: undefined, + }; + }), + setErrorOn(loadOrdersFail, loadOrderFail, createOrderFail) +); diff --git a/src/app/core/store/customer/orders/orders.selectors.spec.ts b/src/app/core/store/customer/orders/orders.selectors.spec.ts index a2e3f69101..d324caaf60 100644 --- a/src/app/core/store/customer/orders/orders.selectors.spec.ts +++ b/src/app/core/store/customer/orders/orders.selectors.spec.ts @@ -6,17 +6,17 @@ import { OrderView } from 'ish-core/models/order/order.model'; import { Product } from 'ish-core/models/product/product.model'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; -import { LoadProductSuccess } from 'ish-core/store/shopping/products'; +import { loadProductSuccess } from 'ish-core/store/shopping/products'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; import { - LoadOrder, - LoadOrderSuccess, - LoadOrders, - LoadOrdersFail, - LoadOrdersSuccess, - SelectOrder, + loadOrder, + loadOrderSuccess, + loadOrders, + loadOrdersFail, + loadOrdersSuccess, + selectOrder, } from './orders.actions'; import { getOrder, @@ -70,23 +70,23 @@ describe('Orders Selectors', () => { describe('select order', () => { beforeEach(() => { - store$.dispatch(new LoadOrdersSuccess({ orders })); - store$.dispatch(new SelectOrder({ orderId: orders[1].id })); + store$.dispatch(loadOrdersSuccess({ orders })); + store$.dispatch(selectOrder({ orderId: orders[1].id })); }); it('should get a certain order if they are loaded orders', () => { expect(getSelectedOrder(store$.state).id).toEqual(orders[1].id); }); it('should not get any order if the previously selected order was not found', () => { - store$.dispatch(new SelectOrder({ orderId: 'invalid' })); + store$.dispatch(selectOrder({ orderId: 'invalid' })); expect(getSelectedOrder(store$.state)).toBeUndefined(); }); }); describe('loading orders', () => { beforeEach(() => { - store$.dispatch(new LoadOrders()); - store$.dispatch(new LoadProductSuccess({ product: { sku: 'sku' } as Product })); + store$.dispatch(loadOrders()); + store$.dispatch(loadProductSuccess({ product: { sku: 'sku' } as Product })); }); it('should set the state to loading', () => { @@ -96,7 +96,7 @@ describe('Orders Selectors', () => { describe('and reporting success', () => { beforeEach(() => { - store$.dispatch(new LoadOrdersSuccess({ orders })); + store$.dispatch(loadOrdersSuccess({ orders })); }); it('should set loading to false', () => { @@ -113,7 +113,7 @@ describe('Orders Selectors', () => { describe('and reporting failure', () => { beforeEach(() => { - store$.dispatch(new LoadOrdersFail({ error: { message: 'error', error: 'errorMessage' } as HttpError })); + store$.dispatch(loadOrdersFail({ error: { message: 'error', error: 'errorMessage' } as HttpError })); }); it('should not have loaded orders on error', () => { @@ -126,8 +126,8 @@ describe('Orders Selectors', () => { describe('loading order', () => { beforeEach(() => { - store$.dispatch(new LoadOrder({ orderId: orders[0].id })); - store$.dispatch(new LoadProductSuccess({ product: { sku: 'sku' } as Product })); + store$.dispatch(loadOrder({ orderId: orders[0].id })); + store$.dispatch(loadProductSuccess({ product: { sku: 'sku' } as Product })); }); it('should set the state to loading', () => { @@ -136,7 +136,7 @@ describe('Orders Selectors', () => { describe('and reporting success', () => { beforeEach(() => { - store$.dispatch(new LoadOrderSuccess({ order: orders[0] })); + store$.dispatch(loadOrderSuccess({ order: orders[0] })); }); it('should set loading to false', () => { @@ -152,7 +152,7 @@ describe('Orders Selectors', () => { describe('and reporting failure', () => { beforeEach(() => { - store$.dispatch(new LoadOrdersFail({ error: { message: 'error' } as HttpError })); + store$.dispatch(loadOrdersFail({ error: { message: 'error' } as HttpError })); }); it('should not have loaded orders on error', () => { diff --git a/src/app/core/store/customer/restore/restore.effects.spec.ts b/src/app/core/store/customer/restore/restore.effects.spec.ts index aebae60306..e4057257b9 100644 --- a/src/app/core/store/customer/restore/restore.effects.spec.ts +++ b/src/app/core/store/customer/restore/restore.effects.spec.ts @@ -10,10 +10,10 @@ import { Order } from 'ish-core/models/order/order.model'; import { User } from 'ish-core/models/user/user.model'; import { CookiesService } from 'ish-core/services/cookies/cookies.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; -import { BasketActionTypes, LoadBasketSuccess } from 'ish-core/store/customer/basket'; +import { loadBasketByAPIToken, loadBasketSuccess } from 'ish-core/store/customer/basket'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; -import { LoadOrderSuccess, OrdersActionTypes } from 'ish-core/store/customer/orders'; -import { LoginUserSuccess, LogoutUser, SetAPIToken, UserActionTypes } from 'ish-core/store/customer/user'; +import { loadOrderByAPIToken, loadOrderSuccess } from 'ish-core/store/customer/orders'; +import { loadUserByAPIToken, loginUserSuccess, logoutUser, setAPIToken } from 'ish-core/store/customer/user'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { BasketMockData } from 'ish-core/utils/dev/basket-mock-data'; @@ -58,7 +58,7 @@ describe('Restore Effects', () => { done(); }, fail); - store$.dispatch(new LogoutUser()); + store$.dispatch(logoutUser()); }); }); @@ -72,7 +72,7 @@ describe('Restore Effects', () => { when(cookiesServiceMock.get('apiToken')).thenReturn(JSON.stringify({ apiToken: 'dummy', type: 'basket' })); restoreEffects.restoreUserOrBasketOrOrderByToken$.subscribe(action => { - expect(action).toHaveProperty('type', BasketActionTypes.LoadBasketByAPIToken); + expect(action).toHaveProperty('type', loadBasketByAPIToken.type); expect(action).toHaveProperty('payload.apiToken', 'dummy'); done(); }, fail); @@ -83,7 +83,7 @@ describe('Restore Effects', () => { when(cookiesServiceMock.get('apiToken')).thenReturn(JSON.stringify({ apiToken: 'dummy', type: 'user' })); restoreEffects.restoreUserOrBasketOrOrderByToken$.subscribe(action => { - expect(action).toHaveProperty('type', UserActionTypes.LoadUserByAPIToken); + expect(action).toHaveProperty('type', loadUserByAPIToken.type); expect(action).toHaveProperty('payload.apiToken', 'dummy'); done(); }, fail); @@ -96,7 +96,7 @@ describe('Restore Effects', () => { ); restoreEffects.restoreUserOrBasketOrOrderByToken$.subscribe(action => { - expect(action).toHaveProperty('type', OrdersActionTypes.LoadOrderByAPIToken); + expect(action).toHaveProperty('type', loadOrderByAPIToken.type); expect(action).toHaveProperty('payload.apiToken', 'dummy'); expect(action).toHaveProperty('payload.orderId', '12345'); done(); @@ -107,14 +107,14 @@ describe('Restore Effects', () => { describe('saveAPITokenToCookie$', () => { it('should not save token when neither basket nor user nor order is available', () => { - store$.dispatch(new SetAPIToken({ apiToken: 'dummy' })); + store$.dispatch(setAPIToken({ apiToken: 'dummy' })); expect(restoreEffects.saveAPITokenToCookie$).toBeObservable(cold('-')); }); it('should save basket token when basket is available', done => { - store$.dispatch(new SetAPIToken({ apiToken: 'dummy' })); - store$.dispatch(new LoadBasketSuccess({ basket: BasketMockData.getBasket() })); + store$.dispatch(setAPIToken({ apiToken: 'dummy' })); + store$.dispatch(loadBasketSuccess({ basket: BasketMockData.getBasket() })); restoreEffects.saveAPITokenToCookie$.subscribe( () => { @@ -129,8 +129,8 @@ describe('Restore Effects', () => { }); it('should save user token when user is available', done => { - store$.dispatch(new SetAPIToken({ apiToken: 'dummy' })); - store$.dispatch(new LoginUserSuccess({ user: { email: 'test@intershop.de' } as User, customer: undefined })); + store$.dispatch(setAPIToken({ apiToken: 'dummy' })); + store$.dispatch(loginUserSuccess({ user: { email: 'test@intershop.de' } as User, customer: undefined })); restoreEffects.saveAPITokenToCookie$.subscribe( () => { @@ -145,9 +145,9 @@ describe('Restore Effects', () => { }); it('should save user token when basket and user are available', done => { - store$.dispatch(new SetAPIToken({ apiToken: 'dummy' })); - store$.dispatch(new LoadBasketSuccess({ basket: BasketMockData.getBasket() })); - store$.dispatch(new LoginUserSuccess({ user: { email: 'test@intershop.de' } as User, customer: undefined })); + store$.dispatch(setAPIToken({ apiToken: 'dummy' })); + store$.dispatch(loadBasketSuccess({ basket: BasketMockData.getBasket() })); + store$.dispatch(loginUserSuccess({ user: { email: 'test@intershop.de' } as User, customer: undefined })); restoreEffects.saveAPITokenToCookie$.subscribe( () => { @@ -162,8 +162,8 @@ describe('Restore Effects', () => { }); it('should save order token when order is available', done => { - store$.dispatch(new SetAPIToken({ apiToken: 'dummy' })); - store$.dispatch(new LoadOrderSuccess({ order: { id: '12345' } as Order })); + store$.dispatch(setAPIToken({ apiToken: 'dummy' })); + store$.dispatch(loadOrderSuccess({ order: { id: '12345' } as Order })); restoreEffects.saveAPITokenToCookie$.subscribe( () => { @@ -182,11 +182,11 @@ describe('Restore Effects', () => { describe('logOutUserIfTokenVanishes$', () => { it('should log out user when token is not available', done => { - store$.dispatch(new LoginUserSuccess({ user: { email: 'test@intershop.de' } as User, customer: undefined })); + store$.dispatch(loginUserSuccess({ user: { email: 'test@intershop.de' } as User, customer: undefined })); restoreEffects.logOutUserIfTokenVanishes$.subscribe( action => { - expect(action.type).toEqual(UserActionTypes.LogoutUser); + expect(action.type).toEqual(logoutUser.type); done(); }, fail, @@ -197,11 +197,11 @@ describe('Restore Effects', () => { describe('removeAnonymousBasketIfTokenVanishes$', () => { it('should remove basket when token is not available', done => { - store$.dispatch(new LoadBasketSuccess({ basket: BasketMockData.getBasket() })); + store$.dispatch(loadBasketSuccess({ basket: BasketMockData.getBasket() })); restoreEffects.removeAnonymousBasketIfTokenVanishes$.subscribe({ next: action => { - expect(action.type).toEqual(UserActionTypes.LogoutUser); + expect(action.type).toEqual(logoutUser.type); done(); }, complete: fail, @@ -209,8 +209,8 @@ describe('Restore Effects', () => { }); it('should do nothing when user is available', done => { - store$.dispatch(new LoginUserSuccess({ user: { email: 'test@intershop.de' } as User, customer: undefined })); - store$.dispatch(new LoadBasketSuccess({ basket: BasketMockData.getBasket() })); + store$.dispatch(loginUserSuccess({ user: { email: 'test@intershop.de' } as User, customer: undefined })); + store$.dispatch(loadBasketSuccess({ basket: BasketMockData.getBasket() })); restoreEffects.removeAnonymousBasketIfTokenVanishes$.subscribe(fail, fail, fail); @@ -230,7 +230,7 @@ describe('Restore Effects', () => { done(); }); - store$.dispatch(new LoadBasketSuccess({ basket: BasketMockData.getBasket() })); + store$.dispatch(loadBasketSuccess({ basket: BasketMockData.getBasket() })); jest.advanceTimersByTime(RestoreEffects.SESSION_KEEP_ALIVE + 100); }); @@ -238,11 +238,11 @@ describe('Restore Effects', () => { it('should not refresh the basket if it is getting unavailable', done => { restoreEffects.sessionKeepAlive$.subscribe(fail); - store$.dispatch(new LoadBasketSuccess({ basket: BasketMockData.getBasket() })); + store$.dispatch(loadBasketSuccess({ basket: BasketMockData.getBasket() })); jest.advanceTimersByTime(RestoreEffects.SESSION_KEEP_ALIVE / 2); - store$.dispatch(new LogoutUser()); + store$.dispatch(logoutUser()); jest.advanceTimersByTime(RestoreEffects.SESSION_KEEP_ALIVE + 100); diff --git a/src/app/core/store/customer/restore/restore.effects.ts b/src/app/core/store/customer/restore/restore.effects.ts index 9752c5f4f8..c91b3cbe83 100644 --- a/src/app/core/store/customer/restore/restore.effects.ts +++ b/src/app/core/store/customer/restore/restore.effects.ts @@ -1,6 +1,6 @@ import { isPlatformBrowser } from '@angular/common'; import { ApplicationRef, Inject, Injectable, PLATFORM_ID } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { routerNavigationAction } from '@ngrx/router-store'; import { Store, select } from '@ngrx/store'; import { EMPTY, combineLatest, iif, interval } from 'rxjs'; @@ -18,15 +18,9 @@ import { } from 'rxjs/operators'; import { CookiesService } from 'ish-core/services/cookies/cookies.service'; -import { LoadBasket, LoadBasketByAPIToken, getCurrentBasket } from 'ish-core/store/customer/basket'; -import { LoadOrderByAPIToken, getSelectedOrderId } from 'ish-core/store/customer/orders'; -import { - LoadUserByAPIToken, - LogoutUser, - UserActionTypes, - getAPIToken, - getLoggedInUser, -} from 'ish-core/store/customer/user'; +import { getCurrentBasket, loadBasket, loadBasketByAPIToken } from 'ish-core/store/customer/basket'; +import { getSelectedOrderId, loadOrderByAPIToken } from 'ish-core/store/customer/orders'; +import { getAPIToken, getLoggedInUser, loadUserByAPIToken, logoutUser } from 'ish-core/store/customer/user'; import { whenTruthy } from 'ish-core/utils/operators'; import { SfeAdapterService } from 'ish-shared/cms/sfe-adapter/sfe-adapter.service'; @@ -54,105 +48,115 @@ export class RestoreEffects { * type = basket: API token is used to restore the basket of an anonymous user. * type = order: API token is used to restore the just created order after coming back from redirect after checkout ( the orderId is also part of the cookie). */ - @Effect({ dispatch: false }) - saveAPITokenToCookie$ = combineLatest([ - this.store$.pipe(select(getLoggedInUser)), - this.store$.pipe(select(getCurrentBasket)), - this.store$.pipe(select(getSelectedOrderId)), - this.store$.pipe(select(getAPIToken)), - ]).pipe( - filter(() => isPlatformBrowser(this.platformId)), - filter(([user, basket, orderId]) => !!user || !!basket || !!orderId), - map(([user, basket, orderId, apiToken]) => - this.makeCookie({ apiToken, type: user ? 'user' : basket ? 'basket' : 'order', orderId }) - ), - tap(cookie => { - const options = { - expires: new Date(Date.now() + 3600000), - secure: (isPlatformBrowser(this.platformId) && location.protocol === 'https:') || false, - }; - this.cookieService.put('apiToken', cookie, options); - }) + saveAPITokenToCookie$ = createEffect( + () => + combineLatest([ + this.store$.pipe(select(getLoggedInUser)), + this.store$.pipe(select(getCurrentBasket)), + this.store$.pipe(select(getSelectedOrderId)), + this.store$.pipe(select(getAPIToken)), + ]).pipe( + filter(() => isPlatformBrowser(this.platformId)), + filter(([user, basket, orderId]) => !!user || !!basket || !!orderId), + map(([user, basket, orderId, apiToken]) => + this.makeCookie({ apiToken, type: user ? 'user' : basket ? 'basket' : 'order', orderId }) + ), + tap(cookie => { + const options = { + expires: new Date(Date.now() + 3600000), + secure: (isPlatformBrowser(this.platformId) && location.protocol === 'https:') || false, + }; + this.cookieService.put('apiToken', cookie, options); + }) + ), + { dispatch: false } ); - @Effect({ dispatch: false }) - destroyTokenInCookieOnLogout$ = this.actions$.pipe( - ofType(UserActionTypes.LogoutUser), - tap(() => { - this.cookieService.remove('apiToken'); - }) + destroyTokenInCookieOnLogout$ = createEffect( + () => + this.actions$.pipe( + ofType(logoutUser), + tap(() => { + this.cookieService.remove('apiToken'); + }) + ), + { dispatch: false } ); /** * Triggers actions to restore a user login, a basket or an order based on previously set cookie (see also effect saveAPITokenToCookie$). */ - @Effect() - restoreUserOrBasketOrOrderByToken$ = iif( - () => isPlatformBrowser(this.platformId), - this.actions$.pipe( - ofType(routerNavigationAction), - first(), - map(() => this.cookieService.get('apiToken')), - whenTruthy(), - map(c => this.parseCookie(c)), - map(cookie => { - switch (cookie.type) { - case 'basket': { - return new LoadBasketByAPIToken({ apiToken: cookie.apiToken }); - } - case 'user': { - return new LoadUserByAPIToken({ apiToken: cookie.apiToken }); + restoreUserOrBasketOrOrderByToken$ = createEffect(() => + iif( + () => isPlatformBrowser(this.platformId), + this.actions$.pipe( + ofType(routerNavigationAction), + first(), + map(() => this.cookieService.get('apiToken')), + whenTruthy(), + map(c => this.parseCookie(c)), + map(cookie => { + switch (cookie.type) { + case 'basket': { + return loadBasketByAPIToken({ apiToken: cookie.apiToken }); + } + case 'user': { + return loadUserByAPIToken({ apiToken: cookie.apiToken }); + } + case 'order': { + return loadOrderByAPIToken({ orderId: cookie.orderId, apiToken: cookie.apiToken }); + } } - case 'order': { - return new LoadOrderByAPIToken({ orderId: cookie.orderId, apiToken: cookie.apiToken }); - } - } - }) + }) + ) ) ); - @Effect() - logOutUserIfTokenVanishes$ = this.appRef.isStable.pipe( - whenTruthy(), - first(), - concatMapTo( - interval(1000).pipe( - takeWhile(() => isPlatformBrowser(this.platformId)), - withLatestFrom(this.store$.pipe(select(getLoggedInUser))), - map(([, user]) => ({ user, apiToken: this.cookieService.get('apiToken') })), - filter(({ user, apiToken }) => user && !apiToken), - mapTo(new LogoutUser()) + logOutUserIfTokenVanishes$ = createEffect(() => + this.appRef.isStable.pipe( + whenTruthy(), + first(), + concatMapTo( + interval(1000).pipe( + takeWhile(() => isPlatformBrowser(this.platformId)), + withLatestFrom(this.store$.pipe(select(getLoggedInUser))), + map(([, user]) => ({ user, apiToken: this.cookieService.get('apiToken') })), + filter(({ user, apiToken }) => user && !apiToken), + mapTo(logoutUser()) + ) ) ) ); - @Effect() - removeAnonymousBasketIfTokenVanishes$ = this.appRef.isStable.pipe( - whenTruthy(), - first(), - concatMapTo( - interval(1000).pipe( - takeWhile(() => isPlatformBrowser(this.platformId)), - withLatestFrom(this.store$.pipe(select(getLoggedInUser)), this.store$.pipe(select(getCurrentBasket))), - map(([, user, basket]) => ({ user, basket, apiToken: this.cookieService.get('apiToken') })), - filter(({ user, basket, apiToken }) => !user && basket && !apiToken), - mapTo(new LogoutUser()) + removeAnonymousBasketIfTokenVanishes$ = createEffect(() => + this.appRef.isStable.pipe( + whenTruthy(), + first(), + concatMapTo( + interval(1000).pipe( + takeWhile(() => isPlatformBrowser(this.platformId)), + withLatestFrom(this.store$.pipe(select(getLoggedInUser)), this.store$.pipe(select(getCurrentBasket))), + map(([, user, basket]) => ({ user, basket, apiToken: this.cookieService.get('apiToken') })), + filter(({ user, basket, apiToken }) => !user && basket && !apiToken), + mapTo(logoutUser()) + ) ) ) ); - @Effect() - sessionKeepAlive$ = this.appRef.isStable.pipe( - filter(() => isPlatformBrowser(this.platformId)), - whenTruthy(), - first(), - concatMapTo( - this.store$.pipe( - select(getCurrentBasket), - switchMap(basket => - this.sfeAdapterService.isInitialized() - ? EMPTY - : interval(RestoreEffects.SESSION_KEEP_ALIVE).pipe(mergeMapTo(basket ? [new LoadBasket()] : [])) + sessionKeepAlive$ = createEffect(() => + this.appRef.isStable.pipe( + filter(() => isPlatformBrowser(this.platformId)), + whenTruthy(), + first(), + concatMapTo( + this.store$.pipe( + select(getCurrentBasket), + switchMap(basket => + this.sfeAdapterService.isInitialized() + ? EMPTY + : interval(RestoreEffects.SESSION_KEEP_ALIVE).pipe(mergeMapTo(basket ? [loadBasket()] : [])) + ) ) ) ) diff --git a/src/app/core/store/customer/user/user.actions.ts b/src/app/core/store/customer/user/user.actions.ts index 6a3ad9a632..3ca1ea4359 100644 --- a/src/app/core/store/customer/user/user.actions.ts +++ b/src/app/core/store/customer/user/user.actions.ts @@ -1,256 +1,124 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; import { Credentials } from 'ish-core/models/credentials/credentials.model'; import { Customer, CustomerRegistrationType, CustomerUserType } from 'ish-core/models/customer/customer.model'; -import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { PasswordReminder } from 'ish-core/models/password-reminder/password-reminder.model'; import { PaymentMethod } from 'ish-core/models/payment-method/payment-method.model'; import { User } from 'ish-core/models/user/user.model'; +import { httpError, payload } from 'ish-core/utils/ngrx-creators'; -export enum UserActionTypes { - LoginUser = '[Account] Login User', - LoginUserSuccess = '[Account API] Login User Success', - LoginUserFail = '[Account API] Login User Failed', - SetAPIToken = '[Account Internal] Set API Token', - ResetAPIToken = '[Account Internal] Reset API Token', - LoadCompanyUser = '[Account Internal] Load Company User', - LoadCompanyUserFail = '[Account API] Load Company User Fail', - LoadCompanyUserSuccess = '[Account API] Load Company User Success', - LogoutUser = '[Account] Logout User', - CreateUser = '[Account] Create User', - CreateUserFail = '[Account API] Create User Failed', - UpdateUser = '[Account] Update User', - UpdateUserSuccess = '[Account API] Update User Succeeded', - UpdateUserFail = '[Account API] Update User Failed', - UpdateUserPassword = '[Account] Update User Password', - UpdateUserPasswordSuccess = '[Account API] Update User Password Succeeded', - UpdateUserPasswordFail = '[Account API] Update User Password Failed', - UpdateCustomer = '[Account] Update Customer', - UpdateCustomerSuccess = '[Account API] Update Customer Succeeded', - UpdateCustomerFail = '[Account API] Update Customer Failed', - UserErrorReset = '[Account Internal] Reset User Error', - LoadUserByAPIToken = '[Account] Load User by API Token', - SetPGID = '[Personalization Internal] Set PGID', - LoadUserPaymentMethods = '[Account] Load User Payment Methods', - LoadUserPaymentMethodsFail = '[Account API] Load User Payment Methods Fail', - LoadUserPaymentMethodsSuccess = '[Account API] Load User Payment Methods Success', - DeleteUserPaymentInstrument = '[Account] Delete User Instrument Payment ', - DeleteUserPaymentInstrumentFail = '[Account API] Delete User Payment Instrument Fail', - DeleteUserPaymentInstrumentSuccess = '[Account API] Delete User Payment Instrument Success', - RequestPasswordReminder = '[Password Reminder] Request Password Reminder', - RequestPasswordReminderFail = '[Password Reminder API] Request Password Reminder Fail', - RequestPasswordReminderSuccess = '[Password Reminder API] Request Password Reminder Success', - ResetPasswordReminder = '[Password Reminder Internal] Reset Password Reminder Data', - UpdateUserPasswordByPasswordReminder = '[Password Reminder] Update User Password', - UpdateUserPasswordByPasswordReminderSuccess = '[Password Reminder] Update User Password Succeeded', - UpdateUserPasswordByPasswordReminderFail = '[Password Reminder] Update User Password Failed', -} - -export class LoginUser implements Action { - readonly type = UserActionTypes.LoginUser; - constructor(public payload: { credentials: Credentials }) {} -} - -export class LoginUserFail implements Action { - readonly type = UserActionTypes.LoginUserFail; - constructor(public payload: { error: HttpError }) {} -} - -export class LoginUserSuccess implements Action { - readonly type = UserActionTypes.LoginUserSuccess; - constructor(public payload: CustomerUserType) {} -} - -export class SetAPIToken implements Action { - readonly type = UserActionTypes.SetAPIToken; - constructor(public payload: { apiToken: string }) {} -} - -export class ResetAPIToken implements Action { - readonly type = UserActionTypes.ResetAPIToken; -} - -export class LoadCompanyUser implements Action { - readonly type = UserActionTypes.LoadCompanyUser; -} - -export class LoadCompanyUserFail implements Action { - readonly type = UserActionTypes.LoadCompanyUserFail; - constructor(public payload: { error: HttpError }) {} -} - -export class LoadCompanyUserSuccess implements Action { - readonly type = UserActionTypes.LoadCompanyUserSuccess; - constructor(public payload: { user: User }) {} -} - -export class LogoutUser implements Action { - readonly type = UserActionTypes.LogoutUser; -} - -export class CreateUser implements Action { - readonly type = UserActionTypes.CreateUser; - constructor(public payload: CustomerRegistrationType) {} -} - -export class CreateUserFail implements Action { - readonly type = UserActionTypes.CreateUserFail; - constructor(public payload: { error: HttpError }) {} -} - -export class UpdateUser implements Action { - readonly type = UserActionTypes.UpdateUser; - constructor(public payload: { user: User; successMessage?: string; successRouterLink?: string }) {} -} - -export class UpdateUserSuccess implements Action { - readonly type = UserActionTypes.UpdateUserSuccess; - constructor(public payload: { user: User; successMessage?: string }) {} -} - -export class UpdateUserFail implements Action { - readonly type = UserActionTypes.UpdateUserFail; - constructor(public payload: { error: HttpError }) {} -} - -export class UpdateUserPassword implements Action { - readonly type = UserActionTypes.UpdateUserPassword; - constructor(public payload: { password: string; currentPassword: string; successMessage?: string }) {} -} - -export class UpdateUserPasswordSuccess implements Action { - readonly type = UserActionTypes.UpdateUserPasswordSuccess; - constructor(public payload: { successMessage?: string }) {} -} - -export class UpdateUserPasswordFail implements Action { - readonly type = UserActionTypes.UpdateUserPasswordFail; - constructor(public payload: { error: HttpError }) {} -} - -export class UpdateCustomer implements Action { - readonly type = UserActionTypes.UpdateCustomer; - constructor(public payload: { customer: Customer; successMessage?: string; successRouterLink?: string }) {} -} - -export class UpdateCustomerSuccess implements Action { - readonly type = UserActionTypes.UpdateCustomerSuccess; - constructor(public payload: { customer: Customer; successMessage?: string }) {} -} - -export class UpdateCustomerFail implements Action { - readonly type = UserActionTypes.UpdateCustomerFail; - constructor(public payload: { error: HttpError }) {} -} - -export class UserErrorReset implements Action { - readonly type = UserActionTypes.UserErrorReset; -} - -export class LoadUserByAPIToken implements Action { - readonly type = UserActionTypes.LoadUserByAPIToken; - constructor(public payload: { apiToken: string }) {} -} - -export class SetPGID implements Action { - readonly type = UserActionTypes.SetPGID; - constructor(public payload: { pgid: string }) {} -} - -export class LoadUserPaymentMethods implements Action { - readonly type = UserActionTypes.LoadUserPaymentMethods; -} - -export class LoadUserPaymentMethodsFail implements Action { - readonly type = UserActionTypes.LoadUserPaymentMethodsFail; - constructor(public payload: { error: HttpError }) {} -} - -export class LoadUserPaymentMethodsSuccess implements Action { - readonly type = UserActionTypes.LoadUserPaymentMethodsSuccess; - constructor(public payload: { paymentMethods: PaymentMethod[] }) {} -} - -export class DeleteUserPaymentInstrument implements Action { - readonly type = UserActionTypes.DeleteUserPaymentInstrument; - constructor(public payload: { id: string }) {} -} - -export class DeleteUserPaymentInstrumentFail implements Action { - readonly type = UserActionTypes.DeleteUserPaymentInstrumentFail; - constructor(public payload: { error: HttpError }) {} -} - -export class DeleteUserPaymentInstrumentSuccess implements Action { - readonly type = UserActionTypes.DeleteUserPaymentInstrumentSuccess; -} - -export class RequestPasswordReminder implements Action { - readonly type = UserActionTypes.RequestPasswordReminder; - constructor(public payload: { data: PasswordReminder }) {} -} - -export class RequestPasswordReminderSuccess implements Action { - readonly type = UserActionTypes.RequestPasswordReminderSuccess; -} - -export class RequestPasswordReminderFail implements Action { - readonly type = UserActionTypes.RequestPasswordReminderFail; - constructor(public payload: { error: HttpError }) {} -} - -export class ResetPasswordReminder implements Action { - readonly type = UserActionTypes.ResetPasswordReminder; -} - -export class UpdateUserPasswordByPasswordReminder implements Action { - readonly type = UserActionTypes.UpdateUserPasswordByPasswordReminder; - constructor(public payload: { password: string; userID: string; secureCode: string }) {} -} - -export class UpdateUserPasswordByPasswordReminderSuccess implements Action { - readonly type = UserActionTypes.UpdateUserPasswordByPasswordReminderSuccess; -} - -export class UpdateUserPasswordByPasswordReminderFail implements Action { - readonly type = UserActionTypes.UpdateUserPasswordByPasswordReminderFail; - constructor(public payload: { error: HttpError }) {} -} - -export type UserAction = - | LoginUser - | LoginUserFail - | LoginUserSuccess - | SetAPIToken - | ResetAPIToken - | LoadCompanyUser - | LoadCompanyUserFail - | LoadCompanyUserSuccess - | LogoutUser - | CreateUser - | CreateUserFail - | UpdateUser - | UpdateUserSuccess - | UpdateUserFail - | UpdateUserPassword - | UpdateUserPasswordSuccess - | UpdateUserPasswordFail - | UpdateCustomer - | UpdateCustomerSuccess - | UpdateCustomerFail - | UserErrorReset - | LoadUserByAPIToken - | SetPGID - | LoadUserPaymentMethods - | LoadUserPaymentMethodsFail - | LoadUserPaymentMethodsSuccess - | DeleteUserPaymentInstrument - | DeleteUserPaymentInstrumentFail - | DeleteUserPaymentInstrumentSuccess - | RequestPasswordReminder - | RequestPasswordReminderSuccess - | RequestPasswordReminderFail - | ResetPasswordReminder - | UpdateUserPasswordByPasswordReminder - | UpdateUserPasswordByPasswordReminderSuccess - | UpdateUserPasswordByPasswordReminderFail; +export const loginUser = createAction('[Account] Login User', payload<{ credentials: Credentials }>()); + +export const loginUserFail = createAction('[Account API] Login User Failed', httpError()); + +export const loginUserSuccess = createAction('[Account API] Login User Success', payload()); + +export const setAPIToken = createAction('[Account Internal] Set API Token', payload<{ apiToken: string }>()); + +export const resetAPIToken = createAction('[Account Internal] Reset API Token'); + +export const loadCompanyUser = createAction('[Account Internal] Load Company User'); + +export const loadCompanyUserFail = createAction('[Account API] Load Company User Fail', httpError()); + +export const loadCompanyUserSuccess = createAction( + '[Account API] Load Company User Success', + payload<{ user: User }>() +); + +export const logoutUser = createAction('[Account] Logout User'); + +export const createUser = createAction('[Account] Create User', payload()); + +export const createUserFail = createAction('[Account API] Create User Failed', httpError()); + +export const updateUser = createAction( + '[Account] Update User', + payload<{ user: User; successMessage?: string; successRouterLink?: string }>() +); + +export const updateUserSuccess = createAction( + '[Account API] Update User Succeeded', + payload<{ user: User; successMessage?: string }>() +); + +export const updateUserFail = createAction('[Account API] Update User Failed', httpError()); + +export const updateUserPassword = createAction( + '[Account] Update User Password', + payload<{ password: string; currentPassword: string; successMessage?: string }>() +); + +export const updateUserPasswordSuccess = createAction( + '[Account API] Update User Password Succeeded', + payload<{ successMessage?: string }>() +); + +export const updateUserPasswordFail = createAction('[Account API] Update User Password Failed', httpError()); + +export const updateCustomer = createAction( + '[Account] Update Customer', + payload<{ customer: Customer; successMessage?: string; successRouterLink?: string }>() +); + +export const updateCustomerSuccess = createAction( + '[Account API] Update Customer Succeeded', + payload<{ customer: Customer; successMessage?: string }>() +); + +export const updateCustomerFail = createAction('[Account API] Update Customer Failed', httpError()); + +export const userErrorReset = createAction('[Account Internal] Reset User Error'); + +export const loadUserByAPIToken = createAction('[Account] Load User by API Token', payload<{ apiToken: string }>()); + +export const setPGID = createAction('[Personalization Internal] Set PGID', payload<{ pgid: string }>()); + +export const loadUserPaymentMethods = createAction('[Account] Load User Payment Methods'); + +export const loadUserPaymentMethodsFail = createAction('[Account API] Load User Payment Methods Fail', httpError()); + +export const loadUserPaymentMethodsSuccess = createAction( + '[Account API] Load User Payment Methods Success', + payload<{ paymentMethods: PaymentMethod[] }>() +); + +export const deleteUserPaymentInstrument = createAction( + '[Account] Delete User Instrument Payment ', + payload<{ id: string }>() +); + +export const deleteUserPaymentInstrumentFail = createAction( + '[Account API] Delete User Payment Instrument Fail', + httpError() +); + +export const deleteUserPaymentInstrumentSuccess = createAction('[Account API] Delete User Payment Instrument Success'); + +export const requestPasswordReminder = createAction( + '[Password Reminder] Request Password Reminder', + payload<{ data: PasswordReminder }>() +); + +export const requestPasswordReminderSuccess = createAction('[Password Reminder API] Request Password Reminder Success'); + +export const requestPasswordReminderFail = createAction( + '[Password Reminder API] Request Password Reminder Fail', + httpError() +); + +export const resetPasswordReminder = createAction('[Password Reminder Internal] Reset Password Reminder Data'); + +export const updateUserPasswordByPasswordReminder = createAction( + '[Password Reminder] Update User Password', + payload<{ password: string; userID: string; secureCode: string }>() +); + +export const updateUserPasswordByPasswordReminderSuccess = createAction( + '[Password Reminder] Update User Password Succeeded' +); + +export const updateUserPasswordByPasswordReminderFail = createAction( + '[Password Reminder] Update User Password Failed', + httpError() +); diff --git a/src/app/core/store/customer/user/user.effects.spec.ts b/src/app/core/store/customer/user/user.effects.spec.ts index a19069558c..467ecac118 100644 --- a/src/app/core/store/customer/user/user.effects.spec.ts +++ b/src/app/core/store/customer/user/user.effects.spec.ts @@ -21,39 +21,39 @@ import { PaymentService } from 'ish-core/services/payment/payment.service'; import { PersonalizationService } from 'ish-core/services/personalization/personalization.service'; import { UserService } from 'ish-core/services/user/user.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; -import { DisplaySuccessMessage } from 'ish-core/store/core/messages'; +import { displaySuccessMessage } from 'ish-core/store/core/messages'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; import { - CreateUser, - CreateUserFail, - DeleteUserPaymentInstrument, - DeleteUserPaymentInstrumentFail, - DeleteUserPaymentInstrumentSuccess, - LoadCompanyUser, - LoadCompanyUserFail, - LoadCompanyUserSuccess, - LoadUserByAPIToken, - LoadUserPaymentMethods, - LoadUserPaymentMethodsFail, - LoadUserPaymentMethodsSuccess, - LoginUser, - LoginUserFail, - LoginUserSuccess, - LogoutUser, - RequestPasswordReminder, - RequestPasswordReminderFail, - RequestPasswordReminderSuccess, - ResetAPIToken, - UpdateCustomer, - UpdateCustomerFail, - UpdateCustomerSuccess, - UpdateUser, - UpdateUserFail, - UpdateUserPassword, - UpdateUserPasswordFail, - UpdateUserPasswordSuccess, - UpdateUserSuccess, + createUser, + createUserFail, + deleteUserPaymentInstrument, + deleteUserPaymentInstrumentFail, + deleteUserPaymentInstrumentSuccess, + loadCompanyUser, + loadCompanyUserFail, + loadCompanyUserSuccess, + loadUserByAPIToken, + loadUserPaymentMethods, + loadUserPaymentMethodsFail, + loadUserPaymentMethodsSuccess, + loginUser, + loginUserFail, + loginUserSuccess, + logoutUser, + requestPasswordReminder, + requestPasswordReminderFail, + requestPasswordReminderSuccess, + resetAPIToken, + updateCustomer, + updateCustomerFail, + updateCustomerSuccess, + updateUser, + updateUserFail, + updateUserPassword, + updateUserPasswordFail, + updateUserPasswordSuccess, + updateUserSuccess, } from './user.actions'; import { UserEffects } from './user.effects'; @@ -124,7 +124,7 @@ describe('User Effects', () => { describe('loginUser$', () => { it('should call the api service when LoginUser event is called', done => { - const action = new LoginUser({ credentials: { login: 'dummy', password: 'dummy' } }); + const action = loginUser({ credentials: { login: 'dummy', password: 'dummy' } }); actions$ = of(action); @@ -135,8 +135,8 @@ describe('User Effects', () => { }); it('should dispatch a LoginUserSuccess action on successful login', () => { - const action = new LoginUser({ credentials: { login: 'dummy', password: 'dummy' } }); - const completion = new LoginUserSuccess(loginResponseData); + const action = loginUser({ credentials: { login: 'dummy', password: 'dummy' } }); + const completion = loginUserSuccess(loginResponseData); actions$ = hot('-a', { a: action }); const expected$ = cold('-b', { b: completion }); @@ -150,8 +150,8 @@ describe('User Effects', () => { when(userServiceMock.signinUser(anything())).thenReturn(throwError(error)); - const action = new LoginUser({ credentials: { login: 'dummy', password: 'dummy' } }); - const completion = new LoginUserFail({ error: HttpErrorMapper.fromError(error) }); + const action = loginUser({ credentials: { login: 'dummy', password: 'dummy' } }); + const completion = loginUserFail({ error: HttpErrorMapper.fromError(error) }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -161,7 +161,7 @@ describe('User Effects', () => { describe('loadCompanyUser$', () => { it('should call the registationService for LoadCompanyUser', done => { - const action = new LoadCompanyUser(); + const action = loadCompanyUser(); actions$ = of(action); effects.loadCompanyUser$.subscribe(() => { @@ -170,8 +170,8 @@ describe('User Effects', () => { }); }); it('should map to action of type LoadCompanyUserSuccess', () => { - const action = new LoadCompanyUser(); - const completion = new LoadCompanyUserSuccess({ user: { firstName: 'Patricia' } as User }); + const action = loadCompanyUser(); + const completion = loadCompanyUserSuccess({ user: { firstName: 'Patricia' } as User }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -184,8 +184,8 @@ describe('User Effects', () => { const error = { status: 401, headers: new HttpHeaders().set('error-key', 'feld') } as HttpErrorResponse; when(userServiceMock.getCompanyUserData()).thenReturn(throwError(error)); - const action = new LoadCompanyUser(); - const completion = new LoadCompanyUserFail({ error: HttpErrorMapper.fromError(error) }); + const action = loadCompanyUser(); + const completion = loadCompanyUserFail({ error: HttpErrorMapper.fromError(error) }); actions$ = hot('-a', { a: action }); const expected$ = cold('-b', { b: completion }); @@ -196,13 +196,13 @@ describe('User Effects', () => { describe('goToLoginAfterLogoutBySessionTimeout$', () => { it('should navigate to login from current route after ResetAPIToken', fakeAsync(() => { - store$.dispatch(new LoginUserSuccess(loginResponseData)); + store$.dispatch(loginUserSuccess(loginResponseData)); router.navigateByUrl('/account'); tick(500); expect(location.path()).toMatchInlineSnapshot(`"/account"`); - actions$ = from([new ResetAPIToken(), new LogoutUser()]); + actions$ = from([resetAPIToken(), logoutUser()]); effects.goToLoginAfterLogoutBySessionTimeout$.subscribe(noop, fail, noop); @@ -214,7 +214,7 @@ describe('User Effects', () => { describe('redirectAfterLogin$', () => { it('should not navigate anywhere when no returnUrl is given', fakeAsync(() => { - const action = new LoginUserSuccess(loginResponseData); + const action = loginUserSuccess(loginResponseData); actions$ = of(action); @@ -230,7 +230,7 @@ describe('User Effects', () => { tick(500); expect(location.path()).toEqual('/login?returnUrl=%2Ffoobar'); - const action = new LoginUserSuccess(loginResponseData); + const action = loginUserSuccess(loginResponseData); actions$ = of(action); @@ -246,7 +246,7 @@ describe('User Effects', () => { tick(500); expect(location.path()).toEqual('/login'); - store$.dispatch(new LoginUserSuccess(loginResponseData)); + store$.dispatch(loginUserSuccess(loginResponseData)); effects.redirectAfterLogin$.subscribe(noop, fail, noop); @@ -260,7 +260,7 @@ describe('User Effects', () => { tick(500); expect(location.path()).toEqual('/home'); - store$.dispatch(new LoginUserSuccess(loginResponseData)); + store$.dispatch(loginUserSuccess(loginResponseData)); effects.redirectAfterLogin$.subscribe(noop, fail, noop); @@ -272,7 +272,7 @@ describe('User Effects', () => { describe('createUser$', () => { it('should call the api service when Create event is called', done => { - const action = new CreateUser({ + const action = createUser({ customer: { isBusinessCustomer: true, customerNo: 'PC', @@ -290,8 +290,8 @@ describe('User Effects', () => { it('should dispatch a CreateUserLogin action on successful user creation', () => { const credentials: Credentials = { login: '1234', password: 'xxx' }; - const action = new CreateUser({ credentials } as CustomerRegistrationType); - const completion = new LoginUser({ credentials }); + const action = createUser({ credentials } as CustomerRegistrationType); + const completion = loginUser({ credentials }); actions$ = hot('-a', { a: action }); const expected$ = cold('-b', { b: completion }); @@ -304,8 +304,8 @@ describe('User Effects', () => { const error = { status: 401, headers: new HttpHeaders().set('error-key', 'feld') } as HttpErrorResponse; when(userServiceMock.createUser(anything())).thenReturn(throwError(error)); - const action = new CreateUser({} as CustomerRegistrationType); - const completion = new CreateUserFail({ error: HttpErrorMapper.fromError(error) }); + const action = createUser({} as CustomerRegistrationType); + const completion = createUserFail({ error: HttpErrorMapper.fromError(error) }); actions$ = hot('-a', { a: action }); const expected$ = cold('-b', { b: completion }); @@ -317,7 +317,7 @@ describe('User Effects', () => { describe('updateUser$', () => { beforeEach(() => { store$.dispatch( - new LoginUserSuccess({ + loginUserSuccess({ customer: { customerNo: '4711', isBusinessCustomer: false, @@ -327,7 +327,7 @@ describe('User Effects', () => { ); }); it('should call the api service when Update event is called', done => { - const action = new UpdateUser({ + const action = updateUser({ user: { firstName: 'Patricia', } as User, @@ -344,8 +344,8 @@ describe('User Effects', () => { it('should dispatch a UpdateUserSuccess action on successful user update', () => { const user = { firstName: 'Patricia' } as User; - const action = new UpdateUser({ user, successMessage: 'success' }); - const completion = new UpdateUserSuccess({ user, successMessage: 'success' }); + const action = updateUser({ user, successMessage: 'success' }); + const completion = updateUserSuccess({ user, successMessage: 'success' }); actions$ = hot('-a', { a: action }); const expected$ = cold('-b', { b: completion }); @@ -356,8 +356,8 @@ describe('User Effects', () => { it('should dispatch a SuccessMessage action if update succeeded', () => { // tslint:disable-next-line:ban-types - const action = new UpdateUserSuccess({ user: {} as User, successMessage: 'success' }); - const completion = new DisplaySuccessMessage({ message: 'success' }); + const action = updateUserSuccess({ user: {} as User, successMessage: 'success' }); + const completion = displaySuccessMessage({ message: 'success' }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-b-b-b', { b: completion }); @@ -370,8 +370,8 @@ describe('User Effects', () => { const error = { status: 401, headers: new HttpHeaders().set('error-key', 'feld') } as HttpErrorResponse; when(userServiceMock.updateUser(anything())).thenReturn(throwError(error)); - const action = new UpdateUser({ user: {} as User }); - const completion = new UpdateUserFail({ error: HttpErrorMapper.fromError(error) }); + const action = updateUser({ user: {} as User }); + const completion = updateUserFail({ error: HttpErrorMapper.fromError(error) }); actions$ = hot('-a', { a: action }); const expected$ = cold('-b', { b: completion }); @@ -383,7 +383,7 @@ describe('User Effects', () => { describe('updateUserPassword$', () => { beforeEach(() => { store$.dispatch( - new LoginUserSuccess({ + loginUserSuccess({ customer: { customerNo: '4711', isBusinessCustomer: false, @@ -393,7 +393,7 @@ describe('User Effects', () => { ); }); it('should call the api service when UpdateUserPassword is called', done => { - const action = new UpdateUserPassword({ password: '123', currentPassword: '1234' }); + const action = updateUserPassword({ password: '123', currentPassword: '1234' }); actions$ = of(action); @@ -407,8 +407,8 @@ describe('User Effects', () => { const password = '123'; const currentPassword = '1234'; - const action = new UpdateUserPassword({ password, currentPassword }); - const completion = new UpdateUserPasswordSuccess({ + const action = updateUserPassword({ password, currentPassword }); + const completion = updateUserPasswordSuccess({ successMessage: 'account.profile.update_password.message', }); @@ -422,8 +422,8 @@ describe('User Effects', () => { const password = '123'; const currentPassword = '1234'; - const action = new UpdateUserPassword({ password, currentPassword, successMessage: 'success' }); - const completion = new UpdateUserPasswordSuccess({ + const action = updateUserPassword({ password, currentPassword, successMessage: 'success' }); + const completion = updateUserPasswordSuccess({ successMessage: 'success', }); @@ -440,8 +440,8 @@ describe('User Effects', () => { const password = '123'; const currentPassword = '1234'; - const action = new UpdateUserPassword({ password, currentPassword }); - const completion = new UpdateUserPasswordFail({ error: { message: 'invalid' } as HttpError }); + const action = updateUserPassword({ password, currentPassword }); + const completion = updateUserPasswordFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -453,14 +453,14 @@ describe('User Effects', () => { describe('updateCustomer$', () => { beforeEach(() => { store$.dispatch( - new LoginUserSuccess({ + loginUserSuccess({ customer, user: {} as User, }) ); }); it('should call the api service when UpdateCustomer is called for a business customer', done => { - const action = new UpdateCustomer({ + const action = updateCustomer({ customer: { ...customer, companyName: 'OilCorp' }, successMessage: 'success', }); @@ -474,8 +474,8 @@ describe('User Effects', () => { }); it('should dispatch an UpdateCustomerSuccess action on successful customer update', () => { - const action = new UpdateCustomer({ customer, successMessage: 'success' }); - const completion = new UpdateCustomerSuccess({ customer, successMessage: 'success' }); + const action = updateCustomer({ customer, successMessage: 'success' }); + const completion = updateCustomerSuccess({ customer, successMessage: 'success' }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -489,13 +489,13 @@ describe('User Effects', () => { isBusinessCustomer: false, } as Customer; store$.dispatch( - new LoginUserSuccess({ + loginUserSuccess({ customer: privateCustomer, user: {} as User, }) ); - const action = new UpdateCustomer({ customer: privateCustomer, successMessage: 'success' }); + const action = updateCustomer({ customer: privateCustomer, successMessage: 'success' }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('----'); @@ -506,8 +506,8 @@ describe('User Effects', () => { it('should dispatch an UpdateCustomerFail action on failed company update', () => { when(userServiceMock.updateCustomer(anything())).thenReturn(throwError({ message: 'invalid' })); - const action = new UpdateCustomer({ customer }); - const completion = new UpdateCustomerFail({ error: { message: 'invalid' } as HttpError }); + const action = updateCustomer({ customer }); + const completion = updateCustomerFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -526,7 +526,7 @@ describe('User Effects', () => { }); it('should dispatch UserErrorReset action on router navigation if error was set', done => { - store$.dispatch(new LoginUserFail({ error: { message: 'error' } as HttpError })); + store$.dispatch(loginUserFail({ error: { message: 'error' } as HttpError })); // tslint:disable-next-line: no-any actions$ = of(routerNavigatedAction({ payload: {} as any })); @@ -544,7 +544,7 @@ describe('User Effects', () => { of({ user: { email: 'test@intershop.de' } } as CustomerUserType) ); - actions$ = of(new LoadUserByAPIToken({ apiToken: 'dummy' })); + actions$ = of(loadUserByAPIToken({ apiToken: 'dummy' })); effects.loadUserByAPIToken$.subscribe(action => { verify(userServiceMock.signinUserByToken('dummy')).once(); @@ -559,7 +559,7 @@ describe('User Effects', () => { it('should call the user service on LoadUserByAPIToken action and do nothing when failing', () => { when(userServiceMock.signinUserByToken('dummy')).thenReturn(EMPTY); - actions$ = hot('a-a-a-', { a: new LoadUserByAPIToken({ apiToken: 'dummy' }) }); + actions$ = hot('a-a-a-', { a: loadUserByAPIToken({ apiToken: 'dummy' }) }); expect(effects.loadUserByAPIToken$).toBeObservable(cold('------')); }); @@ -568,7 +568,7 @@ describe('User Effects', () => { describe('loadUserPaymentMethods$', () => { beforeEach(() => { store$.dispatch( - new LoginUserSuccess({ + loginUserSuccess({ customer, user: {} as User, }) @@ -576,7 +576,7 @@ describe('User Effects', () => { }); it('should call the api service when LoadUserPaymentMethods event is called', done => { - const action = new LoadUserPaymentMethods(); + const action = loadUserPaymentMethods(); actions$ = of(action); effects.loadUserPaymentMethods$.subscribe(() => { verify(paymentServiceMock.getUserPaymentMethods(anything())).once(); @@ -585,8 +585,8 @@ describe('User Effects', () => { }); it('should dispatch a LoadUserPaymentMethodsSuccess action on successful', () => { - const action = new LoadUserPaymentMethods(); - const completion = new LoadUserPaymentMethodsSuccess({ paymentMethods: [] }); + const action = loadUserPaymentMethods(); + const completion = loadUserPaymentMethodsSuccess({ paymentMethods: [] }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-b-b-b', { b: completion }); @@ -600,8 +600,8 @@ describe('User Effects', () => { when(paymentServiceMock.getUserPaymentMethods(anything())).thenReturn(throwError(error)); - const action = new LoadUserPaymentMethods(); - const completion = new LoadUserPaymentMethodsFail({ + const action = loadUserPaymentMethods(); + const completion = loadUserPaymentMethodsFail({ error: HttpErrorMapper.fromError(error), }); @@ -614,7 +614,7 @@ describe('User Effects', () => { describe('deleteUserPayment$', () => { beforeEach(() => { store$.dispatch( - new LoginUserSuccess({ + loginUserSuccess({ customer, user: {} as User, }) @@ -622,7 +622,7 @@ describe('User Effects', () => { }); it('should call the api service when DeleteUserPayment event is called', done => { - const action = new DeleteUserPaymentInstrument({ id: 'paymentInstrumentId' }); + const action = deleteUserPaymentInstrument({ id: 'paymentInstrumentId' }); actions$ = of(action); effects.deleteUserPayment$.subscribe(() => { verify(paymentServiceMock.deleteUserPaymentInstrument(customer.customerNo, 'paymentInstrumentId')).once(); @@ -631,10 +631,10 @@ describe('User Effects', () => { }); it('should dispatch a DeleteUserPaymentSuccess action on successful', () => { - const action = new DeleteUserPaymentInstrument({ id: 'paymentInstrumentId' }); - const completion1 = new DeleteUserPaymentInstrumentSuccess(); - const completion2 = new LoadUserPaymentMethods(); - const completion3 = new DisplaySuccessMessage({ + const action = deleteUserPaymentInstrument({ id: 'paymentInstrumentId' }); + const completion1 = deleteUserPaymentInstrumentSuccess(); + const completion2 = loadUserPaymentMethods(); + const completion3 = displaySuccessMessage({ message: 'account.payment.payment_deleted.message', }); @@ -650,8 +650,8 @@ describe('User Effects', () => { when(paymentServiceMock.deleteUserPaymentInstrument(anyString(), anyString())).thenReturn(throwError(error)); - const action = new DeleteUserPaymentInstrument({ id: 'paymentInstrumentId' }); - const completion = new DeleteUserPaymentInstrumentFail({ + const action = deleteUserPaymentInstrument({ id: 'paymentInstrumentId' }); + const completion = deleteUserPaymentInstrumentFail({ error: HttpErrorMapper.fromError(error), }); @@ -668,7 +668,7 @@ describe('User Effects', () => { }; it('should call the api service when RequestPasswordReminder event is called', done => { - const action = new RequestPasswordReminder({ data }); + const action = requestPasswordReminder({ data }); actions$ = of(action); effects.requestPasswordReminder$.subscribe(() => { verify(userServiceMock.requestPasswordReminder(anything())).once(); @@ -677,8 +677,8 @@ describe('User Effects', () => { }); it('should dispatch a RequestPasswordReminderSuccess action on successful', () => { - const action = new RequestPasswordReminder({ data }); - const completion = new RequestPasswordReminderSuccess(); + const action = requestPasswordReminder({ data }); + const completion = requestPasswordReminderSuccess(); actions$ = hot('-a', { a: action }); const expected$ = cold('-b', { b: completion }); @@ -692,8 +692,8 @@ describe('User Effects', () => { when(userServiceMock.requestPasswordReminder(anything())).thenReturn(throwError(error)); - const action = new RequestPasswordReminder({ data }); - const completion = new RequestPasswordReminderFail({ + const action = requestPasswordReminder({ data }); + const completion = requestPasswordReminderFail({ error: HttpErrorMapper.fromError(error), }); diff --git a/src/app/core/store/customer/user/user.effects.ts b/src/app/core/store/customer/user/user.effects.ts index 2d1a461296..083093a538 100644 --- a/src/app/core/store/customer/user/user.effects.ts +++ b/src/app/core/store/customer/user/user.effects.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { routerNavigatedAction } from '@ngrx/router-store'; import { Store, select } from '@ngrx/store'; import { EMPTY, merge, of, race, timer } from 'rxjs'; @@ -27,45 +27,46 @@ import { HttpErrorMapper } from 'ish-core/models/http-error/http-error.mapper'; import { PaymentService } from 'ish-core/services/payment/payment.service'; import { PersonalizationService } from 'ish-core/services/personalization/personalization.service'; import { UserService } from 'ish-core/services/user/user.service'; -import { GeneralError } from 'ish-core/store/core/error'; -import { DisplaySuccessMessage } from 'ish-core/store/core/messages'; +import { generalError } from 'ish-core/store/core/error'; +import { displaySuccessMessage } from 'ish-core/store/core/messages'; import { ofUrl, selectQueryParam } from 'ish-core/store/core/router'; import { mapErrorToAction, mapToPayload, mapToPayloadProperty, whenTruthy } from 'ish-core/utils/operators'; import { - CreateUser, - CreateUserFail, - DeleteUserPaymentInstrument, - DeleteUserPaymentInstrumentFail, - DeleteUserPaymentInstrumentSuccess, - LoadCompanyUser, - LoadCompanyUserFail, - LoadCompanyUserSuccess, - LoadUserByAPIToken, - LoadUserPaymentMethods, - LoadUserPaymentMethodsFail, - LoadUserPaymentMethodsSuccess, - LoginUser, - LoginUserFail, - LoginUserSuccess, - RequestPasswordReminder, - RequestPasswordReminderFail, - RequestPasswordReminderSuccess, - SetPGID, - UpdateCustomer, - UpdateCustomerFail, - UpdateCustomerSuccess, - UpdateUser, - UpdateUserFail, - UpdateUserPassword, - UpdateUserPasswordByPasswordReminder, - UpdateUserPasswordByPasswordReminderFail, - UpdateUserPasswordByPasswordReminderSuccess, - UpdateUserPasswordFail, - UpdateUserPasswordSuccess, - UpdateUserSuccess, - UserActionTypes, - UserErrorReset, + createUser, + createUserFail, + deleteUserPaymentInstrument, + deleteUserPaymentInstrumentFail, + deleteUserPaymentInstrumentSuccess, + loadCompanyUser, + loadCompanyUserFail, + loadCompanyUserSuccess, + loadUserByAPIToken, + loadUserPaymentMethods, + loadUserPaymentMethodsFail, + loadUserPaymentMethodsSuccess, + loginUser, + loginUserFail, + loginUserSuccess, + logoutUser, + requestPasswordReminder, + requestPasswordReminderFail, + requestPasswordReminderSuccess, + resetAPIToken, + setPGID, + updateCustomer, + updateCustomerFail, + updateCustomerSuccess, + updateUser, + updateUserFail, + updateUserPassword, + updateUserPasswordByPasswordReminder, + updateUserPasswordByPasswordReminderFail, + updateUserPasswordByPasswordReminderSuccess, + updateUserPasswordFail, + updateUserPasswordSuccess, + updateUserSuccess, + userErrorReset, } from './user.actions'; import { getLoggedInCustomer, getLoggedInUser, getUserError } from './user.selectors'; @@ -80,273 +81,289 @@ export class UserEffects { private router: Router ) {} - @Effect() - loginUser$ = this.actions$.pipe( - ofType(UserActionTypes.LoginUser), - mapToPayloadProperty('credentials'), - exhaustMap(credentials => - this.userService.signinUser(credentials).pipe( - map(data => new LoginUserSuccess(data)), - // tslint:disable-next-line:ban - catchError(error => - of( - error.headers.has('error-key') - ? new LoginUserFail({ error: HttpErrorMapper.fromError(error) }) - : new GeneralError({ error: HttpErrorMapper.fromError(error) }) + loginUser$ = createEffect(() => + this.actions$.pipe( + ofType(loginUser), + mapToPayloadProperty('credentials'), + exhaustMap(credentials => + this.userService.signinUser(credentials).pipe( + map(loginUserSuccess), + // tslint:disable-next-line:ban + catchError(error => + of( + error.headers.has('error-key') + ? loginUserFail({ error: HttpErrorMapper.fromError(error) }) + : generalError({ error: HttpErrorMapper.fromError(error) }) + ) ) ) ) ) ); - @Effect() - loadCompanyUser$ = this.actions$.pipe( - ofType(UserActionTypes.LoadCompanyUser), - mergeMap(() => - this.userService.getCompanyUserData().pipe( - map(user => new LoadCompanyUserSuccess({ user })), - mapErrorToAction(LoadCompanyUserFail) + loadCompanyUser$ = createEffect(() => + this.actions$.pipe( + ofType(loadCompanyUser), + mergeMap(() => + this.userService.getCompanyUserData().pipe( + map(user => loadCompanyUserSuccess({ user })), + mapErrorToAction(loadCompanyUserFail) + ) ) ) ); - @Effect({ dispatch: false }) - goToLoginAfterLogoutBySessionTimeout$ = this.actions$.pipe( - ofType(UserActionTypes.ResetAPIToken), - switchMapTo( - race( - // wait for immediate LogoutUser - this.actions$.pipe(ofType(UserActionTypes.LogoutUser)), - // or stop flow - timer(1000).pipe(switchMapTo(EMPTY)) - ) - ), - debounce(() => this.actions$.pipe(debounceTime(2000), first())), - tap(() => { - this.router.navigate(['/login'], { - queryParams: { returnUrl: this.router.url, messageKey: 'session_timeout' }, - }); - }) + goToLoginAfterLogoutBySessionTimeout$ = createEffect( + () => + this.actions$.pipe( + ofType(resetAPIToken), + switchMapTo( + race( + // wait for immediate LogoutUser + this.actions$.pipe(ofType(logoutUser)), + // or stop flow + timer(1000).pipe(switchMapTo(EMPTY)) + ) + ), + debounce(() => this.actions$.pipe(debounceTime(2000), first())), + tap(() => { + this.router.navigate(['/login'], { + queryParams: { returnUrl: this.router.url, messageKey: 'session_timeout' }, + }); + }) + ), + { dispatch: false } ); /** * redirects to the returnUrl after successful login * does not redirect at all, if no returnUrl is defined */ - @Effect({ dispatch: false }) - redirectAfterLogin$ = merge( - this.actions$.pipe( - ofType(UserActionTypes.LoginUserSuccess), - switchMapTo(this.store$.pipe(select(selectQueryParam('returnUrl')), first())), - whenTruthy() - ), - this.store$.pipe( - ofUrl(/^\/login.*/), - select(selectQueryParam('returnUrl')), - map(returnUrl => returnUrl || '/account'), - switchMap(returnUrl => this.store$.pipe(select(getLoggedInUser), whenTruthy(), mapTo(returnUrl))) - ) - ).pipe( - whenTruthy(), - tap(navigateTo => this.router.navigateByUrl(navigateTo)) + redirectAfterLogin$ = createEffect( + () => + merge( + this.actions$.pipe( + ofType(loginUserSuccess), + switchMapTo(this.store$.pipe(select(selectQueryParam('returnUrl')), first())), + whenTruthy() + ), + this.store$.pipe( + ofUrl(/^\/login.*/), + select(selectQueryParam('returnUrl')), + map(returnUrl => returnUrl || '/account'), + switchMap(returnUrl => this.store$.pipe(select(getLoggedInUser), whenTruthy(), mapTo(returnUrl))) + ) + ).pipe( + whenTruthy(), + tap(navigateTo => this.router.navigateByUrl(navigateTo)) + ), + { dispatch: false } ); - @Effect() - createUser$ = this.actions$.pipe( - ofType(UserActionTypes.CreateUser), - mapToPayload(), - mergeMap((data: CustomerRegistrationType) => - this.userService.createUser(data).pipe( - // TODO:see #IS-22750 - user should actually be logged in after registration - map(() => new LoginUser({ credentials: data.credentials })), - // tslint:disable-next-line:ban - catchError(error => - of( - error.headers.has('error-key') - ? new CreateUserFail({ error: HttpErrorMapper.fromError(error) }) - : new GeneralError({ error: HttpErrorMapper.fromError(error) }) + createUser$ = createEffect(() => + this.actions$.pipe( + ofType(createUser), + mapToPayload(), + mergeMap((data: CustomerRegistrationType) => + this.userService.createUser(data).pipe( + // TODO:see #IS-22750 - user should actually be logged in after registration + map(() => loginUser({ credentials: data.credentials })), + // tslint:disable-next-line:ban + catchError(error => + of( + error.headers.has('error-key') + ? createUserFail({ error: HttpErrorMapper.fromError(error) }) + : generalError({ error: HttpErrorMapper.fromError(error) }) + ) ) ) ) ) ); - @Effect() - updateUser$ = this.actions$.pipe( - ofType(UserActionTypes.UpdateUser), - mapToPayload(), - withLatestFrom(this.store$.pipe(select(getLoggedInCustomer))), - concatMap(([payload, customer]) => - this.userService.updateUser({ user: payload.user, customer }).pipe( - tap(() => { - if (payload.successRouterLink) { - this.router.navigateByUrl(payload.successRouterLink); - } - }), - map(changedUser => new UpdateUserSuccess({ user: changedUser, successMessage: payload.successMessage })), - mapErrorToAction(UpdateUserFail) + updateUser$ = createEffect(() => + this.actions$.pipe( + ofType(updateUser), + mapToPayload(), + withLatestFrom(this.store$.pipe(select(getLoggedInCustomer))), + concatMap(([payload, customer]) => + this.userService.updateUser({ user: payload.user, customer }).pipe( + tap(() => { + if (payload.successRouterLink) { + this.router.navigateByUrl(payload.successRouterLink); + } + }), + map(changedUser => updateUserSuccess({ user: changedUser, successMessage: payload.successMessage })), + mapErrorToAction(updateUserFail) + ) ) ) ); - @Effect() - updateUserPassword$ = this.actions$.pipe( - ofType(UserActionTypes.UpdateUserPassword), - mapToPayload(), - withLatestFrom(this.store$.pipe(select(getLoggedInCustomer))), - withLatestFrom(this.store$.pipe(select(getLoggedInUser))), - concatMap(([[payload, customer], user]) => - this.userService.updateUserPassword(customer, user, payload.password, payload.currentPassword).pipe( - tap(() => this.router.navigateByUrl('/account/profile')), - mapTo( - new UpdateUserPasswordSuccess({ - successMessage: payload.successMessage || 'account.profile.update_password.message', - }) - ), - mapErrorToAction(UpdateUserPasswordFail) + updateUserPassword$ = createEffect(() => + this.actions$.pipe( + ofType(updateUserPassword), + mapToPayload(), + withLatestFrom(this.store$.pipe(select(getLoggedInCustomer))), + withLatestFrom(this.store$.pipe(select(getLoggedInUser))), + concatMap(([[payload, customer], user]) => + this.userService.updateUserPassword(customer, user, payload.password, payload.currentPassword).pipe( + tap(() => this.router.navigateByUrl('/account/profile')), + mapTo( + updateUserPasswordSuccess({ + successMessage: payload.successMessage || 'account.profile.update_password.message', + }) + ), + mapErrorToAction(updateUserPasswordFail) + ) ) ) ); - @Effect() - updateCustomer$ = this.actions$.pipe( - ofType(UserActionTypes.UpdateCustomer), - mapToPayload(), - withLatestFrom(this.store$.pipe(select(getLoggedInCustomer))), - filter(([, loggedInCustomer]) => !!loggedInCustomer && loggedInCustomer.isBusinessCustomer), - concatMap(([payload]) => - this.userService.updateCustomer(payload.customer).pipe( - tap(() => { - if (payload.successRouterLink) { - this.router.navigateByUrl(payload.successRouterLink); - } - }), - map( - changedCustomer => - new UpdateCustomerSuccess({ customer: changedCustomer, successMessage: payload.successMessage }) - ), - mapErrorToAction(UpdateCustomerFail) + updateCustomer$ = createEffect(() => + this.actions$.pipe( + ofType(updateCustomer), + mapToPayload(), + withLatestFrom(this.store$.pipe(select(getLoggedInCustomer))), + filter(([, loggedInCustomer]) => !!loggedInCustomer && loggedInCustomer.isBusinessCustomer), + concatMap(([payload]) => + this.userService.updateCustomer(payload.customer).pipe( + tap(() => { + if (payload.successRouterLink) { + this.router.navigateByUrl(payload.successRouterLink); + } + }), + map(changedCustomer => + updateCustomerSuccess({ customer: changedCustomer, successMessage: payload.successMessage }) + ), + mapErrorToAction(updateCustomerFail) + ) ) ) ); - @Effect() - displayUpdateUserSuccessMessage$ = this.actions$.pipe( - ofType( - UserActionTypes.UpdateUserPasswordSuccess, - UserActionTypes.UpdateUserSuccess, - UserActionTypes.UpdateCustomerSuccess - ), - mapToPayloadProperty('successMessage'), - filter(successMessage => !!successMessage), - map( - successMessage => - new DisplaySuccessMessage({ + displayUpdateUserSuccessMessage$ = createEffect(() => + this.actions$.pipe( + ofType(updateUserPasswordSuccess, updateUserSuccess, updateCustomerSuccess), + mapToPayloadProperty('successMessage'), + filter(successMessage => !!successMessage), + map(successMessage => + displaySuccessMessage({ message: successMessage, }) + ) ) ); - @Effect() - resetUserError$ = this.actions$.pipe( - ofType(routerNavigatedAction), - withLatestFrom(this.store$.pipe(select(getUserError))), - filter(([, error]) => !!error), - mapTo(new UserErrorReset()) + resetUserError$ = createEffect(() => + this.actions$.pipe( + ofType(routerNavigatedAction), + withLatestFrom(this.store$.pipe(select(getUserError))), + filter(([, error]) => !!error), + mapTo(userErrorReset()) + ) ); - @Effect() - loadCompanyUserAfterLogin$ = this.actions$.pipe( - ofType(UserActionTypes.LoginUserSuccess), - mapToPayload(), - filter(payload => payload.customer.isBusinessCustomer), - mapTo(new LoadCompanyUser()) + loadCompanyUserAfterLogin$ = createEffect(() => + this.actions$.pipe( + ofType(loginUserSuccess), + mapToPayload(), + filter(payload => payload.customer.isBusinessCustomer), + mapTo(loadCompanyUser()) + ) ); - @Effect() - loadUserByAPIToken$ = this.actions$.pipe( - ofType(UserActionTypes.LoadUserByAPIToken), - mapToPayloadProperty('apiToken'), - concatMap(apiToken => this.userService.signinUserByToken(apiToken).pipe(map(user => new LoginUserSuccess(user)))) + loadUserByAPIToken$ = createEffect(() => + this.actions$.pipe( + ofType(loadUserByAPIToken), + mapToPayloadProperty('apiToken'), + concatMap(apiToken => this.userService.signinUserByToken(apiToken).pipe(map(loginUserSuccess))) + ) ); - @Effect() - fetchPGID$ = this.actions$.pipe( - ofType(UserActionTypes.LoginUserSuccess), - switchMap(() => - this.personalizationService.getPGID().pipe( - map(pgid => new SetPGID({ pgid })), - // tslint:disable-next-line:ban - catchError(() => EMPTY) + fetchPGID$ = createEffect(() => + this.actions$.pipe( + ofType(loginUserSuccess), + switchMap(() => + this.personalizationService.getPGID().pipe( + map(pgid => setPGID({ pgid })), + // tslint:disable-next-line:ban + catchError(() => EMPTY) + ) ) ) ); - @Effect() - loadUserPaymentMethods$ = this.actions$.pipe( - ofType(UserActionTypes.LoadUserPaymentMethods), - withLatestFrom(this.store$.pipe(select(getLoggedInCustomer))), - filter(([, customer]) => !!customer), - concatMap(([, customer]) => - this.paymentService.getUserPaymentMethods(customer).pipe( - map(result => new LoadUserPaymentMethodsSuccess({ paymentMethods: result })), - mapErrorToAction(LoadUserPaymentMethodsFail) + loadUserPaymentMethods$ = createEffect(() => + this.actions$.pipe( + ofType(loadUserPaymentMethods), + withLatestFrom(this.store$.pipe(select(getLoggedInCustomer))), + filter(([, customer]) => !!customer), + concatMap(([, customer]) => + this.paymentService.getUserPaymentMethods(customer).pipe( + map(result => loadUserPaymentMethodsSuccess({ paymentMethods: result })), + mapErrorToAction(loadUserPaymentMethodsFail) + ) ) ) ); - @Effect() - deleteUserPayment$ = this.actions$.pipe( - ofType(UserActionTypes.DeleteUserPaymentInstrument), - mapToPayloadProperty('id'), - withLatestFrom(this.store$.pipe(select(getLoggedInCustomer))), - filter(([, customer]) => !!customer), - concatMap(([id, customer]) => - this.paymentService.deleteUserPaymentInstrument(customer.customerNo, id).pipe( - concatMapTo([ - new DeleteUserPaymentInstrumentSuccess(), - new LoadUserPaymentMethods(), - new DisplaySuccessMessage({ - message: 'account.payment.payment_deleted.message', - }), - ]), - mapErrorToAction(DeleteUserPaymentInstrumentFail) + deleteUserPayment$ = createEffect(() => + this.actions$.pipe( + ofType(deleteUserPaymentInstrument), + mapToPayloadProperty('id'), + withLatestFrom(this.store$.pipe(select(getLoggedInCustomer))), + filter(([, customer]) => !!customer), + concatMap(([id, customer]) => + this.paymentService.deleteUserPaymentInstrument(customer.customerNo, id).pipe( + concatMapTo([ + deleteUserPaymentInstrumentSuccess(), + loadUserPaymentMethods(), + displaySuccessMessage({ + message: 'account.payment.payment_deleted.message', + }), + ]), + mapErrorToAction(deleteUserPaymentInstrumentFail) + ) ) ) ); - @Effect() - requestPasswordReminder$ = this.actions$.pipe( - ofType(UserActionTypes.RequestPasswordReminder), - mapToPayloadProperty('data'), - concatMap(data => - this.userService.requestPasswordReminder(data).pipe( - map(() => new RequestPasswordReminderSuccess()), - mapErrorToAction(RequestPasswordReminderFail) + requestPasswordReminder$ = createEffect(() => + this.actions$.pipe( + ofType(requestPasswordReminder), + mapToPayloadProperty('data'), + concatMap(data => + this.userService + .requestPasswordReminder(data) + .pipe(map(requestPasswordReminderSuccess), mapErrorToAction(requestPasswordReminderFail)) ) ) ); - @Effect() - updateUserPasswordByPasswordReminder$ = this.actions$.pipe( - ofType(UserActionTypes.UpdateUserPasswordByPasswordReminder), - mapToPayload(), - concatMap(data => - this.userService.updateUserPasswordByReminder(data).pipe( - map(() => new UpdateUserPasswordByPasswordReminderSuccess()), - mapErrorToAction(UpdateUserPasswordByPasswordReminderFail) + updateUserPasswordByPasswordReminder$ = createEffect(() => + this.actions$.pipe( + ofType(updateUserPasswordByPasswordReminder), + mapToPayload(), + concatMap(data => + this.userService + .updateUserPasswordByReminder(data) + .pipe( + map(updateUserPasswordByPasswordReminderSuccess), + mapErrorToAction(updateUserPasswordByPasswordReminderFail) + ) ) ) ); - @Effect() - updateUserPasswordByPasswordReminderSuccess$ = this.actions$.pipe( - ofType(UserActionTypes.UpdateUserPasswordByPasswordReminderSuccess), - map( - () => - new DisplaySuccessMessage({ + updateUserPasswordByPasswordReminderSuccess$ = createEffect(() => + this.actions$.pipe( + ofType(updateUserPasswordByPasswordReminderSuccess), + map(() => + displaySuccessMessage({ message: 'account.profile.update_password.message', }) + ) ) ); } diff --git a/src/app/core/store/customer/user/user.reducer.spec.ts b/src/app/core/store/customer/user/user.reducer.spec.ts index 3a974f4f97..1cfd6e8033 100644 --- a/src/app/core/store/customer/user/user.reducer.spec.ts +++ b/src/app/core/store/customer/user/user.reducer.spec.ts @@ -5,34 +5,42 @@ import { PaymentMethod } from 'ish-core/models/payment-method/payment-method.mod import { User } from 'ish-core/models/user/user.model'; import { - CreateUserFail, - DeleteUserPaymentInstrument, - DeleteUserPaymentInstrumentFail, - DeleteUserPaymentInstrumentSuccess, - LoadCompanyUserFail, - LoadCompanyUserSuccess, - LoadUserPaymentMethods, - LoadUserPaymentMethodsFail, - LoadUserPaymentMethodsSuccess, - LoginUser, - LoginUserFail, - LoginUserSuccess, - LogoutUser, - RequestPasswordReminder, - RequestPasswordReminderFail, - RequestPasswordReminderSuccess, - ResetPasswordReminder, - UpdateCustomer, - UpdateCustomerFail, - UpdateCustomerSuccess, - UpdateUser, - UpdateUserFail, - UpdateUserPassword, - UpdateUserPasswordFail, - UpdateUserPasswordSuccess, - UpdateUserSuccess, - UserAction, - UserErrorReset, + createUser, + createUserFail, + deleteUserPaymentInstrument, + deleteUserPaymentInstrumentFail, + deleteUserPaymentInstrumentSuccess, + loadCompanyUser, + loadCompanyUserFail, + loadCompanyUserSuccess, + loadUserByAPIToken, + loadUserPaymentMethods, + loadUserPaymentMethodsFail, + loadUserPaymentMethodsSuccess, + loginUser, + loginUserFail, + loginUserSuccess, + logoutUser, + requestPasswordReminder, + requestPasswordReminderFail, + requestPasswordReminderSuccess, + resetAPIToken, + resetPasswordReminder, + setAPIToken, + setPGID, + updateCustomer, + updateCustomerFail, + updateCustomerSuccess, + updateUser, + updateUserFail, + updateUserPassword, + updateUserPasswordByPasswordReminder, + updateUserPasswordByPasswordReminderFail, + updateUserPasswordByPasswordReminderSuccess, + updateUserPasswordFail, + updateUserPasswordSuccess, + updateUserSuccess, + userErrorReset, } from './user.actions'; import { initialState, userReducer } from './user.reducer'; @@ -73,13 +81,93 @@ describe('User Reducer', () => { describe('reducer', () => { it('should return initial state when undefined state is supplied', () => { - const newState = userReducer(undefined, {} as UserAction); + const newState = userReducer( + undefined, + {} as ReturnType< + | typeof loginUser + | typeof loginUserFail + | typeof loginUserSuccess + | typeof setAPIToken + | typeof resetAPIToken + | typeof loadCompanyUser + | typeof loadCompanyUserFail + | typeof loadCompanyUserSuccess + | typeof logoutUser + | typeof createUser + | typeof createUserFail + | typeof updateUser + | typeof updateUserSuccess + | typeof updateUserFail + | typeof updateUserPassword + | typeof updateUserPasswordSuccess + | typeof updateUserPasswordFail + | typeof updateCustomer + | typeof updateCustomerSuccess + | typeof updateCustomerFail + | typeof userErrorReset + | typeof loadUserByAPIToken + | typeof setPGID + | typeof loadUserPaymentMethods + | typeof loadUserPaymentMethodsFail + | typeof loadUserPaymentMethodsSuccess + | typeof deleteUserPaymentInstrument + | typeof deleteUserPaymentInstrumentFail + | typeof deleteUserPaymentInstrumentSuccess + | typeof requestPasswordReminder + | typeof requestPasswordReminderSuccess + | typeof requestPasswordReminderFail + | typeof resetPasswordReminder + | typeof updateUserPasswordByPasswordReminder + | typeof updateUserPasswordByPasswordReminderSuccess + | typeof updateUserPasswordByPasswordReminderFail + > + ); expect(newState).toEqual(initialState); }); it('should return initial state when undefined action is supplied', () => { - const newState = userReducer(initialState, {} as UserAction); + const newState = userReducer( + initialState, + {} as ReturnType< + | typeof loginUser + | typeof loginUserFail + | typeof loginUserSuccess + | typeof setAPIToken + | typeof resetAPIToken + | typeof loadCompanyUser + | typeof loadCompanyUserFail + | typeof loadCompanyUserSuccess + | typeof logoutUser + | typeof createUser + | typeof createUserFail + | typeof updateUser + | typeof updateUserSuccess + | typeof updateUserFail + | typeof updateUserPassword + | typeof updateUserPasswordSuccess + | typeof updateUserPasswordFail + | typeof updateCustomer + | typeof updateCustomerSuccess + | typeof updateCustomerFail + | typeof userErrorReset + | typeof loadUserByAPIToken + | typeof setPGID + | typeof loadUserPaymentMethods + | typeof loadUserPaymentMethodsFail + | typeof loadUserPaymentMethodsSuccess + | typeof deleteUserPaymentInstrument + | typeof deleteUserPaymentInstrumentFail + | typeof deleteUserPaymentInstrumentSuccess + | typeof requestPasswordReminder + | typeof requestPasswordReminderSuccess + | typeof requestPasswordReminderFail + | typeof resetPasswordReminder + | typeof updateUserPasswordByPasswordReminder + | typeof updateUserPasswordByPasswordReminderSuccess + | typeof updateUserPasswordByPasswordReminderFail + > + ); expect(newState).toEqual(initialState); }); @@ -87,19 +175,19 @@ describe('User Reducer', () => { describe('LoginUser actions', () => { it('should set initial when LoginUser action is reduced', () => { - const newState = userReducer(initialState, new LoginUser({ credentials: { login: 'dummy', password: 'dummy' } })); + const newState = userReducer(initialState, loginUser({ credentials: { login: 'dummy', password: 'dummy' } })); expect(newState).toEqual(initialState); }); it('should set customer and authorized when LoginUserSuccess action is reduced', () => { - const newState = userReducer(initialState, new LoginUserSuccess({ customer, user })); + const newState = userReducer(initialState, loginUserSuccess({ customer, user })); expect(newState).toEqual({ ...initialState, customer, user, authorized: true }); }); it('should set user when LoginUserSuccess action is reduced for a private customer', () => { - const newState = userReducer(initialState, new LoginUserSuccess({ customer, user })); + const newState = userReducer(initialState, loginUserSuccess({ customer, user })); expect(newState.customer.isBusinessCustomer).toEqual(customer.isBusinessCustomer); expect(newState.user.firstName).toEqual(user.firstName); @@ -108,25 +196,25 @@ describe('User Reducer', () => { it('should set error when LoginUserFail action is reduced', () => { const error = { status: 500, headers: { 'error-key': 'error' } as HttpHeader } as HttpError; - const newState = userReducer(initialState, new LoginUserFail({ error })); + const newState = userReducer(initialState, loginUserFail({ error })); expect(newState).toEqual({ ...initialState, error }); }); it('should set error when LoginUserFail action is reduced and error is resetted after reset action', () => { const error = { status: 500, headers: { 'error-key': 'error' } as HttpHeader } as HttpError; - let newState = userReducer(initialState, new LoginUserFail({ error })); + let newState = userReducer(initialState, loginUserFail({ error })); expect(newState).toEqual({ ...initialState, error }); - newState = userReducer(newState, new UserErrorReset()); + newState = userReducer(newState, userErrorReset()); expect(newState.error).toBeUndefined(); }); it('should unset authorized and customer when reducing LoginUser', () => { const oldState = { ...initialState, customer, user, authorized: true }; - const newState = userReducer(oldState, new LoginUser({ credentials: { login: 'dummy', password: 'dummy' } })); + const newState = userReducer(oldState, loginUser({ credentials: { login: 'dummy', password: 'dummy' } })); expect(newState).toEqual({ ...initialState, customer: undefined, user: undefined, authorized: false }); }); @@ -136,7 +224,7 @@ describe('User Reducer', () => { it('should unset authorized and customer when reducing LogoutUser', () => { const oldState = { ...initialState, customer, authorized: true }; - const newState = userReducer(oldState, new LogoutUser()); + const newState = userReducer(oldState, logoutUser()); expect(newState).toEqual({ ...initialState, customer: undefined, user: undefined, authorized: false }); }); @@ -144,14 +232,14 @@ describe('User Reducer', () => { describe('LoadCompanyUser actions', () => { it('should set user when LoadCompanyUserSuccess action is reduced', () => { - const newState = userReducer(initialState, new LoadCompanyUserSuccess({ user })); + const newState = userReducer(initialState, loadCompanyUserSuccess({ user })); expect(newState).toEqual({ ...initialState, user }); }); it('should set error when LoadCompanyUserFail action is reduced', () => { const error = { message: 'invalid' } as HttpError; - const action = new LoadCompanyUserFail({ error }); + const action = loadCompanyUserFail({ error }); const state = userReducer(initialState, action); expect(state.error).toEqual(error); @@ -161,7 +249,7 @@ describe('User Reducer', () => { describe('Create user actions', () => { it('should set error when CreateUserFail action is reduced', () => { const error = { message: 'invalid' } as HttpError; - const action = new CreateUserFail({ error }); + const action = createUserFail({ error }); const state = userReducer(initialState, action); expect(state.error).toEqual(error); @@ -170,7 +258,7 @@ describe('User Reducer', () => { describe('Update user actions', () => { it('should loading to true when UpdateUser action is reduced', () => { - const action = new UpdateUser({ user: {} as User }); + const action = updateUser({ user: {} as User }); const state = userReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -181,7 +269,7 @@ describe('User Reducer', () => { firstName: 'test', } as User; - const action = new UpdateUserSuccess({ user: changedUser as User, successMessage: 'success' }); + const action = updateUserSuccess({ user: changedUser as User, successMessage: 'success' }); const state = userReducer(initialState, action); expect(state.user).toEqual(changedUser); @@ -190,7 +278,7 @@ describe('User Reducer', () => { it('should set error and set loading to false when UpdateUserFail is reduced', () => { const error = { message: 'invalid' } as HttpError; - const action = new UpdateUserFail({ error }); + const action = updateUserFail({ error }); const state = userReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -200,14 +288,14 @@ describe('User Reducer', () => { describe('Update user password actions', () => { it('should loading to true when UpdateUserPassword action is reduced', () => { - const action = new UpdateUserPassword({ password: '123', currentPassword: '1234' }); + const action = updateUserPassword({ password: '123', currentPassword: '1234' }); const state = userReducer(initialState, action); expect(state.loading).toBeTrue(); }); it('should set successMessage and reset loading when UpdateUserPasswordSuccess is reduced', () => { - const action = new UpdateUserPasswordSuccess({ successMessage: 'success' }); + const action = updateUserPasswordSuccess({ successMessage: 'success' }); const state = userReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -215,7 +303,7 @@ describe('User Reducer', () => { it('should set error and set loading to false when UpdateUserPasswordFail is reduced', () => { const error = { message: 'invalid' } as HttpError; - const action = new UpdateUserPasswordFail({ error }); + const action = updateUserPasswordFail({ error }); const state = userReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -225,7 +313,7 @@ describe('User Reducer', () => { describe('Update customer actions', () => { it('should loading to true when UpdateCustomer action is reduced', () => { - const action = new UpdateCustomer({ customer: {} as Customer }); + const action = updateCustomer({ customer: {} as Customer }); const state = userReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -236,7 +324,7 @@ describe('User Reducer', () => { companyName: 'test', } as Customer; - const action = new UpdateCustomerSuccess({ customer: changedCustomer, successMessage: 'success' }); + const action = updateCustomerSuccess({ customer: changedCustomer, successMessage: 'success' }); const state = userReducer(initialState, action); expect(state.customer).toEqual(changedCustomer); @@ -245,7 +333,7 @@ describe('User Reducer', () => { it('should set error and set loading to false when UpdateCustomerFail is reduced', () => { const error = { message: 'invalid' } as HttpError; - const action = new UpdateCustomerFail({ error }); + const action = updateCustomerFail({ error }); const state = userReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -255,7 +343,7 @@ describe('User Reducer', () => { describe('LoadUserPaymentMethods action', () => { it('should set loading when reduced', () => { - const action = new LoadUserPaymentMethods(); + const action = loadUserPaymentMethods(); const response = userReducer(initialState, action); expect(response.loading).toBeTrue(); @@ -264,7 +352,7 @@ describe('User Reducer', () => { describe('LoadUserPaymentMethodsSuccess action', () => { it('should set success when reduced', () => { - const action = new LoadUserPaymentMethodsSuccess({ paymentMethods: [{ id: 'ISH_CREDITCARD' } as PaymentMethod] }); + const action = loadUserPaymentMethodsSuccess({ paymentMethods: [{ id: 'ISH_CREDITCARD' } as PaymentMethod] }); const response = userReducer(initialState, action); expect(response.paymentMethods).toHaveLength(1); @@ -275,7 +363,7 @@ describe('User Reducer', () => { describe('LoadUserPaymentMethodsFail action', () => { it('should set error when reduced', () => { const error = { message: 'invalid' } as HttpError; - const action = new LoadUserPaymentMethodsFail({ error }); + const action = loadUserPaymentMethodsFail({ error }); const response = userReducer(initialState, action); expect(response.error).toMatchObject(error); @@ -285,7 +373,7 @@ describe('User Reducer', () => { describe('DeleteUserPayment action', () => { it('should set loading when reduced', () => { - const action = new DeleteUserPaymentInstrument({ id: 'paymentInstrumentId' }); + const action = deleteUserPaymentInstrument({ id: 'paymentInstrumentId' }); const response = userReducer(initialState, action); expect(response.loading).toBeTrue(); @@ -294,7 +382,7 @@ describe('User Reducer', () => { describe('DeleteUserPaymentSuccess action', () => { it('should set loading to false when reduced', () => { - const action = new DeleteUserPaymentInstrumentSuccess(); + const action = deleteUserPaymentInstrumentSuccess(); const response = userReducer(initialState, action); expect(response.loading).toBeFalse(); @@ -304,7 +392,7 @@ describe('User Reducer', () => { describe('DeleteUserPaymentFail action', () => { it('should set error when reduced', () => { const error = { message: 'invalid' } as HttpError; - const action = new DeleteUserPaymentInstrumentFail({ error }); + const action = deleteUserPaymentInstrumentFail({ error }); const response = userReducer(initialState, action); expect(response.error).toMatchObject(error); @@ -322,7 +410,7 @@ describe('User Reducer', () => { describe('RequestPasswordReminderSuccess action', () => { it('should set success when reduced', () => { - const action = new RequestPasswordReminderSuccess(); + const action = requestPasswordReminderSuccess(); const response = userReducer(initialState, action); expect(response.passwordReminderError).toBeUndefined(); @@ -334,7 +422,7 @@ describe('User Reducer', () => { describe('RequestPasswordReminderFail action', () => { it('should set error when reduced', () => { const error = { message: 'invalid' } as HttpError; - const action = new RequestPasswordReminderFail({ error }); + const action = requestPasswordReminderFail({ error }); const response = userReducer(initialState, action); expect(response.passwordReminderError).toMatchObject(error); @@ -345,8 +433,8 @@ describe('User Reducer', () => { describe('RequestPasswordReminderReset action', () => { it('should set success & reset when reduced', () => { - let state = userReducer(initialState, new RequestPasswordReminderSuccess()); - state = userReducer(state, new ResetPasswordReminder()); + let state = userReducer(initialState, requestPasswordReminderSuccess()); + state = userReducer(state, resetPasswordReminder()); expect(state.passwordReminderError).toBeUndefined(); expect(state.passwordReminderSuccess).toBeFalsy(); @@ -361,7 +449,7 @@ describe('User Reducer', () => { firstName: 'Patricia', lastName: 'Miller', }; - const state = userReducer(initialState, new RequestPasswordReminder({ data })); + const state = userReducer(initialState, requestPasswordReminder({ data })); expect(state.passwordReminderError).toBeUndefined(); expect(state.passwordReminderSuccess).toBeFalsy(); diff --git a/src/app/core/store/customer/user/user.reducer.ts b/src/app/core/store/customer/user/user.reducer.ts index fb8b72bdc6..7151712a4f 100644 --- a/src/app/core/store/customer/user/user.reducer.ts +++ b/src/app/core/store/customer/user/user.reducer.ts @@ -1,9 +1,48 @@ +import { createReducer, on } from '@ngrx/store'; + import { Customer } from 'ish-core/models/customer/customer.model'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { PaymentMethod } from 'ish-core/models/payment-method/payment-method.model'; import { User } from 'ish-core/models/user/user.model'; - -import { UserAction, UserActionTypes } from './user.actions'; +import { setErrorOn, setLoadingOn } from 'ish-core/utils/ngrx-creators'; + +import { + createUser, + createUserFail, + deleteUserPaymentInstrument, + deleteUserPaymentInstrumentFail, + deleteUserPaymentInstrumentSuccess, + loadCompanyUser, + loadCompanyUserFail, + loadCompanyUserSuccess, + loadUserPaymentMethods, + loadUserPaymentMethodsFail, + loadUserPaymentMethodsSuccess, + loginUser, + loginUserFail, + loginUserSuccess, + logoutUser, + requestPasswordReminder, + requestPasswordReminderFail, + requestPasswordReminderSuccess, + resetAPIToken, + resetPasswordReminder, + setAPIToken, + setPGID, + updateCustomer, + updateCustomerFail, + updateCustomerSuccess, + updateUser, + updateUserFail, + updateUserPassword, + updateUserPasswordByPasswordReminder, + updateUserPasswordByPasswordReminderFail, + updateUserPasswordByPasswordReminderSuccess, + updateUserPasswordFail, + updateUserPasswordSuccess, + updateUserSuccess, + userErrorReset, +} from './user.actions'; export interface UserState { customer: Customer; @@ -34,197 +73,136 @@ export const initialState: UserState = { lastAuthTokenBeforeLogin: undefined, }; -export function userReducer(state = initialState, action: UserAction): UserState { - switch (action.type) { - case UserActionTypes.UserErrorReset: { - return { - ...state, - error: undefined, - }; - } - - case UserActionTypes.LoginUser: { - return { - ...initialState, - authToken: state.authToken, - lastAuthTokenBeforeLogin: state.authToken, - }; - } - case UserActionTypes.LogoutUser: { - return initialState; - } - - case UserActionTypes.SetAPIToken: { - return { - ...state, - authToken: action.payload.apiToken, - }; - } - - case UserActionTypes.ResetAPIToken: { - return { - ...state, - authToken: undefined, - }; - } - - case UserActionTypes.LoadCompanyUser: - case UserActionTypes.CreateUser: - case UserActionTypes.UpdateUser: - case UserActionTypes.UpdateUserPassword: - case UserActionTypes.UpdateCustomer: - case UserActionTypes.LoadUserPaymentMethods: - case UserActionTypes.DeleteUserPaymentInstrument: { - return { - ...state, - loading: true, - }; - } - - case UserActionTypes.LoginUserFail: - case UserActionTypes.LoadCompanyUserFail: - case UserActionTypes.CreateUserFail: { - const error = action.payload.error; - - return { - ...initialState, - loading: false, - error, - authToken: state.authToken, - }; - } - - case UserActionTypes.UpdateUserFail: - case UserActionTypes.UpdateUserPasswordFail: - case UserActionTypes.UpdateCustomerFail: - case UserActionTypes.LoadUserPaymentMethodsFail: - case UserActionTypes.DeleteUserPaymentInstrumentFail: { - const error = action.payload.error; - - return { - ...state, - loading: false, - error, - }; - } - - case UserActionTypes.LoginUserSuccess: { - const customer = action.payload.customer; - const user = action.payload.user; - - return { - ...state, - authorized: true, - customer, - user, - loading: false, - error: undefined, - }; - } - - case UserActionTypes.LoadCompanyUserSuccess: { - const user = action.payload.user; - - return { - ...state, - user, - loading: false, - error: undefined, - }; - } - - case UserActionTypes.UpdateUserSuccess: { - const user = action.payload.user; - - return { - ...state, - user, - loading: false, - error: undefined, - }; - } - - case UserActionTypes.UpdateUserPasswordSuccess: { - return { - ...state, - loading: false, - error: undefined, - }; - } - - case UserActionTypes.UpdateCustomerSuccess: { - const customer = action.payload.customer; - - return { - ...state, - customer, - loading: false, - error: undefined, - }; - } - - case UserActionTypes.SetPGID: { - return { - ...state, - pgid: action.payload.pgid, - }; - } - - case UserActionTypes.LoadUserPaymentMethodsSuccess: { - return { - ...state, - paymentMethods: action.payload.paymentMethods, - loading: false, - error: undefined, - }; - } - - case UserActionTypes.DeleteUserPaymentInstrumentSuccess: { - return { - ...state, - loading: false, - error: undefined, - }; - } - - case UserActionTypes.UpdateUserPasswordByPasswordReminder: - case UserActionTypes.RequestPasswordReminder: { - return { - ...state, - loading: true, - passwordReminderSuccess: undefined, - passwordReminderError: undefined, - }; - } - - case UserActionTypes.UpdateUserPasswordByPasswordReminderSuccess: - case UserActionTypes.RequestPasswordReminderSuccess: { - return { - ...state, - loading: false, - passwordReminderSuccess: true, - passwordReminderError: undefined, - }; - } - - case UserActionTypes.UpdateUserPasswordByPasswordReminderFail: - case UserActionTypes.RequestPasswordReminderFail: { - return { - ...state, - loading: false, - passwordReminderSuccess: false, - passwordReminderError: action.payload.error, - }; - } - - case UserActionTypes.ResetPasswordReminder: { - return { - ...state, - passwordReminderSuccess: undefined, - passwordReminderError: undefined, - }; - } - } - - return state; -} +export const userReducer = createReducer( + initialState, + on(userErrorReset, (state: UserState) => ({ + ...state, + error: undefined, + })), + on(loginUser, (state: UserState) => ({ + ...initialState, + authToken: state.authToken, + lastAuthTokenBeforeLogin: state.authToken, + })), + on(logoutUser, () => initialState), + on(setAPIToken, (state: UserState, action) => ({ + ...state, + authToken: action.payload.apiToken, + })), + on(resetAPIToken, (state: UserState) => ({ + ...state, + authToken: undefined, + })), + setLoadingOn( + loadCompanyUser, + createUser, + updateUser, + updateUserPassword, + updateCustomer, + loadUserPaymentMethods, + deleteUserPaymentInstrument + ), + on(loginUserFail, loadCompanyUserFail, createUserFail, (state: UserState, action) => { + const error = action.payload.error; + + return { + ...initialState, + loading: false, + error, + authToken: state.authToken, + }; + }), + setErrorOn( + updateUserFail, + updateUserPasswordFail, + updateCustomerFail, + loadUserPaymentMethodsFail, + deleteUserPaymentInstrumentFail + ), + on(loginUserSuccess, (state: UserState, action) => { + const customer = action.payload.customer; + const user = action.payload.user; + + return { + ...state, + authorized: true, + customer, + user, + loading: false, + error: undefined, + }; + }), + on(loadCompanyUserSuccess, (state: UserState, action) => { + const user = action.payload.user; + + return { + ...state, + user, + loading: false, + error: undefined, + }; + }), + on(updateUserSuccess, (state: UserState, action) => { + const user = action.payload.user; + + return { + ...state, + user, + loading: false, + error: undefined, + }; + }), + on(updateUserPasswordSuccess, (state: UserState) => ({ + ...state, + loading: false, + error: undefined, + })), + on(updateCustomerSuccess, (state: UserState, action) => { + const customer = action.payload.customer; + + return { + ...state, + customer, + loading: false, + error: undefined, + }; + }), + on(setPGID, (state: UserState, action) => ({ + ...state, + pgid: action.payload.pgid, + })), + on(loadUserPaymentMethodsSuccess, (state: UserState, action) => ({ + ...state, + paymentMethods: action.payload.paymentMethods, + loading: false, + error: undefined, + })), + on(deleteUserPaymentInstrumentSuccess, (state: UserState) => ({ + ...state, + loading: false, + error: undefined, + })), + on(updateUserPasswordByPasswordReminder, requestPasswordReminder, (state: UserState) => ({ + ...state, + loading: true, + passwordReminderSuccess: undefined, + passwordReminderError: undefined, + })), + on(updateUserPasswordByPasswordReminderSuccess, requestPasswordReminderSuccess, (state: UserState) => ({ + ...state, + loading: false, + passwordReminderSuccess: true, + passwordReminderError: undefined, + })), + on(updateUserPasswordByPasswordReminderFail, requestPasswordReminderFail, (state: UserState, action) => ({ + ...state, + loading: false, + passwordReminderSuccess: false, + passwordReminderError: action.payload.error, + })), + on(resetPasswordReminder, (state: UserState) => ({ + ...state, + passwordReminderSuccess: undefined, + passwordReminderError: undefined, + })) +); diff --git a/src/app/core/store/customer/user/user.selectors.spec.ts b/src/app/core/store/customer/user/user.selectors.spec.ts index 718a88172a..4398d94858 100644 --- a/src/app/core/store/customer/user/user.selectors.spec.ts +++ b/src/app/core/store/customer/user/user.selectors.spec.ts @@ -9,20 +9,20 @@ import { User } from 'ish-core/models/user/user.model'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; import { GeneralStoreModule } from 'ish-core/store/general/general-store.module'; -import { LoadServerConfigSuccess } from 'ish-core/store/general/server-config'; -import { LoadProductSuccess } from 'ish-core/store/shopping/products'; +import { loadServerConfigSuccess } from 'ish-core/store/general/server-config'; +import { loadProductSuccess } from 'ish-core/store/shopping/products'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; import { - LoadCompanyUserSuccess, - LoadUserPaymentMethods, - LoadUserPaymentMethodsSuccess, - LoginUserFail, - LoginUserSuccess, - RequestPasswordReminder, - RequestPasswordReminderFail, - RequestPasswordReminderSuccess, - UpdateUserPassword, + loadCompanyUserSuccess, + loadUserPaymentMethods, + loadUserPaymentMethodsSuccess, + loginUserFail, + loginUserSuccess, + requestPasswordReminder, + requestPasswordReminderFail, + requestPasswordReminderSuccess, + updateUserPassword, } from './user.actions'; import { getLoggedInCustomer, @@ -51,7 +51,7 @@ describe('User Selectors', () => { }); store$ = TestBed.inject(StoreWithSnapshots); - store$.dispatch(new LoadProductSuccess({ product: { sku: 'sku' } as Product })); + store$.dispatch(loadProductSuccess({ product: { sku: 'sku' } as Product })); }); it('should select no customer/user when no event was sent', () => { @@ -67,7 +67,7 @@ describe('User Selectors', () => { it('should select the customer when logging in successfully', () => { const customerNo = 'PC'; store$.dispatch( - new LoginUserSuccess({ + loginUserSuccess({ customer: { isBusinessCustomer: true, customerNo, @@ -87,7 +87,7 @@ describe('User Selectors', () => { const customerNo = 'PC'; store$.dispatch( - new LoginUserSuccess({ + loginUserSuccess({ customer: { customerNo, isBusinessCustomer: false, @@ -108,7 +108,7 @@ describe('User Selectors', () => { it('should not select the user when logging in as company customer successfully', () => { store$.dispatch( - new LoginUserSuccess({ + loginUserSuccess({ customer: { customerNo: 'PC', isBusinessCustomer: true, @@ -125,7 +125,7 @@ describe('User Selectors', () => { it('should select the user when load company user is successful', () => { const firstName = 'test'; - store$.dispatch(new LoadCompanyUserSuccess({ user: { firstName } as User })); + store$.dispatch(loadCompanyUserSuccess({ user: { firstName } as User })); expect(getLoggedInCustomer(store$.state)).toBeUndefined(); expect(getLoggedInUser(store$.state)).toHaveProperty('firstName', firstName); @@ -135,7 +135,7 @@ describe('User Selectors', () => { it('should select no customer and an error when an error event was sent', () => { const error = { status: 401, headers: { 'error-key': 'dummy' } as HttpHeader } as HttpError; - store$.dispatch(new LoginUserFail({ error })); + store$.dispatch(loginUserFail({ error })); expect(getLoggedInCustomer(store$.state)).toBeUndefined(); expect(getLoggedInUser(store$.state)).toBeUndefined(); @@ -148,15 +148,13 @@ describe('User Selectors', () => { describe('loading payment methods', () => { beforeEach(() => { - store$.dispatch(new LoadUserPaymentMethods()); + store$.dispatch(loadUserPaymentMethods()); }); it('should set the state to loading', () => { expect(getUserLoading(store$.state)).toBeTrue(); }); it('should select payment methods when the user has saved payment instruments', () => { - store$.dispatch( - new LoadUserPaymentMethodsSuccess({ paymentMethods: [{ id: 'ISH_CREDITCARD' } as PaymentMethod] }) - ); + store$.dispatch(loadUserPaymentMethodsSuccess({ paymentMethods: [{ id: 'ISH_CREDITCARD' } as PaymentMethod] })); expect(getUserPaymentMethods(store$.state)).toHaveLength(1); expect(getUserLoading(store$.state)).toBeFalse(); @@ -164,7 +162,7 @@ describe('User Selectors', () => { }); it('should select loading when the user starts to update his password', () => { - store$.dispatch(new UpdateUserPassword({ password: '123', currentPassword: '1234' })); + store$.dispatch(updateUserPassword({ password: '123', currentPassword: '1234' })); expect(getUserLoading(store$.state)).toBeTrue(); }); @@ -180,7 +178,7 @@ describe('User Selectors', () => { firstName: 'Patricia', lastName: 'Miller', }; - store$.dispatch(new RequestPasswordReminder({ data })); + store$.dispatch(requestPasswordReminder({ data })); expect(getPasswordReminderError(store$.state)).toBeUndefined(); expect(getPasswordReminderSuccess(store$.state)).toBeUndefined(); @@ -188,7 +186,7 @@ describe('User Selectors', () => { }); it('should success on RequestPasswordReminderSuccess action', () => { - store$.dispatch(new RequestPasswordReminderSuccess()); + store$.dispatch(requestPasswordReminderSuccess()); expect(getPasswordReminderError(store$.state)).toBeUndefined(); expect(getPasswordReminderSuccess(store$.state)).toBeTrue(); expect(getUserLoading(store$.state)).toBeFalse(); @@ -196,7 +194,7 @@ describe('User Selectors', () => { it('should have error on PasswordReminderFail action', () => { const error = { message: 'invalid' } as HttpError; - store$.dispatch(new RequestPasswordReminderFail({ error })); + store$.dispatch(requestPasswordReminderFail({ error })); expect(getPasswordReminderError(store$.state)).toMatchObject(error); expect(getPasswordReminderSuccess(store$.state)).toBeFalse(); expect(getUserLoading(store$.state)).toBeFalse(); @@ -210,7 +208,7 @@ describe('User Selectors', () => { ['net', { isBusinessCustomer: true } as Customer], ])('should be "%s" for user %o', (expected, customer: Customer) => { if (customer) { - store$.dispatch(new LoginUserSuccess({ customer })); + store$.dispatch(loginUserSuccess({ customer })); } expect(getPriceDisplayType(store$.state)).toEqual(expected); }); @@ -219,7 +217,7 @@ describe('User Selectors', () => { describe('B2C', () => { beforeEach(() => { store$.dispatch( - new LoadServerConfigSuccess({ + loadServerConfigSuccess({ config: { pricing: { defaultCustomerTypeForPriceDisplay: 'PRIVATE', @@ -237,7 +235,7 @@ describe('User Selectors', () => { ['net', { isBusinessCustomer: true } as Customer], ])('should be "%s" for user %o', (expected, customer: Customer) => { if (customer) { - store$.dispatch(new LoginUserSuccess({ customer })); + store$.dispatch(loginUserSuccess({ customer })); } expect(getPriceDisplayType(store$.state)).toEqual(expected); }); @@ -246,7 +244,7 @@ describe('User Selectors', () => { describe('B2B', () => { beforeEach(() => { store$.dispatch( - new LoadServerConfigSuccess({ + loadServerConfigSuccess({ config: { pricing: { defaultCustomerTypeForPriceDisplay: 'SMB', @@ -264,7 +262,7 @@ describe('User Selectors', () => { ['net', { isBusinessCustomer: true } as Customer], ])('should be "%s" for user %o', (expected, customer: Customer) => { if (customer) { - store$.dispatch(new LoginUserSuccess({ customer })); + store$.dispatch(loginUserSuccess({ customer })); } expect(getPriceDisplayType(store$.state)).toEqual(expected); }); diff --git a/src/app/core/store/general/contact/contact.actions.ts b/src/app/core/store/general/contact/contact.actions.ts index 43b54c7a5f..c1babcede4 100644 --- a/src/app/core/store/general/contact/contact.actions.ts +++ b/src/app/core/store/general/contact/contact.actions.ts @@ -1,49 +1,19 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; import { Contact } from 'ish-core/models/contact/contact.model'; -import { HttpError } from 'ish-core/models/http-error/http-error.model'; +import { httpError, payload } from 'ish-core/utils/ngrx-creators'; -export enum ContactActionTypes { - LoadContact = '[Contact Internal] Load Contact Subjects', - LoadContactSuccess = '[Contact API] Load Contact Subjects Success', - LoadContactFail = '[Contact API] Load Contact Subjects Fail', - CreateContact = '[Contact] Create Contact Us Request', - CreateContactFail = '[Contact API] Create Contact Us Request Fail', - CreateContactSuccess = '[Contact API] Create Contact Us Request Success', -} +export const loadContact = createAction('[Contact Internal] Load Contact Subjects'); -export class LoadContact implements Action { - readonly type = ContactActionTypes.LoadContact; -} +export const loadContactSuccess = createAction( + '[Contact API] Load Contact Subjects Success', + payload<{ subjects: string[] }>() +); -export class LoadContactSuccess implements Action { - readonly type = ContactActionTypes.LoadContactSuccess; - constructor(public payload: { subjects: string[] }) {} -} +export const loadContactFail = createAction('[Contact API] Load Contact Subjects Fail', httpError()); -export class LoadContactFail implements Action { - readonly type = ContactActionTypes.LoadContactFail; - constructor(public payload: { error: HttpError }) {} -} +export const createContact = createAction('[Contact] Create Contact Us Request', payload<{ contact: Contact }>()); -export class CreateContact implements Action { - readonly type = ContactActionTypes.CreateContact; - constructor(public payload: { contact: Contact }) {} -} +export const createContactFail = createAction('[Contact API] Create Contact Us Request Fail', httpError()); -export class CreateContactFail implements Action { - readonly type = ContactActionTypes.CreateContactFail; - constructor(public payload: { error: HttpError }) {} -} - -export class CreateContactSuccess implements Action { - readonly type = ContactActionTypes.CreateContactSuccess; -} - -export type ContactAction = - | LoadContact - | LoadContactFail - | LoadContactSuccess - | CreateContact - | CreateContactFail - | CreateContactSuccess; +export const createContactSuccess = createAction('[Contact API] Create Contact Us Request Success'); diff --git a/src/app/core/store/general/contact/contact.effects.spec.ts b/src/app/core/store/general/contact/contact.effects.spec.ts index 40694ac80b..4d2f1d4ffe 100644 --- a/src/app/core/store/general/contact/contact.effects.spec.ts +++ b/src/app/core/store/general/contact/contact.effects.spec.ts @@ -9,7 +9,7 @@ import { Contact } from 'ish-core/models/contact/contact.model'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { ContactService } from 'ish-core/services/contact/contact.service'; -import { ContactActionTypes, CreateContact, LoadContactFail, LoadContactSuccess } from './contact.actions'; +import { createContact, loadContact, loadContactFail, loadContactSuccess } from './contact.actions'; import { ContactEffects } from './contact.effects'; describe('Contact Effects', () => { @@ -43,8 +43,8 @@ describe('Contact Effects', () => { describe('loadSubjects$', () => { it('should load all subjects on effects init and dispatch a LoadContactSuccess action', () => { when(contactServiceMock.getContactSubjects()).thenReturn(of(subjects)); - const action = { type: ContactActionTypes.LoadContact } as Action; - const expected = new LoadContactSuccess({ subjects }); + const action = { type: loadContact.type } as Action; + const expected = loadContactSuccess({ subjects }); actions$ = hot('-a-------', { a: action }); @@ -54,8 +54,8 @@ describe('Contact Effects', () => { it('should dispatch a LoadContactFail action if a load error occurs', () => { when(contactServiceMock.getContactSubjects()).thenReturn(throwError({ message: 'error' })); - const action = { type: ContactActionTypes.LoadContact } as Action; - const expected = new LoadContactFail({ error: { message: 'error' } as HttpError }); + const action = { type: loadContact.type } as Action; + const expected = loadContactFail({ error: { message: 'error' } as HttpError }); actions$ = hot('-a', { a: action }); @@ -66,7 +66,7 @@ describe('Contact Effects', () => { describe('createContactRequest$', () => { it('should not dispatch actions when encountering LoadContactData', () => { when(contactServiceMock.createContactRequest(contact)).thenReturn(of()); - const action = new CreateContact({ contact }); + const action = createContact({ contact }); hot('-a-a-a', { a: action }); const expected$ = cold(''); diff --git a/src/app/core/store/general/contact/contact.effects.ts b/src/app/core/store/general/contact/contact.effects.ts index 440b765c26..af58695237 100644 --- a/src/app/core/store/general/contact/contact.effects.ts +++ b/src/app/core/store/general/contact/contact.effects.ts @@ -1,16 +1,17 @@ import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { concatMap, map, mapTo } from 'rxjs/operators'; import { ContactService } from 'ish-core/services/contact/contact.service'; import { mapErrorToAction, mapToPayloadProperty } from 'ish-core/utils/operators'; import { - ContactActionTypes, - CreateContactFail, - CreateContactSuccess, - LoadContactFail, - LoadContactSuccess, + createContact, + createContactFail, + createContactSuccess, + loadContact, + loadContactFail, + loadContactSuccess, } from './contact.actions'; @Injectable() @@ -20,13 +21,14 @@ export class ContactEffects { /** * Load the contact subjects, which the customer can select for his request */ - @Effect() - loadSubjects$ = this.actions$.pipe( - ofType(ContactActionTypes.LoadContact), - concatMap(() => - this.contactService.getContactSubjects().pipe( - map(subjects => new LoadContactSuccess({ subjects })), - mapErrorToAction(LoadContactFail) + loadSubjects$ = createEffect(() => + this.actions$.pipe( + ofType(loadContact), + concatMap(() => + this.contactService.getContactSubjects().pipe( + map(subjects => loadContactSuccess({ subjects })), + mapErrorToAction(loadContactFail) + ) ) ) ); @@ -34,14 +36,15 @@ export class ContactEffects { /** * Send the contact request, when a customer want to get in contact with the shop */ - @Effect() - createContact$ = this.actions$.pipe( - ofType(ContactActionTypes.CreateContact), - mapToPayloadProperty('contact'), - concatMap(contact => - this.contactService - .createContactRequest(contact) - .pipe(mapTo(new CreateContactSuccess()), mapErrorToAction(CreateContactFail)) + createContact$ = createEffect(() => + this.actions$.pipe( + ofType(createContact), + mapToPayloadProperty('contact'), + concatMap(contact => + this.contactService + .createContactRequest(contact) + .pipe(mapTo(createContactSuccess()), mapErrorToAction(createContactFail)) + ) ) ); } diff --git a/src/app/core/store/general/contact/contact.reducer.ts b/src/app/core/store/general/contact/contact.reducer.ts index ff6d12269a..e055b0296e 100644 --- a/src/app/core/store/general/contact/contact.reducer.ts +++ b/src/app/core/store/general/contact/contact.reducer.ts @@ -1,4 +1,13 @@ -import { ContactAction, ContactActionTypes } from './contact.actions'; +import { createReducer, on } from '@ngrx/store'; + +import { + createContact, + createContactFail, + createContactSuccess, + loadContact, + loadContactFail, + loadContactSuccess, +} from './contact.actions'; export interface ContactState { subjects: string[]; @@ -12,53 +21,40 @@ const initialState: ContactState = { success: undefined, }; -export function contactReducer(state = initialState, action: ContactAction): ContactState { - switch (action.type) { - case ContactActionTypes.LoadContact: { - return { - ...state, - loading: true, - success: undefined, - }; - } - case ContactActionTypes.LoadContactFail: { - return { - ...state, - loading: false, - success: undefined, - }; - } - case ContactActionTypes.LoadContactSuccess: { - const { subjects } = action.payload; - return { - ...state, - subjects, - loading: false, - success: undefined, - }; - } - case ContactActionTypes.CreateContact: { - return { - ...state, - loading: true, - success: undefined, - }; - } - case ContactActionTypes.CreateContactFail: { - return { - ...state, - loading: false, - success: false, - }; - } - case ContactActionTypes.CreateContactSuccess: { - return { - ...state, - loading: false, - success: true, - }; - } - } - - return state; -} +export const contactReducer = createReducer( + initialState, + on(loadContact, (state: ContactState) => ({ + ...state, + loading: true, + success: undefined, + })), + on(loadContactFail, (state: ContactState) => ({ + ...state, + loading: false, + success: undefined, + })), + on(loadContactSuccess, (state: ContactState, action) => { + const { subjects } = action.payload; + return { + ...state, + subjects, + loading: false, + success: undefined, + }; + }), + on(createContact, (state: ContactState) => ({ + ...state, + loading: true, + success: undefined, + })), + on(createContactFail, (state: ContactState) => ({ + ...state, + loading: false, + success: false, + })), + on(createContactSuccess, (state: ContactState) => ({ + ...state, + loading: false, + success: true, + })) +); diff --git a/src/app/core/store/general/contact/contact.selectors.spec.ts b/src/app/core/store/general/contact/contact.selectors.spec.ts index 5770837597..936f7378d2 100644 --- a/src/app/core/store/general/contact/contact.selectors.spec.ts +++ b/src/app/core/store/general/contact/contact.selectors.spec.ts @@ -7,12 +7,12 @@ import { GeneralStoreModule } from 'ish-core/store/general/general-store.module' import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; import { - CreateContact, - CreateContactFail, - CreateContactSuccess, - LoadContact, - LoadContactFail, - LoadContactSuccess, + createContact, + createContactFail, + createContactSuccess, + loadContact, + loadContactFail, + loadContactSuccess, } from './contact.actions'; import { getContactLoading, getContactSubjects } from './contact.selectors'; @@ -50,7 +50,7 @@ describe('Contact Selectors', () => { }); describe('CreateContact', () => { - const action = new CreateContact({ contact }); + const action = createContact({ contact }); beforeEach(() => { store$.dispatch(action); @@ -62,7 +62,7 @@ describe('Contact Selectors', () => { describe('and reporting success', () => { beforeEach(() => { - store$.dispatch(new CreateContactSuccess()); + store$.dispatch(createContactSuccess()); }); it('should set loading to false', () => { @@ -72,7 +72,7 @@ describe('Contact Selectors', () => { describe('and reporting failure', () => { beforeEach(() => { - store$.dispatch(new CreateContactFail({ error: { message: 'error' } as HttpError })); + store$.dispatch(createContactFail({ error: { message: 'error' } as HttpError })); }); it('should not have loaded subjects on error', () => { @@ -82,7 +82,7 @@ describe('Contact Selectors', () => { }); describe('loading subjects', () => { beforeEach(() => { - store$.dispatch(new LoadContact()); + store$.dispatch(loadContact()); }); it('should set the state to loading', () => { @@ -91,7 +91,7 @@ describe('Contact Selectors', () => { describe('and reporting success', () => { beforeEach(() => { - store$.dispatch(new LoadContactSuccess({ subjects })); + store$.dispatch(loadContactSuccess({ subjects })); }); it('should set loading to false', () => { @@ -102,7 +102,7 @@ describe('Contact Selectors', () => { describe('and reporting failure', () => { beforeEach(() => { - store$.dispatch(new LoadContactFail({ error: { message: 'error' } as HttpError })); + store$.dispatch(loadContactFail({ error: { message: 'error' } as HttpError })); }); it('should not have loaded subjects on error', () => { diff --git a/src/app/core/store/general/countries/countries.actions.ts b/src/app/core/store/general/countries/countries.actions.ts index 67f968213b..86f6091f3a 100644 --- a/src/app/core/store/general/countries/countries.actions.ts +++ b/src/app/core/store/general/countries/countries.actions.ts @@ -1,26 +1,10 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; import { Country } from 'ish-core/models/country/country.model'; -import { HttpError } from 'ish-core/models/http-error/http-error.model'; +import { httpError, payload } from 'ish-core/utils/ngrx-creators'; -export enum CountryActionTypes { - LoadCountries = '[Core] Load Countries', - LoadCountriesFail = '[Core] Load Countries Fail', - LoadCountriesSuccess = '[Core] Load Countries Success', -} +export const loadCountries = createAction('[Core] Load Countries'); -export class LoadCountries implements Action { - readonly type = CountryActionTypes.LoadCountries; -} +export const loadCountriesFail = createAction('[Core] Load Countries Fail', httpError()); -export class LoadCountriesFail implements Action { - readonly type = CountryActionTypes.LoadCountriesFail; - constructor(public payload: { error: HttpError }) {} -} - -export class LoadCountriesSuccess implements Action { - readonly type = CountryActionTypes.LoadCountriesSuccess; - constructor(public payload: { countries: Country[] }) {} -} - -export type CountryAction = LoadCountries | LoadCountriesFail | LoadCountriesSuccess; +export const loadCountriesSuccess = createAction('[Core] Load Countries Success', payload<{ countries: Country[] }>()); diff --git a/src/app/core/store/general/countries/countries.effects.spec.ts b/src/app/core/store/general/countries/countries.effects.spec.ts index d1d14a1b55..4c192bf047 100644 --- a/src/app/core/store/general/countries/countries.effects.spec.ts +++ b/src/app/core/store/general/countries/countries.effects.spec.ts @@ -11,7 +11,7 @@ import { CountryService } from 'ish-core/services/country/country.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { GeneralStoreModule } from 'ish-core/store/general/general-store.module'; -import { CountryActionTypes, LoadCountriesFail, LoadCountriesSuccess } from './countries.actions'; +import { loadCountries, loadCountriesFail, loadCountriesSuccess } from './countries.actions'; import { CountriesEffects } from './countries.effects'; describe('Countries Effects', () => { @@ -42,8 +42,8 @@ describe('Countries Effects', () => { describe('loadCountries$', () => { it('should load all countries on effects init and dispatch a LoadCountriesSuccess action', () => { - const action = { type: CountryActionTypes.LoadCountries } as Action; - const expected = new LoadCountriesSuccess({ countries }); + const action = { type: loadCountries.type } as Action; + const expected = loadCountriesSuccess({ countries }); actions$ = hot('-a-------', { a: action }); @@ -53,8 +53,8 @@ describe('Countries Effects', () => { it('should dispatch a LoadCountriesFail action if a load error occurs', () => { when(countryServiceMock.getCountries()).thenReturn(throwError({ message: 'error' })); - const action = { type: CountryActionTypes.LoadCountries } as Action; - const expected = new LoadCountriesFail({ error: { message: 'error' } as HttpError }); + const action = { type: loadCountries.type } as Action; + const expected = loadCountriesFail({ error: { message: 'error' } as HttpError }); actions$ = hot('-a', { a: action }); diff --git a/src/app/core/store/general/countries/countries.effects.ts b/src/app/core/store/general/countries/countries.effects.ts index 24ad26acc1..0d30ef0750 100644 --- a/src/app/core/store/general/countries/countries.effects.ts +++ b/src/app/core/store/general/countries/countries.effects.ts @@ -1,27 +1,28 @@ import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { concatMap, filter, map, withLatestFrom } from 'rxjs/operators'; import { CountryService } from 'ish-core/services/country/country.service'; import { mapErrorToAction } from 'ish-core/utils/operators'; -import { CountryActionTypes, LoadCountriesFail, LoadCountriesSuccess } from './countries.actions'; +import { loadCountries, loadCountriesFail, loadCountriesSuccess } from './countries.actions'; import { getAllCountries } from './countries.selectors'; @Injectable() export class CountriesEffects { constructor(private actions$: Actions, private store: Store, private countryService: CountryService) {} - @Effect() - loadCountries$ = this.actions$.pipe( - ofType(CountryActionTypes.LoadCountries), - withLatestFrom(this.store.pipe(select(getAllCountries))), - filter(([, countries]) => !countries.length), - concatMap(() => - this.countryService.getCountries().pipe( - map(countries => new LoadCountriesSuccess({ countries })), - mapErrorToAction(LoadCountriesFail) + loadCountries$ = createEffect(() => + this.actions$.pipe( + ofType(loadCountries), + withLatestFrom(this.store.pipe(select(getAllCountries))), + filter(([, countries]) => !countries.length), + concatMap(() => + this.countryService.getCountries().pipe( + map(countries => loadCountriesSuccess({ countries })), + mapErrorToAction(loadCountriesFail) + ) ) ) ); diff --git a/src/app/core/store/general/countries/countries.reducer.spec.ts b/src/app/core/store/general/countries/countries.reducer.spec.ts index b2a8d50535..526f7812a0 100644 --- a/src/app/core/store/general/countries/countries.reducer.spec.ts +++ b/src/app/core/store/general/countries/countries.reducer.spec.ts @@ -1,13 +1,13 @@ import { Country } from 'ish-core/models/country/country.model'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; -import { CountryAction, LoadCountries, LoadCountriesFail, LoadCountriesSuccess } from './countries.actions'; +import { loadCountries, loadCountriesFail, loadCountriesSuccess } from './countries.actions'; import { countriesReducer, initialState } from './countries.reducer'; describe('Countries Reducer', () => { describe('undefined action', () => { it('should return the default state when previous state is undefined', () => { - const action = {} as CountryAction; + const action = {} as ReturnType; const state = countriesReducer(undefined, action); expect(state).toBe(initialState); @@ -17,7 +17,7 @@ describe('Countries Reducer', () => { describe('LoadCountries actions', () => { describe('LoadCountry action', () => { it('should set loading to true', () => { - const action = new LoadCountries(); + const action = loadCountries(); const state = countriesReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -27,7 +27,7 @@ describe('Countries Reducer', () => { describe('LoadCountriesFail action', () => { it('should set loading to false', () => { - const action = new LoadCountriesFail({ error: {} as HttpError }); + const action = loadCountriesFail({ error: {} as HttpError }); const state = countriesReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -46,7 +46,7 @@ describe('Countries Reducer', () => { }); it('should insert countries if not exist', () => { - const action = new LoadCountriesSuccess({ countries: [country] }); + const action = loadCountriesSuccess({ countries: [country] }); const state = countriesReducer(initialState, action); expect(state.ids).toHaveLength(1); diff --git a/src/app/core/store/general/countries/countries.reducer.ts b/src/app/core/store/general/countries/countries.reducer.ts index 2e9adcca44..8aa9f5f4ae 100644 --- a/src/app/core/store/general/countries/countries.reducer.ts +++ b/src/app/core/store/general/countries/countries.reducer.ts @@ -1,8 +1,10 @@ import { EntityState, createEntityAdapter } from '@ngrx/entity'; +import { createReducer, on } from '@ngrx/store'; import { Country } from 'ish-core/models/country/country.model'; +import { setLoadingOn } from 'ish-core/utils/ngrx-creators'; -import { CountryAction, CountryActionTypes } from './countries.actions'; +import { loadCountries, loadCountriesFail, loadCountriesSuccess } from './countries.actions'; export const countryAdapter = createEntityAdapter({ selectId: country => country.countryCode, @@ -16,31 +18,19 @@ export const initialState: CountriesState = countryAdapter.getInitialState({ loading: false, }); -export function countriesReducer(state = initialState, action: CountryAction): CountriesState { - switch (action.type) { - case CountryActionTypes.LoadCountries: { - return { - ...state, - loading: true, - }; - } - - case CountryActionTypes.LoadCountriesFail: { - return { - ...state, - loading: false, - }; - } - - case CountryActionTypes.LoadCountriesSuccess: { - const { countries } = action.payload; - - return { - ...countryAdapter.setAll(countries, state), - loading: false, - }; - } - } - - return state; -} +export const countriesReducer = createReducer( + initialState, + setLoadingOn(loadCountries), + on(loadCountriesFail, (state: CountriesState) => ({ + ...state, + loading: false, + })), + on(loadCountriesSuccess, (state: CountriesState, action) => { + const { countries } = action.payload; + + return { + ...countryAdapter.setAll(countries, state), + loading: false, + }; + }) +); diff --git a/src/app/core/store/general/countries/countries.selectors.spec.ts b/src/app/core/store/general/countries/countries.selectors.spec.ts index 6cc5a58a3e..27ad173029 100644 --- a/src/app/core/store/general/countries/countries.selectors.spec.ts +++ b/src/app/core/store/general/countries/countries.selectors.spec.ts @@ -6,7 +6,7 @@ import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { GeneralStoreModule } from 'ish-core/store/general/general-store.module'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; -import { LoadCountries, LoadCountriesFail, LoadCountriesSuccess } from './countries.actions'; +import { loadCountries, loadCountriesFail, loadCountriesSuccess } from './countries.actions'; import { getAllCountries, getCountriesLoading } from './countries.selectors'; describe('Countries Selectors', () => { @@ -35,7 +35,7 @@ describe('Countries Selectors', () => { describe('loading countries', () => { beforeEach(() => { - store$.dispatch(new LoadCountries()); + store$.dispatch(loadCountries()); }); it('should set the state to loading', () => { @@ -44,7 +44,7 @@ describe('Countries Selectors', () => { describe('and reporting success', () => { beforeEach(() => { - store$.dispatch(new LoadCountriesSuccess({ countries })); + store$.dispatch(loadCountriesSuccess({ countries })); }); it('should set loading to false', () => { @@ -55,7 +55,7 @@ describe('Countries Selectors', () => { describe('and reporting failure', () => { beforeEach(() => { - store$.dispatch(new LoadCountriesFail({ error: { message: 'error' } as HttpError })); + store$.dispatch(loadCountriesFail({ error: { message: 'error' } as HttpError })); }); it('should not have loaded category on error', () => { diff --git a/src/app/core/store/general/regions/regions.actions.ts b/src/app/core/store/general/regions/regions.actions.ts index fd61205cca..4515f32764 100644 --- a/src/app/core/store/general/regions/regions.actions.ts +++ b/src/app/core/store/general/regions/regions.actions.ts @@ -1,27 +1,10 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; -import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { Region } from 'ish-core/models/region/region.model'; +import { httpError, payload } from 'ish-core/utils/ngrx-creators'; -export enum RegionActionTypes { - LoadRegions = '[Core] Load Regions', - LoadRegionsFail = '[Core] Load Regions Fail', - LoadRegionsSuccess = '[Core] Load Regions Success', -} +export const loadRegions = createAction('[Core] Load Regions', payload<{ countryCode: string }>()); -export class LoadRegions implements Action { - readonly type = RegionActionTypes.LoadRegions; - constructor(public payload: { countryCode: string }) {} -} +export const loadRegionsFail = createAction('[Core] Load Regions Fail', httpError()); -export class LoadRegionsFail implements Action { - readonly type = RegionActionTypes.LoadRegionsFail; - constructor(public payload: { error: HttpError }) {} -} - -export class LoadRegionsSuccess implements Action { - readonly type = RegionActionTypes.LoadRegionsSuccess; - constructor(public payload: { regions: Region[] }) {} -} - -export type RegionAction = LoadRegions | LoadRegionsFail | LoadRegionsSuccess; +export const loadRegionsSuccess = createAction('[Core] Load Regions Success', payload<{ regions: Region[] }>()); diff --git a/src/app/core/store/general/regions/regions.effects.spec.ts b/src/app/core/store/general/regions/regions.effects.spec.ts index 78ac0f001f..322c876f8c 100644 --- a/src/app/core/store/general/regions/regions.effects.spec.ts +++ b/src/app/core/store/general/regions/regions.effects.spec.ts @@ -11,7 +11,7 @@ import { CountryService } from 'ish-core/services/country/country.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { GeneralStoreModule } from 'ish-core/store/general/general-store.module'; -import { LoadRegions, LoadRegionsFail, LoadRegionsSuccess } from './regions.actions'; +import { loadRegions, loadRegionsFail, loadRegionsSuccess } from './regions.actions'; import { RegionsEffects } from './regions.effects'; describe('Regions Effects', () => { @@ -44,7 +44,7 @@ describe('Regions Effects', () => { describe('loadRegions$', () => { it('should call the countryService for loadRegions', done => { - const action = new LoadRegions({ countryCode }); + const action = loadRegions({ countryCode }); actions$ = of(action); effects.loadRegions$.subscribe(() => { @@ -53,8 +53,8 @@ describe('Regions Effects', () => { }); }); it('should map to action of type LoadRegionSuccess', () => { - const action = new LoadRegions({ countryCode }); - const completion = new LoadRegionsSuccess({ regions }); + const action = loadRegions({ countryCode }); + const completion = loadRegionsSuccess({ regions }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -62,9 +62,9 @@ describe('Regions Effects', () => { }); it('should map invalid request to action of type LoadRegionsFail', () => { when(countryServiceMock.getRegionsByCountry(anyString())).thenReturn(throwError({ message: 'invalid' })); - const action = new LoadRegions({ countryCode }); + const action = loadRegions({ countryCode }); const error = { message: 'invalid' } as HttpError; - const completion = new LoadRegionsFail({ error }); + const completion = loadRegionsFail({ error }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); diff --git a/src/app/core/store/general/regions/regions.effects.ts b/src/app/core/store/general/regions/regions.effects.ts index c4a60d22bd..f4dd8cf9ca 100644 --- a/src/app/core/store/general/regions/regions.effects.ts +++ b/src/app/core/store/general/regions/regions.effects.ts @@ -1,28 +1,29 @@ import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { concatMap, filter, map, withLatestFrom } from 'rxjs/operators'; import { CountryService } from 'ish-core/services/country/country.service'; import { mapErrorToAction, mapToPayloadProperty } from 'ish-core/utils/operators'; -import { LoadRegionsFail, LoadRegionsSuccess, RegionActionTypes } from './regions.actions'; +import { loadRegions, loadRegionsFail, loadRegionsSuccess } from './regions.actions'; import { getAllRegions } from './regions.selectors'; @Injectable() export class RegionsEffects { constructor(private actions$: Actions, private store: Store, private countryService: CountryService) {} - @Effect() - loadRegions$ = this.actions$.pipe( - ofType(RegionActionTypes.LoadRegions), - mapToPayloadProperty('countryCode'), - withLatestFrom(this.store.pipe(select(getAllRegions))), - filter(([countryCode, allRegions]) => !allRegions.some(r => r.countryCode === countryCode)), - concatMap(([countryCode]) => - this.countryService.getRegionsByCountry(countryCode).pipe( - map(regions => new LoadRegionsSuccess({ regions })), - mapErrorToAction(LoadRegionsFail) + loadRegions$ = createEffect(() => + this.actions$.pipe( + ofType(loadRegions), + mapToPayloadProperty('countryCode'), + withLatestFrom(this.store.pipe(select(getAllRegions))), + filter(([countryCode, allRegions]) => !allRegions.some(r => r.countryCode === countryCode)), + concatMap(([countryCode]) => + this.countryService.getRegionsByCountry(countryCode).pipe( + map(regions => loadRegionsSuccess({ regions })), + mapErrorToAction(loadRegionsFail) + ) ) ) ); diff --git a/src/app/core/store/general/regions/regions.reducer.spec.ts b/src/app/core/store/general/regions/regions.reducer.spec.ts index c8901926be..fca65b5f67 100644 --- a/src/app/core/store/general/regions/regions.reducer.spec.ts +++ b/src/app/core/store/general/regions/regions.reducer.spec.ts @@ -1,7 +1,7 @@ import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { Region } from 'ish-core/models/region/region.model'; -import { LoadRegions, LoadRegionsFail, LoadRegionsSuccess, RegionAction } from './regions.actions'; +import { loadRegions, loadRegionsFail, loadRegionsSuccess } from './regions.actions'; import { initialState, regionsReducer } from './regions.reducer'; describe('Regions Reducer', () => { @@ -9,7 +9,7 @@ describe('Regions Reducer', () => { describe('undefined action', () => { it('should return the default state when previous state is undefined', () => { - const action = {} as RegionAction; + const action = {} as ReturnType; const state = regionsReducer(undefined, action); expect(state).toBe(initialState); @@ -19,7 +19,7 @@ describe('Regions Reducer', () => { describe('LoadRegions actions', () => { describe('LoadRegion action', () => { it('should set loading to true', () => { - const action = new LoadRegions({ countryCode }); + const action = loadRegions({ countryCode }); const state = regionsReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -29,7 +29,7 @@ describe('Regions Reducer', () => { describe('LoadRegionFail action', () => { it('should set loading to false', () => { - const action = new LoadRegionsFail({ error: {} as HttpError }); + const action = loadRegionsFail({ error: {} as HttpError }); const state = regionsReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -50,7 +50,7 @@ describe('Regions Reducer', () => { }); it('should insert region if not exist', () => { - const action = new LoadRegionsSuccess({ regions: [region] }); + const action = loadRegionsSuccess({ regions: [region] }); const state = regionsReducer(initialState, action); expect(state.ids).toHaveLength(1); diff --git a/src/app/core/store/general/regions/regions.reducer.ts b/src/app/core/store/general/regions/regions.reducer.ts index 8be8a7857e..9642f70c5d 100644 --- a/src/app/core/store/general/regions/regions.reducer.ts +++ b/src/app/core/store/general/regions/regions.reducer.ts @@ -1,8 +1,10 @@ import { EntityState, createEntityAdapter } from '@ngrx/entity'; +import { createReducer, on } from '@ngrx/store'; import { Region } from 'ish-core/models/region/region.model'; +import { setLoadingOn } from 'ish-core/utils/ngrx-creators'; -import { RegionAction, RegionActionTypes } from './regions.actions'; +import { loadRegions, loadRegionsFail, loadRegionsSuccess } from './regions.actions'; export const regionAdapter = createEntityAdapter({ selectId: region => region.id, @@ -16,31 +18,19 @@ export const initialState: RegionsState = regionAdapter.getInitialState({ loading: false, }); -export function regionsReducer(state = initialState, action: RegionAction): RegionsState { - switch (action.type) { - case RegionActionTypes.LoadRegions: { - return { - ...state, - loading: true, - }; - } - - case RegionActionTypes.LoadRegionsFail: { - return { - ...state, - loading: false, - }; - } - - case RegionActionTypes.LoadRegionsSuccess: { - const { regions } = action.payload; - - return { - ...regionAdapter.upsertMany(regions, state), - loading: false, - }; - } - } - - return state; -} +export const regionsReducer = createReducer( + initialState, + setLoadingOn(loadRegions), + on(loadRegionsFail, (state: RegionsState) => ({ + ...state, + loading: false, + })), + on(loadRegionsSuccess, (state: RegionsState, action) => { + const { regions } = action.payload; + + return { + ...regionAdapter.upsertMany(regions, state), + loading: false, + }; + }) +); diff --git a/src/app/core/store/general/regions/regions.selectors.spec.ts b/src/app/core/store/general/regions/regions.selectors.spec.ts index 852353a63a..cfff24c694 100644 --- a/src/app/core/store/general/regions/regions.selectors.spec.ts +++ b/src/app/core/store/general/regions/regions.selectors.spec.ts @@ -6,7 +6,7 @@ import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { GeneralStoreModule } from 'ish-core/store/general/general-store.module'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; -import { LoadRegions, LoadRegionsFail, LoadRegionsSuccess } from './regions.actions'; +import { loadRegions, loadRegionsFail, loadRegionsSuccess } from './regions.actions'; import { getAllRegions, getRegionsByCountryCode, getRegionsLoading } from './regions.selectors'; describe('Regions Selectors', () => { @@ -40,7 +40,7 @@ describe('Regions Selectors', () => { describe('loading regions', () => { beforeEach(() => { - store$.dispatch(new LoadRegions({ countryCode })); + store$.dispatch(loadRegions({ countryCode })); }); it('should set the state to loading', () => { expect(getRegionsLoading(store$.state)).toBeTrue(); @@ -48,7 +48,7 @@ describe('Regions Selectors', () => { describe('and reporting success', () => { beforeEach(() => { - store$.dispatch(new LoadRegionsSuccess({ regions: allRegions })); + store$.dispatch(loadRegionsSuccess({ regions: allRegions })); }); it('should set loading to false', () => { @@ -59,7 +59,7 @@ describe('Regions Selectors', () => { describe('and reporting failure', () => { beforeEach(() => { - store$.dispatch(new LoadRegionsFail({ error: { message: 'error' } as HttpError })); + store$.dispatch(loadRegionsFail({ error: { message: 'error' } as HttpError })); }); it('should not have loaded category on error', () => { diff --git a/src/app/core/store/general/server-config/server-config.actions.ts b/src/app/core/store/general/server-config/server-config.actions.ts index 772e92dafb..23585b5a6f 100644 --- a/src/app/core/store/general/server-config/server-config.actions.ts +++ b/src/app/core/store/general/server-config/server-config.actions.ts @@ -1,26 +1,13 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; -import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { ServerConfig } from 'ish-core/models/server-config/server-config.model'; +import { httpError, payload } from 'ish-core/utils/ngrx-creators'; -export enum ServerConfigActionTypes { - LoadServerConfig = '[Configuration Internal] Get the ICM configuration', - LoadServerConfigSuccess = '[Configuration API] Get the ICM configuration Success', - LoadServerConfigFail = '[Configuration API] Get the ICM configuration Fail', -} +export const loadServerConfig = createAction('[Configuration Internal] Get the ICM configuration'); -export class LoadServerConfig implements Action { - readonly type = ServerConfigActionTypes.LoadServerConfig; -} +export const loadServerConfigSuccess = createAction( + '[Configuration API] Get the ICM configuration Success', + payload<{ config: ServerConfig }>() +); -export class LoadServerConfigSuccess implements Action { - readonly type = ServerConfigActionTypes.LoadServerConfigSuccess; - constructor(public payload: { config: ServerConfig }) {} -} - -export class LoadServerConfigFail implements Action { - readonly type = ServerConfigActionTypes.LoadServerConfigFail; - constructor(public payload: { error: HttpError }) {} -} - -export type ServerConfigAction = LoadServerConfig | LoadServerConfigSuccess | LoadServerConfigFail; +export const loadServerConfigFail = createAction('[Configuration API] Get the ICM configuration Fail', httpError()); diff --git a/src/app/core/store/general/server-config/server-config.effects.spec.ts b/src/app/core/store/general/server-config/server-config.effects.spec.ts index dcc1cbbde4..e67a86713e 100644 --- a/src/app/core/store/general/server-config/server-config.effects.spec.ts +++ b/src/app/core/store/general/server-config/server-config.effects.spec.ts @@ -11,7 +11,7 @@ import { ConfigurationService } from 'ish-core/services/configuration/configurat import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { GeneralStoreModule } from 'ish-core/store/general/general-store.module'; -import { LoadServerConfig, LoadServerConfigFail, LoadServerConfigSuccess } from './server-config.actions'; +import { loadServerConfig, loadServerConfigFail, loadServerConfigSuccess } from './server-config.actions'; import { ServerConfigEffects } from './server-config.effects'; describe('Server Config Effects', () => { @@ -40,7 +40,7 @@ describe('Server Config Effects', () => { it('should trigger the loading of config data on the first page', () => { // tslint:disable-next-line: no-any const action = routerNavigationAction({ payload: {} as any }); - const expected = new LoadServerConfig(); + const expected = loadServerConfig(); actions$ = hot('a', { a: action }); expect(effects.loadServerConfigOnInit$).toBeObservable(cold('a', { a: expected })); @@ -48,7 +48,7 @@ describe('Server Config Effects', () => { it('should not trigger the loading of config data on the second page', () => { store$.dispatch( - new LoadServerConfigSuccess({ + loadServerConfigSuccess({ config: { application: 'intershop.B2CResponsive' }, }) ); @@ -68,8 +68,8 @@ describe('Server Config Effects', () => { }); it('should map to action of type ApplyConfiguration', () => { - const action = new LoadServerConfig(); - const completion = new LoadServerConfigSuccess({ config: {} }); + const action = loadServerConfig(); + const completion = loadServerConfigSuccess({ config: {} }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -80,8 +80,8 @@ describe('Server Config Effects', () => { it('should map invalid request to action of type LoadServerConfigFail', () => { when(configurationServiceMock.getServerConfiguration()).thenReturn(throwError({ message: 'invalid' })); - const action = new LoadServerConfig(); - const completion = new LoadServerConfigFail({ error: { message: 'invalid' } as HttpError }); + const action = loadServerConfig(); + const completion = loadServerConfigFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); diff --git a/src/app/core/store/general/server-config/server-config.effects.ts b/src/app/core/store/general/server-config/server-config.effects.ts index f0b69ce761..9eb4652226 100644 --- a/src/app/core/store/general/server-config/server-config.effects.ts +++ b/src/app/core/store/general/server-config/server-config.effects.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { routerNavigationAction } from '@ngrx/router-store'; import { Store, select } from '@ngrx/store'; import { concatMap, map, mapTo, switchMapTo } from 'rxjs/operators'; @@ -7,12 +7,7 @@ import { concatMap, map, mapTo, switchMapTo } from 'rxjs/operators'; import { ConfigurationService } from 'ish-core/services/configuration/configuration.service'; import { mapErrorToAction, whenFalsy } from 'ish-core/utils/operators'; -import { - LoadServerConfig, - LoadServerConfigFail, - LoadServerConfigSuccess, - ServerConfigActionTypes, -} from './server-config.actions'; +import { loadServerConfig, loadServerConfigFail, loadServerConfigSuccess } from './server-config.actions'; import { isServerConfigurationLoaded } from './server-config.selectors'; @Injectable() @@ -22,21 +17,23 @@ export class ServerConfigEffects { /** * get server configuration on routing event, if it is not already loaded */ - @Effect() - loadServerConfigOnInit$ = this.actions$.pipe( - ofType(routerNavigationAction), - switchMapTo(this.store.pipe(select(isServerConfigurationLoaded))), - whenFalsy(), - mapTo(new LoadServerConfig()) + loadServerConfigOnInit$ = createEffect(() => + this.actions$.pipe( + ofType(routerNavigationAction), + switchMapTo(this.store.pipe(select(isServerConfigurationLoaded))), + whenFalsy(), + mapTo(loadServerConfig()) + ) ); - @Effect() - loadServerConfig$ = this.actions$.pipe( - ofType(ServerConfigActionTypes.LoadServerConfig), - concatMap(() => - this.configService.getServerConfiguration().pipe( - map(config => new LoadServerConfigSuccess({ config })), - mapErrorToAction(LoadServerConfigFail) + loadServerConfig$ = createEffect(() => + this.actions$.pipe( + ofType(loadServerConfig), + concatMap(() => + this.configService.getServerConfiguration().pipe( + map(config => loadServerConfigSuccess({ config })), + mapErrorToAction(loadServerConfigFail) + ) ) ) ); diff --git a/src/app/core/store/general/server-config/server-config.reducer.ts b/src/app/core/store/general/server-config/server-config.reducer.ts index f52011f312..962ea84ca1 100644 --- a/src/app/core/store/general/server-config/server-config.reducer.ts +++ b/src/app/core/store/general/server-config/server-config.reducer.ts @@ -1,6 +1,8 @@ +import { createReducer, on } from '@ngrx/store'; + import { ServerConfig } from 'ish-core/models/server-config/server-config.model'; -import { ServerConfigAction, ServerConfigActionTypes } from './server-config.actions'; +import { loadServerConfigSuccess } from './server-config.actions'; export interface ServerConfigState { _config: ServerConfig; @@ -10,14 +12,9 @@ const initialState: ServerConfigState = { _config: undefined, }; -export function serverConfigReducer(state = initialState, action: ServerConfigAction): ServerConfigState { - switch (action.type) { - case ServerConfigActionTypes.LoadServerConfigSuccess: { - return { - _config: action.payload.config, - }; - } - } - - return state; -} +export const serverConfigReducer = createReducer( + initialState, + on(loadServerConfigSuccess, (_, action) => ({ + _config: action.payload.config, + })) +); diff --git a/src/app/core/store/general/server-config/server-config.selectors.spec.ts b/src/app/core/store/general/server-config/server-config.selectors.spec.ts index eeee0546e0..c3aa7de0e2 100644 --- a/src/app/core/store/general/server-config/server-config.selectors.spec.ts +++ b/src/app/core/store/general/server-config/server-config.selectors.spec.ts @@ -4,7 +4,7 @@ import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { GeneralStoreModule } from 'ish-core/store/general/general-store.module'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; -import { LoadServerConfigSuccess } from './server-config.actions'; +import { loadServerConfigSuccess } from './server-config.actions'; import { getServerConfigParameter, isServerConfigurationLoaded } from './server-config.selectors'; describe('Server Config Selectors', () => { @@ -32,7 +32,7 @@ describe('Server Config Selectors', () => { describe('after setting serverConfig', () => { beforeEach(() => { store$.dispatch( - new LoadServerConfigSuccess({ + loadServerConfigSuccess({ config: { application: { applicationType: 'intershop.B2CResponsive', diff --git a/src/app/core/store/hybrid/hybrid.effects.ts b/src/app/core/store/hybrid/hybrid.effects.ts index 4a93898bcf..0882f985cd 100644 --- a/src/app/core/store/hybrid/hybrid.effects.ts +++ b/src/app/core/store/hybrid/hybrid.effects.ts @@ -1,7 +1,7 @@ import { isPlatformServer } from '@angular/common'; import { Inject, Injectable, Optional, PLATFORM_ID } from '@angular/core'; import { TransferState, makeStateKey } from '@angular/platform-browser'; -import { Actions, Effect } from '@ngrx/effects'; +import { Actions, createEffect } from '@ngrx/effects'; import { filter, take, tap } from 'rxjs/operators'; export const SSR_HYBRID_STATE = makeStateKey('ssrHybrid'); @@ -15,11 +15,14 @@ export class HybridEffects { @Optional() @Inject('SSR_HYBRID') private ssrHybridState: boolean ) {} - @Effect({ dispatch: false }) - propagateSSRHybridPropToTransferState$ = this.actions.pipe( - take(1), - filter(() => isPlatformServer(this.platformId)), - filter(() => !!this.ssrHybridState), - tap(() => this.transferState.set(SSR_HYBRID_STATE, true)) + propagateSSRHybridPropToTransferState$ = createEffect( + () => + this.actions.pipe( + take(1), + filter(() => isPlatformServer(this.platformId)), + filter(() => !!this.ssrHybridState), + tap(() => this.transferState.set(SSR_HYBRID_STATE, true)) + ), + { dispatch: false } ); } diff --git a/src/app/core/store/shopping/categories/categories.actions.ts b/src/app/core/store/shopping/categories/categories.actions.ts index a5b9eeda13..d5fac10c7d 100644 --- a/src/app/core/store/shopping/categories/categories.actions.ts +++ b/src/app/core/store/shopping/categories/categories.actions.ts @@ -1,51 +1,25 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; import { CategoryTree } from 'ish-core/models/category-tree/category-tree.model'; -import { HttpError } from 'ish-core/models/http-error/http-error.model'; +import { httpError, payload } from 'ish-core/utils/ngrx-creators'; -export enum CategoriesActionTypes { - LoadTopLevelCategories = '[Shopping] Load top level categories', - LoadTopLevelCategoriesFail = '[Shopping] Load top level categories fail', - LoadTopLevelCategoriesSuccess = '[Shopping] Load top level categories success', - LoadCategory = '[Shopping] Load Category', - LoadCategoryFail = '[Shopping] Load Category Fail', - LoadCategorySuccess = '[Shopping] Load Category Success', -} +export const loadTopLevelCategories = createAction( + '[Shopping] Load top level categories', + payload<{ depth: number }>() +); -export class LoadTopLevelCategories implements Action { - readonly type = CategoriesActionTypes.LoadTopLevelCategories; - constructor(public payload: { depth: number }) {} -} +export const loadTopLevelCategoriesFail = createAction('[Shopping] Load top level categories fail', httpError()); -export class LoadTopLevelCategoriesFail implements Action { - readonly type = CategoriesActionTypes.LoadTopLevelCategoriesFail; - constructor(public payload: { error: HttpError }) {} -} +export const loadTopLevelCategoriesSuccess = createAction( + '[Shopping] Load top level categories success', + payload<{ categories: CategoryTree }>() +); -export class LoadTopLevelCategoriesSuccess implements Action { - readonly type = CategoriesActionTypes.LoadTopLevelCategoriesSuccess; - constructor(public payload: { categories: CategoryTree }) {} -} +export const loadCategory = createAction('[Shopping] Load Category', payload<{ categoryId: string }>()); -export class LoadCategory implements Action { - readonly type = CategoriesActionTypes.LoadCategory; - constructor(public payload: { categoryId: string }) {} -} +export const loadCategoryFail = createAction('[Shopping] Load Category Fail', httpError()); -export class LoadCategoryFail implements Action { - readonly type = CategoriesActionTypes.LoadCategoryFail; - constructor(public payload: { error: HttpError }) {} -} - -export class LoadCategorySuccess implements Action { - readonly type = CategoriesActionTypes.LoadCategorySuccess; - constructor(public payload: { categories: CategoryTree }) {} -} - -export type CategoriesAction = - | LoadTopLevelCategories - | LoadTopLevelCategoriesFail - | LoadTopLevelCategoriesSuccess - | LoadCategory - | LoadCategoryFail - | LoadCategorySuccess; +export const loadCategorySuccess = createAction( + '[Shopping] Load Category Success', + payload<{ categories: CategoryTree }>() +); diff --git a/src/app/core/store/shopping/categories/categories.effects.spec.ts b/src/app/core/store/shopping/categories/categories.effects.spec.ts index 57854fd97b..05109c97a2 100644 --- a/src/app/core/store/shopping/categories/categories.effects.spec.ts +++ b/src/app/core/store/shopping/categories/categories.effects.spec.ts @@ -20,12 +20,12 @@ import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.modu import { categoryTree } from 'ish-core/utils/dev/test-data-utils'; import { - LoadCategory, - LoadCategoryFail, - LoadCategorySuccess, - LoadTopLevelCategories, - LoadTopLevelCategoriesFail, - LoadTopLevelCategoriesSuccess, + loadCategory, + loadCategoryFail, + loadCategorySuccess, + loadTopLevelCategories, + loadTopLevelCategoriesFail, + loadTopLevelCategoriesSuccess, } from './categories.actions'; import { CategoriesEffects } from './categories.effects'; @@ -102,7 +102,7 @@ describe('Categories Effects', () => { it('should trigger LoadCategory when /category/XXX is visited and category is not completely loaded', done => { category.completenessLevel = 0; - store$.dispatch(new LoadCategorySuccess({ categories: categoryTree([category]) })); + store$.dispatch(loadCategorySuccess({ categories: categoryTree([category]) })); router.navigateByUrl('/category/dummy'); effects.selectedCategory$.subscribe(action => { @@ -116,7 +116,7 @@ describe('Categories Effects', () => { it('should do nothing if category is completely loaded', done => { category.completenessLevel = CategoryCompletenessLevel.Max; - store$.dispatch(new LoadCategorySuccess({ categories: categoryTree([category]) })); + store$.dispatch(loadCategorySuccess({ categories: categoryTree([category]) })); router.navigateByUrl('/category/dummy'); effects.selectedCategory$.subscribe(fail, fail, fail); @@ -127,7 +127,7 @@ describe('Categories Effects', () => { category.completenessLevel = 0; const subcategory = { ...category, uniqueId: `${category.uniqueId}${CategoryHelper.uniqueIdSeparator}456` }; - store$.dispatch(new LoadCategorySuccess({ categories: categoryTree([category, subcategory]) })); + store$.dispatch(loadCategorySuccess({ categories: categoryTree([category, subcategory]) })); router.navigateByUrl('/category/dummy'); effects.selectedCategory$.subscribe(action => { @@ -163,7 +163,7 @@ describe('Categories Effects', () => { describe('loadCategory$', () => { it('should call the categoriesService for LoadCategory action', done => { const categoryId = '123'; - const action = new LoadCategory({ categoryId }); + const action = loadCategory({ categoryId }); actions$ = of(action); effects.loadCategory$.subscribe(() => { @@ -174,14 +174,14 @@ describe('Categories Effects', () => { it('should map to action of type LoadCategorySuccess', () => { const categoryId = '123'; - const action = new LoadCategory({ categoryId }); + const action = loadCategory({ categoryId }); const response = categoryTree([ { uniqueId: categoryId, categoryPath: ['123'], } as Category, ]); - const completion = new LoadCategorySuccess({ categories: response }); + const completion = loadCategorySuccess({ categories: response }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -190,8 +190,8 @@ describe('Categories Effects', () => { it('should map invalid request to action of type LoadCategoryFail', () => { const categoryId = 'invalid'; - const action = new LoadCategory({ categoryId }); - const completion = new LoadCategoryFail({ error: { message: 'invalid category' } as HttpError }); + const action = loadCategory({ categoryId }); + const completion = loadCategoryFail({ error: { message: 'invalid category' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -207,7 +207,7 @@ describe('Categories Effects', () => { }); it('should load top level categories retrying for every routing action', () => { - const completion = new LoadTopLevelCategories({ depth }); + const completion = loadTopLevelCategories({ depth }); // tslint:disable-next-line: no-any actions$ = hot(' ----a---a--a', { a: routerNavigatedAction({ payload: {} as any }) }); @@ -217,7 +217,7 @@ describe('Categories Effects', () => { }); it('should not load top level categories when already available', () => { - store$.dispatch(new LoadTopLevelCategoriesSuccess({ categories: categoryTree() })); + store$.dispatch(loadTopLevelCategoriesSuccess({ categories: categoryTree() })); // tslint:disable-next-line: no-any actions$ = hot(' ----a---a--a', { a: routerNavigatedAction({ payload: {} as any }) }); @@ -230,7 +230,7 @@ describe('Categories Effects', () => { describe('loadTopLevelCategories$', () => { it('should call the categoriesService for LoadTopLevelCategories action', done => { const depth = 2; - const action = new LoadTopLevelCategories({ depth }); + const action = loadTopLevelCategories({ depth }); actions$ = of(action); effects.loadTopLevelCategories$.subscribe(() => { @@ -241,8 +241,8 @@ describe('Categories Effects', () => { it('should map to action of type LoadCategorySuccess', () => { const depth = 2; - const action = new LoadTopLevelCategories({ depth }); - const completion = new LoadTopLevelCategoriesSuccess({ categories: TOP_LEVEL_CATEGORIES }); + const action = loadTopLevelCategories({ depth }); + const completion = loadTopLevelCategoriesSuccess({ categories: TOP_LEVEL_CATEGORIES }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -251,8 +251,8 @@ describe('Categories Effects', () => { it('should map invalid request to action of type LoadCategoryFail', () => { const depth = -1; - const action = new LoadTopLevelCategories({ depth }); - const completion = new LoadTopLevelCategoriesFail({ + const action = loadTopLevelCategories({ depth }); + const completion = loadTopLevelCategoriesFail({ error: { message: 'invalid number' } as HttpError, }); actions$ = hot('-a-a-a', { a: action }); @@ -264,7 +264,7 @@ describe('Categories Effects', () => { describe('redirectIfErrorInCategories$', () => { it('should redirect if triggered', fakeAsync(() => { - const action = new LoadCategoryFail({ error: { status: 404 } as HttpError }); + const action = loadCategoryFail({ error: { status: 404 } as HttpError }); actions$ = of(action); diff --git a/src/app/core/store/shopping/categories/categories.effects.ts b/src/app/core/store/shopping/categories/categories.effects.ts index 76ba3be9b6..3458e8c7eb 100644 --- a/src/app/core/store/shopping/categories/categories.effects.ts +++ b/src/app/core/store/shopping/categories/categories.effects.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { routerNavigatedAction } from '@ngrx/router-store'; import { Store, select } from '@ngrx/store'; import { isEqual } from 'lodash-es'; @@ -20,18 +20,17 @@ import { CategoryHelper } from 'ish-core/models/category/category.model'; import { ofCategoryUrl } from 'ish-core/routing/category/category.route'; import { CategoriesService } from 'ish-core/services/categories/categories.service'; import { selectRouteParam } from 'ish-core/store/core/router'; -import { LoadMoreProducts } from 'ish-core/store/shopping/product-listing'; +import { loadMoreProducts } from 'ish-core/store/shopping/product-listing'; import { HttpStatusCodeService } from 'ish-core/utils/http-status-code/http-status-code.service'; import { mapErrorToAction, mapToPayloadProperty, mapToProperty, whenFalsy, whenTruthy } from 'ish-core/utils/operators'; import { - CategoriesActionTypes, - LoadCategory, - LoadCategoryFail, - LoadCategorySuccess, - LoadTopLevelCategories, - LoadTopLevelCategoriesFail, - LoadTopLevelCategoriesSuccess, + loadCategory, + loadCategoryFail, + loadCategorySuccess, + loadTopLevelCategories, + loadTopLevelCategoriesFail, + loadTopLevelCategoriesSuccess, } from './categories.actions'; import { getCategoryEntities, getSelectedCategory, isTopLevelCategoriesLoaded } from './categories.selectors'; @@ -49,81 +48,90 @@ export class CategoriesEffects { * listens to routing and fires {@link LoadCategory} * when the requested {@link Category} is not available, yet */ - @Effect() - selectedCategory$ = this.store.pipe( - select(selectRouteParam('categoryUniqueId')), - whenTruthy(), - withLatestFrom(this.store.pipe(select(getCategoryEntities))), - filter(([id, entities]) => !CategoryHelper.isCategoryCompletelyLoaded(entities[id])), - map(([categoryId]) => new LoadCategory({ categoryId })) + selectedCategory$ = createEffect(() => + this.store.pipe( + select(selectRouteParam('categoryUniqueId')), + whenTruthy(), + withLatestFrom(this.store.pipe(select(getCategoryEntities))), + filter(([id, entities]) => !CategoryHelper.isCategoryCompletelyLoaded(entities[id])), + map(([categoryId]) => loadCategory({ categoryId })) + ) ); /** * fires {@link LoadCategory} for category path categories of the selected category that are not yet completely loaded */ - @Effect() - loadCategoriesOfCategoryPath$ = this.store.pipe( - select(getSelectedCategory), - filter(CategoryHelper.isCategoryCompletelyLoaded), - mapToProperty('categoryPath'), - withLatestFrom(this.store.pipe(select(getCategoryEntities))), - map(([ids, entities]) => ids.filter(id => !CategoryHelper.isCategoryCompletelyLoaded(entities[id]))), - mergeMap(ids => ids.map(categoryId => new LoadCategory({ categoryId }))) + loadCategoriesOfCategoryPath$ = createEffect(() => + this.store.pipe( + select(getSelectedCategory), + filter(CategoryHelper.isCategoryCompletelyLoaded), + mapToProperty('categoryPath'), + withLatestFrom(this.store.pipe(select(getCategoryEntities))), + map(([ids, entities]) => ids.filter(id => !CategoryHelper.isCategoryCompletelyLoaded(entities[id]))), + mergeMap(ids => ids.map(categoryId => loadCategory({ categoryId }))) + ) ); /** * loads a {@link Category} using the {@link CategoriesService} */ - @Effect() - loadCategory$ = this.actions$.pipe( - ofType(CategoriesActionTypes.LoadCategory), - mapToPayloadProperty('categoryId'), - mergeMap(categoryUniqueId => - this.categoryService.getCategory(categoryUniqueId).pipe( - map(categories => new LoadCategorySuccess({ categories })), - mapErrorToAction(LoadCategoryFail) + loadCategory$ = createEffect(() => + this.actions$.pipe( + ofType(loadCategory), + mapToPayloadProperty('categoryId'), + mergeMap(categoryUniqueId => + this.categoryService.getCategory(categoryUniqueId).pipe( + map(categories => loadCategorySuccess({ categories })), + mapErrorToAction(loadCategoryFail) + ) ) ) ); - @Effect() - loadTopLevelWhenUnavailable$ = this.actions$.pipe( - ofType(routerNavigatedAction), - switchMapTo(this.store.pipe(select(isTopLevelCategoriesLoaded))), - whenFalsy(), - mapTo(new LoadTopLevelCategories({ depth: this.mainNavigationMaxSubCategoriesDepth })) + loadTopLevelWhenUnavailable$ = createEffect(() => + this.actions$.pipe( + ofType(routerNavigatedAction), + switchMapTo(this.store.pipe(select(isTopLevelCategoriesLoaded))), + whenFalsy(), + mapTo(loadTopLevelCategories({ depth: this.mainNavigationMaxSubCategoriesDepth })) + ) ); - @Effect() - loadTopLevelCategories$ = this.actions$.pipe( - ofType(CategoriesActionTypes.LoadTopLevelCategories), - mapToPayloadProperty('depth'), - switchMap(limit => - this.categoryService.getTopLevelCategories(limit).pipe( - map(categories => new LoadTopLevelCategoriesSuccess({ categories })), - mapErrorToAction(LoadTopLevelCategoriesFail) + loadTopLevelCategories$ = createEffect(() => + this.actions$.pipe( + ofType(loadTopLevelCategories), + mapToPayloadProperty('depth'), + switchMap(limit => + this.categoryService.getTopLevelCategories(limit).pipe( + map(categories => loadTopLevelCategoriesSuccess({ categories })), + mapErrorToAction(loadTopLevelCategoriesFail) + ) ) ) ); - @Effect() - productOrCategoryChanged$ = this.actions$.pipe( - ofType(routerNavigatedAction), - switchMapTo( - this.store.pipe( - ofCategoryUrl(), - select(getSelectedCategory), - whenTruthy(), - filter(cat => cat.hasOnlineProducts), - map(({ uniqueId }) => new LoadMoreProducts({ id: { type: 'category', value: uniqueId } })) - ) - ), - distinctUntilChanged(isEqual) + productOrCategoryChanged$ = createEffect(() => + this.actions$.pipe( + ofType(routerNavigatedAction), + switchMapTo( + this.store.pipe( + ofCategoryUrl(), + select(getSelectedCategory), + whenTruthy(), + filter(cat => cat.hasOnlineProducts), + map(({ uniqueId }) => loadMoreProducts({ id: { type: 'category', value: uniqueId } })) + ) + ), + distinctUntilChanged(isEqual) + ) ); - @Effect({ dispatch: false }) - redirectIfErrorInCategories$ = this.actions$.pipe( - ofType(CategoriesActionTypes.LoadCategoryFail), - tap(() => this.httpStatusCodeService.setStatusAndRedirect(404)) + redirectIfErrorInCategories$ = createEffect( + () => + this.actions$.pipe( + ofType(loadCategoryFail), + tap(() => this.httpStatusCodeService.setStatusAndRedirect(404)) + ), + { dispatch: false } ); } diff --git a/src/app/core/store/shopping/categories/categories.reducer.spec.ts b/src/app/core/store/shopping/categories/categories.reducer.spec.ts index 528d26623e..8fffcd88bf 100644 --- a/src/app/core/store/shopping/categories/categories.reducer.spec.ts +++ b/src/app/core/store/shopping/categories/categories.reducer.spec.ts @@ -4,18 +4,26 @@ import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { categoryTree } from 'ish-core/utils/dev/test-data-utils'; import { - CategoriesAction, - LoadCategory, - LoadCategoryFail, - LoadCategorySuccess, - LoadTopLevelCategoriesSuccess, + loadCategory, + loadCategoryFail, + loadCategorySuccess, + loadTopLevelCategories, + loadTopLevelCategoriesFail, + loadTopLevelCategoriesSuccess, } from './categories.actions'; import { categoriesReducer, initialState } from './categories.reducer'; describe('Categories Reducer', () => { describe('undefined action', () => { it('should return the default state when previous state is undefined', () => { - const action = {} as CategoriesAction; + const action = {} as ReturnType< + | typeof loadTopLevelCategories + | typeof loadTopLevelCategoriesFail + | typeof loadTopLevelCategoriesSuccess + | typeof loadCategory + | typeof loadCategoryFail + | typeof loadCategorySuccess + >; const state = categoriesReducer(undefined, action); expect(state).toBe(initialState); @@ -25,7 +33,7 @@ describe('Categories Reducer', () => { describe('LoadCategory actions', () => { describe('LoadCategory action', () => { it('should set loading to true', () => { - const action = new LoadCategory({ categoryId: '123' }); + const action = loadCategory({ categoryId: '123' }); const state = categoriesReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -35,7 +43,7 @@ describe('Categories Reducer', () => { describe('LoadCategoryFail action', () => { it('should set loading to false', () => { - const action = new LoadCategoryFail({ error: {} as HttpError }); + const action = loadCategoryFail({ error: {} as HttpError }); const state = categoriesReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -56,7 +64,7 @@ describe('Categories Reducer', () => { }); it('should insert category if not exists', () => { - const action = new LoadCategorySuccess({ categories: categoryTree([category]) }); + const action = loadCategorySuccess({ categories: categoryTree([category]) }); const state = categoriesReducer(initialState, action); expect(Object.keys(state.categories.nodes)).toHaveLength(1); @@ -64,7 +72,7 @@ describe('Categories Reducer', () => { }); it('should update category if already exists', () => { - const action1 = new LoadCategorySuccess({ categories: categoryTree([category]) }); + const action1 = loadCategorySuccess({ categories: categoryTree([category]) }); const state1 = categoriesReducer(initialState, action1); const updatedCategory = { @@ -74,7 +82,7 @@ describe('Categories Reducer', () => { completenessLevel: 2, }; - const action2 = new LoadCategorySuccess({ categories: categoryTree([updatedCategory]) }); + const action2 = loadCategorySuccess({ categories: categoryTree([updatedCategory]) }); const state2 = categoriesReducer(state1, action2); expect(Object.keys(state2.categories.nodes)).toHaveLength(1); @@ -82,7 +90,7 @@ describe('Categories Reducer', () => { }); it('should set loading to false', () => { - const action = new LoadCategorySuccess({ categories: categoryTree([category]) }); + const action = loadCategorySuccess({ categories: categoryTree([category]) }); const state = categoriesReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -118,7 +126,7 @@ describe('Categories Reducer', () => { }); it('should add all flattened categories to the entities state', () => { - const action = new LoadTopLevelCategoriesSuccess({ categories }); + const action = loadTopLevelCategoriesSuccess({ categories }); const state = categoriesReducer(initialState, action); const expectedIds = ['1', '2', '1.1', '1.1.1', '2.1', '2.2']; @@ -129,7 +137,7 @@ describe('Categories Reducer', () => { }); it('should collect the IDs for all top level categories in the state', () => { - const action = new LoadTopLevelCategoriesSuccess({ categories }); + const action = loadTopLevelCategoriesSuccess({ categories }); const state = categoriesReducer(initialState, action); const topLevelIds = ['1', '2']; @@ -138,7 +146,7 @@ describe('Categories Reducer', () => { }); it('should remember if top level categories were loaded', () => { - const action = new LoadTopLevelCategoriesSuccess({ categories }); + const action = loadTopLevelCategoriesSuccess({ categories }); const state = categoriesReducer(initialState, action); expect(state.topLevelLoaded).toBeTrue(); diff --git a/src/app/core/store/shopping/categories/categories.reducer.ts b/src/app/core/store/shopping/categories/categories.reducer.ts index 32d77189cc..a69e8bdc82 100644 --- a/src/app/core/store/shopping/categories/categories.reducer.ts +++ b/src/app/core/store/shopping/categories/categories.reducer.ts @@ -1,10 +1,13 @@ +import { createReducer, on } from '@ngrx/store'; + import { CategoryTree, CategoryTreeHelper } from 'ish-core/models/category-tree/category-tree.model'; +import { setLoadingOn } from 'ish-core/utils/ngrx-creators'; import { - CategoriesAction, - CategoriesActionTypes, - LoadCategorySuccess, - LoadTopLevelCategoriesSuccess, + loadCategory, + loadCategoryFail, + loadCategorySuccess, + loadTopLevelCategoriesSuccess, } from './categories.actions'; export interface CategoriesState { @@ -19,7 +22,10 @@ export const initialState: CategoriesState = { topLevelLoaded: false, }; -function mergeCategories(state: CategoriesState, action: LoadTopLevelCategoriesSuccess | LoadCategorySuccess) { +function mergeCategories( + state: CategoriesState, + action: ReturnType +) { const loadedTree = action.payload.categories; const categories = CategoryTreeHelper.merge(state.categories, loadedTree); return { @@ -29,30 +35,16 @@ function mergeCategories(state: CategoriesState, action: LoadTopLevelCategoriesS }; } -export function categoriesReducer(state = initialState, action: CategoriesAction): CategoriesState { - switch (action.type) { - case CategoriesActionTypes.LoadCategory: { - return { - ...state, - loading: true, - }; - } - - case CategoriesActionTypes.LoadCategoryFail: { - return { - ...state, - loading: false, - }; - } - - case CategoriesActionTypes.LoadTopLevelCategoriesSuccess: { - return { ...mergeCategories(state, action), topLevelLoaded: true }; - } - - case CategoriesActionTypes.LoadCategorySuccess: { - return mergeCategories(state, action); - } - } - - return state; -} +export const categoriesReducer = createReducer( + initialState, + setLoadingOn(loadCategory), + on(loadCategoryFail, (state: CategoriesState) => ({ + ...state, + loading: false, + })), + on(loadTopLevelCategoriesSuccess, (state: CategoriesState, action) => ({ + ...mergeCategories(state, action), + topLevelLoaded: true, + })), + on(loadCategorySuccess, mergeCategories) +); diff --git a/src/app/core/store/shopping/categories/categories.selectors.spec.ts b/src/app/core/store/shopping/categories/categories.selectors.spec.ts index 2baf8260b9..39d20f465a 100644 --- a/src/app/core/store/shopping/categories/categories.selectors.spec.ts +++ b/src/app/core/store/shopping/categories/categories.selectors.spec.ts @@ -7,16 +7,16 @@ import { Category } from 'ish-core/models/category/category.model'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { Product } from 'ish-core/models/product/product.model'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; -import { LoadProductSuccess } from 'ish-core/store/shopping/products'; +import { loadProductSuccess } from 'ish-core/store/shopping/products'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; import { categoryTree } from 'ish-core/utils/dev/test-data-utils'; import { - LoadCategory, - LoadCategoryFail, - LoadCategorySuccess, - LoadTopLevelCategoriesSuccess, + loadCategory, + loadCategoryFail, + loadCategorySuccess, + loadTopLevelCategoriesSuccess, } from './categories.actions'; import { getCategoryEntities, @@ -73,7 +73,7 @@ describe('Categories Selectors', () => { describe('loading a category', () => { beforeEach(() => { - store$.dispatch(new LoadCategory({ categoryId: '' })); + store$.dispatch(loadCategory({ categoryId: '' })); }); it('should set the state to loading', () => { @@ -82,7 +82,7 @@ describe('Categories Selectors', () => { describe('and reporting success', () => { beforeEach(() => { - store$.dispatch(new LoadCategorySuccess({ categories: categoryTree([cat]) })); + store$.dispatch(loadCategorySuccess({ categories: categoryTree([cat]) })); }); it('should set loading to false', () => { @@ -93,7 +93,7 @@ describe('Categories Selectors', () => { describe('and reporting failure', () => { beforeEach(() => { - store$.dispatch(new LoadCategoryFail({ error: { message: 'error' } as HttpError })); + store$.dispatch(loadCategoryFail({ error: { message: 'error' } as HttpError })); }); it('should not have loaded category on error', () => { @@ -105,8 +105,8 @@ describe('Categories Selectors', () => { describe('state with a category', () => { beforeEach(() => { - store$.dispatch(new LoadCategorySuccess({ categories: categoryTree([cat]) })); - store$.dispatch(new LoadProductSuccess({ product: prod })); + store$.dispatch(loadCategorySuccess({ categories: categoryTree([cat]) })); + store$.dispatch(loadProductSuccess({ product: prod })); }); describe('but no current router state', () => { @@ -144,7 +144,7 @@ describe('Categories Selectors', () => { beforeEach(() => { catA = { uniqueId: 'A', categoryPath: ['A'] } as Category; catB = { uniqueId: 'B', categoryPath: ['B'] } as Category; - store$.dispatch(new LoadTopLevelCategoriesSuccess({ categories: categoryTree([catA, catB]) })); + store$.dispatch(loadTopLevelCategoriesSuccess({ categories: categoryTree([catA, catB]) })); }); it('should select root categories when used', () => { diff --git a/src/app/core/store/shopping/compare/compare.actions.ts b/src/app/core/store/shopping/compare/compare.actions.ts index cb97b2ada7..7dd30dab2b 100644 --- a/src/app/core/store/shopping/compare/compare.actions.ts +++ b/src/app/core/store/shopping/compare/compare.actions.ts @@ -1,24 +1,9 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; -export enum CompareActionTypes { - AddToCompare = '[Shopping] Add Product to Compare', - RemoveFromCompare = '[Shopping] Remove Product from Compare', - ToggleCompare = '[Shopping] Toggle Product Compare', -} +import { payload } from 'ish-core/utils/ngrx-creators'; -export class AddToCompare implements Action { - readonly type = CompareActionTypes.AddToCompare; - constructor(public payload: { sku: string }) {} -} +export const addToCompare = createAction('[Shopping] Add Product to Compare', payload<{ sku: string }>()); -export class RemoveFromCompare implements Action { - readonly type = CompareActionTypes.RemoveFromCompare; - constructor(public payload: { sku: string }) {} -} +export const removeFromCompare = createAction('[Shopping] Remove Product from Compare', payload<{ sku: string }>()); -export class ToggleCompare implements Action { - readonly type = CompareActionTypes.ToggleCompare; - constructor(public payload: { sku: string }) {} -} - -export type CompareAction = AddToCompare | RemoveFromCompare | ToggleCompare; +export const toggleCompare = createAction('[Shopping] Toggle Product Compare', payload<{ sku: string }>()); diff --git a/src/app/core/store/shopping/compare/compare.effects.spec.ts b/src/app/core/store/shopping/compare/compare.effects.spec.ts index 5aa6446170..2f2c44f34f 100644 --- a/src/app/core/store/shopping/compare/compare.effects.spec.ts +++ b/src/app/core/store/shopping/compare/compare.effects.spec.ts @@ -7,7 +7,7 @@ import { Observable } from 'rxjs'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; -import { AddToCompare, RemoveFromCompare, ToggleCompare } from './compare.actions'; +import { addToCompare, removeFromCompare, toggleCompare } from './compare.actions'; import { CompareEffects } from './compare.effects'; describe('Compare Effects', () => { @@ -29,8 +29,8 @@ describe('Compare Effects', () => { it('should switch to ADD action', () => { const sku = '123'; - const action = new ToggleCompare({ sku }); - const completion = new AddToCompare({ sku }); + const action = toggleCompare({ sku }); + const completion = addToCompare({ sku }); actions$ = hot('-a', { a: action }); const expected$ = cold('-b', { b: completion }); @@ -40,10 +40,10 @@ describe('Compare Effects', () => { it('should switch to REMOVE action', () => { const sku = '123'; - store$.dispatch(new AddToCompare({ sku })); + store$.dispatch(addToCompare({ sku })); - const action = new ToggleCompare({ sku }); - const completion = new RemoveFromCompare({ sku }); + const action = toggleCompare({ sku }); + const completion = removeFromCompare({ sku }); actions$ = hot('-a', { a: action }); const expected$ = cold('-b', { b: completion }); diff --git a/src/app/core/store/shopping/compare/compare.effects.ts b/src/app/core/store/shopping/compare/compare.effects.ts index 405296a0b5..d7507a8dcd 100644 --- a/src/app/core/store/shopping/compare/compare.effects.ts +++ b/src/app/core/store/shopping/compare/compare.effects.ts @@ -1,33 +1,35 @@ import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { map, mergeMap, withLatestFrom } from 'rxjs/operators'; import { ProductCompletenessLevel } from 'ish-core/models/product/product.model'; import { ofUrl } from 'ish-core/store/core/router'; -import { LoadProductIfNotLoaded } from 'ish-core/store/shopping/products'; +import { loadProductIfNotLoaded } from 'ish-core/store/shopping/products'; import { mapToPayloadProperty } from 'ish-core/utils/operators'; -import { AddToCompare, CompareActionTypes, RemoveFromCompare, ToggleCompare } from './compare.actions'; +import { addToCompare, removeFromCompare, toggleCompare } from './compare.actions'; import { getCompareProductsSKUs } from './compare.selectors'; @Injectable() export class CompareEffects { constructor(private actions$: Actions, private store: Store) {} - @Effect() - toggleCompare$ = this.actions$.pipe( - ofType(CompareActionTypes.ToggleCompare), - mapToPayloadProperty('sku'), - withLatestFrom(this.store.pipe(select(getCompareProductsSKUs))), - map(([sku, skuList]) => ({ sku, isInList: skuList.includes(sku) })), - map(({ sku, isInList }) => (isInList ? new RemoveFromCompare({ sku }) : new AddToCompare({ sku }))) + toggleCompare$ = createEffect(() => + this.actions$.pipe( + ofType(toggleCompare), + mapToPayloadProperty('sku'), + withLatestFrom(this.store.pipe(select(getCompareProductsSKUs))), + map(([sku, skuList]) => ({ sku, isInList: skuList.includes(sku) })), + map(({ sku, isInList }) => (isInList ? removeFromCompare({ sku }) : addToCompare({ sku }))) + ) ); - @Effect() - completeProductsForComparePage$ = this.store.pipe( - ofUrl(/^\/compare.*/), - select(getCompareProductsSKUs), - mergeMap(skus => skus.map(sku => new LoadProductIfNotLoaded({ sku, level: ProductCompletenessLevel.Detail }))) + completeProductsForComparePage$ = createEffect(() => + this.store.pipe( + ofUrl(/^\/compare.*/), + select(getCompareProductsSKUs), + mergeMap(skus => skus.map(sku => loadProductIfNotLoaded({ sku, level: ProductCompletenessLevel.Detail }))) + ) ); } diff --git a/src/app/core/store/shopping/compare/compare.reducer.spec.ts b/src/app/core/store/shopping/compare/compare.reducer.spec.ts index adf9bd3755..c1c19f8a7a 100644 --- a/src/app/core/store/shopping/compare/compare.reducer.spec.ts +++ b/src/app/core/store/shopping/compare/compare.reducer.spec.ts @@ -1,10 +1,10 @@ -import { AddToCompare, CompareAction, RemoveFromCompare } from './compare.actions'; +import { addToCompare, removeFromCompare, toggleCompare } from './compare.actions'; import { compareReducer, initialState } from './compare.reducer'; describe('Compare Reducer', () => { describe('undefined action', () => { it('should return the default state when queried with undefined state', () => { - const action = {} as CompareAction; + const action = {} as ReturnType; const state = compareReducer(undefined, action); expect(state).toBe(initialState); @@ -14,7 +14,7 @@ describe('Compare Reducer', () => { describe('AddToCompare action', () => { it('should add SKU to compare state for the given SKU', () => { const sku = '1234567'; - const action = new AddToCompare({ sku }); + const action = addToCompare({ sku }); const state = compareReducer(initialState, action); expect(state.products).toContain(sku); @@ -29,7 +29,7 @@ describe('Compare Reducer', () => { ...initialState, products: ['123', sku], }; - const action = new RemoveFromCompare({ sku }); + const action = removeFromCompare({ sku }); const state = compareReducer(previousState, action); expect(state.products).not.toContain(sku); diff --git a/src/app/core/store/shopping/compare/compare.reducer.ts b/src/app/core/store/shopping/compare/compare.reducer.ts index 2f0b19f99e..7dcc2eaf0a 100644 --- a/src/app/core/store/shopping/compare/compare.reducer.ts +++ b/src/app/core/store/shopping/compare/compare.reducer.ts @@ -1,4 +1,6 @@ -import { CompareAction, CompareActionTypes } from './compare.actions'; +import { createReducer, on } from '@ngrx/store'; + +import { addToCompare, removeFromCompare } from './compare.actions'; export interface CompareState { products: string[]; @@ -8,22 +10,18 @@ export const initialState: CompareState = { products: [], }; -export function compareReducer(state = initialState, action: CompareAction): CompareState { - switch (action.type) { - case CompareActionTypes.AddToCompare: { - const { sku } = action.payload; - const products = state.products.includes(sku) ? [...state.products] : [...state.products, sku]; - - return { ...state, products }; - } +export const compareReducer = createReducer( + initialState, + on(addToCompare, (state: CompareState, action) => { + const { sku } = action.payload; + const products = state.products.includes(sku) ? [...state.products] : [...state.products, sku]; - case CompareActionTypes.RemoveFromCompare: { - const { sku } = action.payload; - const products = state.products.filter(current => current !== sku); + return { ...state, products }; + }), + on(removeFromCompare, (state: CompareState, action) => { + const { sku } = action.payload; + const products = state.products.filter(current => current !== sku); - return { ...state, products }; - } - } - - return state; -} + return { ...state, products }; + }) +); diff --git a/src/app/core/store/shopping/filter/filter.actions.ts b/src/app/core/store/shopping/filter/filter.actions.ts index 8acb4c6e54..7d24a9d89f 100644 --- a/src/app/core/store/shopping/filter/filter.actions.ts +++ b/src/app/core/store/shopping/filter/filter.actions.ts @@ -1,66 +1,33 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; import { FilterNavigation } from 'ish-core/models/filter-navigation/filter-navigation.model'; -import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { ProductListingID } from 'ish-core/models/product-listing/product-listing.model'; +import { httpError, payload } from 'ish-core/utils/ngrx-creators'; -export enum FilterActionTypes { - LoadFilterForCategory = '[Shopping] Load Filter For Category', - LoadFilterForSearch = '[Shopping] Load Filter for Search', - LoadFilterSuccess = '[Shopping] Load Filter Success', - LoadFilterFail = '[Shopping] Load Filter Fail', - ApplyFilter = '[Shopping] Apply Filter', - ApplyFilterSuccess = '[Shopping] Apply Filter Success', - ApplyFilterFail = '[Shopping] Apply Filter Fail', - LoadProductsForFilter = '[Shopping] Load Products For Filter', -} +export const loadFilterForCategory = createAction( + '[Shopping] Load Filter For Category', + payload<{ uniqueId: string }>() +); -export class LoadFilterForCategory implements Action { - readonly type = FilterActionTypes.LoadFilterForCategory; - constructor(public payload: { uniqueId: string }) {} -} +export const loadFilterSuccess = createAction( + '[Shopping] Load Filter Success', + payload<{ filterNavigation: FilterNavigation }>() +); -export class LoadFilterSuccess implements Action { - readonly type = FilterActionTypes.LoadFilterSuccess; - constructor(public payload: { filterNavigation: FilterNavigation }) {} -} +export const loadFilterFail = createAction('[Shopping] Load Filter Fail', httpError()); -export class LoadFilterFail implements Action { - readonly type = FilterActionTypes.LoadFilterFail; - constructor(public payload: { error: HttpError }) {} -} +export const loadFilterForSearch = createAction('[Shopping] Load Filter for Search', payload<{ searchTerm: string }>()); -export class LoadFilterForSearch implements Action { - readonly type = FilterActionTypes.LoadFilterForSearch; - constructor(public payload: { searchTerm: string }) {} -} +export const applyFilter = createAction('[Shopping] Apply Filter', payload<{ searchParameter: string }>()); -export class ApplyFilter implements Action { - readonly type = FilterActionTypes.ApplyFilter; - constructor(public payload: { searchParameter: string }) {} -} +export const applyFilterSuccess = createAction( + '[Shopping] Apply Filter Success', + payload<{ availableFilter: FilterNavigation; searchParameter: string }>() +); -export class ApplyFilterSuccess implements Action { - readonly type = FilterActionTypes.ApplyFilterSuccess; - constructor(public payload: { availableFilter: FilterNavigation; searchParameter: string }) {} -} +export const applyFilterFail = createAction('[Shopping] Apply Filter Fail', httpError()); -export class ApplyFilterFail implements Action { - readonly type = FilterActionTypes.ApplyFilterFail; - constructor(public payload: { error: HttpError }) {} -} - -export class LoadProductsForFilter implements Action { - readonly type = FilterActionTypes.LoadProductsForFilter; - constructor(public payload: { id: ProductListingID; searchParameter: string }) {} -} - -export type FilterActions = - | LoadFilterForCategory - | LoadFilterSuccess - | LoadFilterFail - | ApplyFilter - | ApplyFilterSuccess - | ApplyFilterFail - | LoadFilterForSearch - | LoadProductsForFilter; +export const loadProductsForFilter = createAction( + '[Shopping] Load Products For Filter', + payload<{ id: ProductListingID; searchParameter: string }>() +); diff --git a/src/app/core/store/shopping/filter/filter.effects.spec.ts b/src/app/core/store/shopping/filter/filter.effects.spec.ts index c2acc724ff..d5f0303e39 100644 --- a/src/app/core/store/shopping/filter/filter.effects.spec.ts +++ b/src/app/core/store/shopping/filter/filter.effects.spec.ts @@ -9,17 +9,17 @@ import { PRODUCT_LISTING_ITEMS_PER_PAGE } from 'ish-core/configurations/injectio import { FilterNavigation } from 'ish-core/models/filter-navigation/filter-navigation.model'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { FilterService } from 'ish-core/services/filter/filter.service'; -import { SetProductListingPages } from 'ish-core/store/shopping/product-listing'; +import { setProductListingPages } from 'ish-core/store/shopping/product-listing'; import { - ApplyFilter, - ApplyFilterFail, - ApplyFilterSuccess, - LoadFilterFail, - LoadFilterForCategory, - LoadFilterForSearch, - LoadFilterSuccess, - LoadProductsForFilter, + applyFilter, + applyFilterFail, + applyFilterSuccess, + loadFilterFail, + loadFilterForCategory, + loadFilterForSearch, + loadFilterSuccess, + loadProductsForFilter, } from './filter.actions'; import { FilterEffects } from './filter.effects'; @@ -80,7 +80,7 @@ describe('Filter Effects', () => { describe('loadAvailableFilterForCategories$', () => { it('should call the filterService for LoadFilterForCategories action', done => { - const action = new LoadFilterForCategory({ uniqueId: 'c' }); + const action = loadFilterForCategory({ uniqueId: 'c' }); actions$ = of(action); effects.loadAvailableFilterForCategories$.subscribe(() => { @@ -90,8 +90,8 @@ describe('Filter Effects', () => { }); it('should map to action of type LoadFilterSuccess', () => { - const action = new LoadFilterForCategory({ uniqueId: 'c' }); - const completion = new LoadFilterSuccess({ filterNavigation: filterNav }); + const action = loadFilterForCategory({ uniqueId: 'c' }); + const completion = loadFilterSuccess({ filterNavigation: filterNav }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -99,8 +99,8 @@ describe('Filter Effects', () => { }); it('should map invalid request to action of type LoadFilterFail', () => { - const action = new LoadFilterForCategory({ uniqueId: 'invalid' }); - const completion = new LoadFilterFail({ error: { message: 'invalid' } as HttpError }); + const action = loadFilterForCategory({ uniqueId: 'invalid' }); + const completion = loadFilterFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -110,7 +110,7 @@ describe('Filter Effects', () => { describe('applyFilter$', () => { it('should call the filterService for ApplyFilter action', done => { - const action = new ApplyFilter({ searchParameter: 'b' }); + const action = applyFilter({ searchParameter: 'b' }); actions$ = of(action); effects.applyFilter$.subscribe(() => { @@ -120,8 +120,8 @@ describe('Filter Effects', () => { }); it('should map to action of type ApplyFilterSuccess', () => { - const action = new ApplyFilter({ searchParameter: 'b' }); - const completion = new ApplyFilterSuccess({ + const action = applyFilter({ searchParameter: 'b' }); + const completion = applyFilterSuccess({ availableFilter: filterNav, searchParameter: 'b', }); @@ -132,8 +132,8 @@ describe('Filter Effects', () => { }); it('should map invalid request to action of type ApplyFilterFail', () => { - const action = new ApplyFilter({ searchParameter: 'invalid' }); - const completion = new ApplyFilterFail({ error: { message: 'invalid' } as HttpError }); + const action = applyFilter({ searchParameter: 'invalid' }); + const completion = applyFilterFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -143,7 +143,7 @@ describe('Filter Effects', () => { describe('loadFilteredProducts$', () => { it('should trigger product actions for ApplyFilterSuccess action', () => { - const action = new LoadProductsForFilter({ + const action = loadProductsForFilter({ id: { type: 'search', value: 'test', @@ -151,7 +151,7 @@ describe('Filter Effects', () => { }, searchParameter: 'b', }); - const completion = new SetProductListingPages({ + const completion = setProductListingPages({ id: { type: 'search', value: 'test', @@ -169,7 +169,7 @@ describe('Filter Effects', () => { describe('loadFilterForSearch$', () => { it('should call the filterService for LoadFilterForSearch action', done => { - const action = new LoadFilterForSearch({ searchTerm: 'search' }); + const action = loadFilterForSearch({ searchTerm: 'search' }); actions$ = of(action); effects.loadFilterForSearch$.subscribe(() => { @@ -179,8 +179,8 @@ describe('Filter Effects', () => { }); it('should map to action of type LoadFilterSuccess', () => { - const action = new LoadFilterForSearch({ searchTerm: 'search' }); - const completion = new LoadFilterSuccess({ filterNavigation: filterNav }); + const action = loadFilterForSearch({ searchTerm: 'search' }); + const completion = loadFilterSuccess({ filterNavigation: filterNav }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -188,8 +188,8 @@ describe('Filter Effects', () => { }); it('should map invalid request to action of type LoadFilterFail', () => { - const action = new LoadFilterForSearch({ searchTerm: 'invalid' }); - const completion = new LoadFilterFail({ error: { message: 'invalid' } as HttpError }); + const action = loadFilterForSearch({ searchTerm: 'invalid' }); + const completion = loadFilterFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); diff --git a/src/app/core/store/shopping/filter/filter.effects.ts b/src/app/core/store/shopping/filter/filter.effects.ts index ae838d3928..d5bf47a5a2 100644 --- a/src/app/core/store/shopping/filter/filter.effects.ts +++ b/src/app/core/store/shopping/filter/filter.effects.ts @@ -1,23 +1,22 @@ import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { map, mergeMap, switchMap } from 'rxjs/operators'; import { ProductListingMapper } from 'ish-core/models/product-listing/product-listing.mapper'; import { FilterService } from 'ish-core/services/filter/filter.service'; -import { SetProductListingPages } from 'ish-core/store/shopping/product-listing'; -import { LoadProductFail } from 'ish-core/store/shopping/products'; +import { setProductListingPages } from 'ish-core/store/shopping/product-listing'; +import { loadProductFail } from 'ish-core/store/shopping/products'; import { mapErrorToAction, mapToPayload, mapToPayloadProperty } from 'ish-core/utils/operators'; import { - ApplyFilter, - ApplyFilterFail, - ApplyFilterSuccess, - FilterActionTypes, - LoadFilterFail, - LoadFilterForCategory, - LoadFilterForSearch, - LoadFilterSuccess, - LoadProductsForFilter, + applyFilter, + applyFilterFail, + applyFilterSuccess, + loadFilterFail, + loadFilterForCategory, + loadFilterForSearch, + loadFilterSuccess, + loadProductsForFilter, } from './filter.actions'; @Injectable() @@ -28,57 +27,61 @@ export class FilterEffects { private productListingMapper: ProductListingMapper ) {} - @Effect() - loadAvailableFilterForCategories$ = this.actions$.pipe( - ofType(FilterActionTypes.LoadFilterForCategory), - mapToPayloadProperty('uniqueId'), - mergeMap(uniqueId => - this.filterService.getFilterForCategory(uniqueId).pipe( - map(filterNavigation => new LoadFilterSuccess({ filterNavigation })), - mapErrorToAction(LoadFilterFail) + loadAvailableFilterForCategories$ = createEffect(() => + this.actions$.pipe( + ofType(loadFilterForCategory), + mapToPayloadProperty('uniqueId'), + mergeMap(uniqueId => + this.filterService.getFilterForCategory(uniqueId).pipe( + map(filterNavigation => loadFilterSuccess({ filterNavigation })), + mapErrorToAction(loadFilterFail) + ) ) ) ); - @Effect() - loadFilterForSearch$ = this.actions$.pipe( - ofType(FilterActionTypes.LoadFilterForSearch), - mapToPayloadProperty('searchTerm'), - mergeMap(searchTerm => - this.filterService.getFilterForSearch(searchTerm).pipe( - map(filterNavigation => new LoadFilterSuccess({ filterNavigation })), - mapErrorToAction(LoadFilterFail) + loadFilterForSearch$ = createEffect(() => + this.actions$.pipe( + ofType(loadFilterForSearch), + mapToPayloadProperty('searchTerm'), + mergeMap(searchTerm => + this.filterService.getFilterForSearch(searchTerm).pipe( + map(filterNavigation => loadFilterSuccess({ filterNavigation })), + mapErrorToAction(loadFilterFail) + ) ) ) ); - @Effect() - applyFilter$ = this.actions$.pipe( - ofType(FilterActionTypes.ApplyFilter), - mapToPayload(), - mergeMap(({ searchParameter }) => - this.filterService.applyFilter(searchParameter).pipe( - map(availableFilter => new ApplyFilterSuccess({ availableFilter, searchParameter })), - mapErrorToAction(ApplyFilterFail) + applyFilter$ = createEffect(() => + this.actions$.pipe( + ofType(applyFilter), + mapToPayload(), + mergeMap(({ searchParameter }) => + this.filterService.applyFilter(searchParameter).pipe( + map(availableFilter => applyFilterSuccess({ availableFilter, searchParameter })), + mapErrorToAction(applyFilterFail) + ) ) ) ); - @Effect() - loadFilteredProducts$ = this.actions$.pipe( - ofType(FilterActionTypes.LoadProductsForFilter), - mapToPayload(), - switchMap(({ id, searchParameter }) => - this.filterService.getFilteredProducts(searchParameter).pipe( - mergeMap(({ productSKUs, total }) => [ - new SetProductListingPages( - this.productListingMapper.createPages(productSKUs, id.type, id.value, { - filters: id.filters, - itemCount: total, - }) - ), - ]), - mapErrorToAction(LoadProductFail) + loadFilteredProducts$ = createEffect(() => + this.actions$.pipe( + ofType(loadProductsForFilter), + mapToPayload(), + switchMap(({ id, searchParameter }) => + this.filterService.getFilteredProducts(searchParameter).pipe( + mergeMap(({ productSKUs, total }) => [ + setProductListingPages( + this.productListingMapper.createPages(productSKUs, id.type, id.value, { + filters: id.filters, + itemCount: total, + }) + ), + ]), + mapErrorToAction(loadProductFail) + ) ) ) ); diff --git a/src/app/core/store/shopping/filter/filter.reducer.spec.ts b/src/app/core/store/shopping/filter/filter.reducer.spec.ts index 3e5d3c376f..5e33e8c6c0 100644 --- a/src/app/core/store/shopping/filter/filter.reducer.spec.ts +++ b/src/app/core/store/shopping/filter/filter.reducer.spec.ts @@ -1,13 +1,31 @@ import { FilterNavigation } from 'ish-core/models/filter-navigation/filter-navigation.model'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; -import { ApplyFilterSuccess, FilterActions, LoadFilterFail, LoadFilterSuccess } from './filter.actions'; +import { + applyFilter, + applyFilterFail, + applyFilterSuccess, + loadFilterFail, + loadFilterForCategory, + loadFilterForSearch, + loadFilterSuccess, + loadProductsForFilter, +} from './filter.actions'; import { filterReducer, initialState } from './filter.reducer'; describe('Filter Reducer', () => { describe('undefined action', () => { it('should return the default state when previous state is undefined', () => { - const action = {} as FilterActions; + const action = {} as ReturnType< + | typeof loadFilterForCategory + | typeof loadFilterSuccess + | typeof loadFilterFail + | typeof applyFilter + | typeof applyFilterSuccess + | typeof applyFilterFail + | typeof loadFilterForSearch + | typeof loadProductsForFilter + >; const state = filterReducer(undefined, action); expect(state).toBe(initialState); @@ -17,7 +35,7 @@ describe('Filter Reducer', () => { describe('LoadFilterSuccess', () => { it('should set filter when reduced', () => { const filterNavigation = { filter: [{ name: 'a' }] } as FilterNavigation; - const action = new LoadFilterSuccess({ filterNavigation }); + const action = loadFilterSuccess({ filterNavigation }); const state = filterReducer(initialState, action); expect(state.availableFilter).toEqual(filterNavigation); @@ -26,7 +44,7 @@ describe('Filter Reducer', () => { describe('LoadFilterFailed', () => { it('should set filter when reduced', () => { - const action = new LoadFilterFail({ error: {} as HttpError }); + const action = loadFilterFail({ error: {} as HttpError }); const state = filterReducer(initialState, action); expect(state.availableFilter).toBeFalsy(); @@ -36,7 +54,7 @@ describe('Filter Reducer', () => { describe('LoadFilterSuccess', () => { it('should set filter when reduced', () => { const filterNavigation = { filter: [{ name: 'a' }] } as FilterNavigation; - const action = new LoadFilterSuccess({ filterNavigation }); + const action = loadFilterSuccess({ filterNavigation }); const state = filterReducer(initialState, action); expect(state.availableFilter).toEqual(filterNavigation); @@ -45,7 +63,7 @@ describe('Filter Reducer', () => { describe('LoadFilterFailed', () => { it('should set filter when reduced', () => { - const action = new LoadFilterFail({ error: {} as HttpError }); + const action = loadFilterFail({ error: {} as HttpError }); const state = filterReducer(initialState, action); expect(state.availableFilter).toBeFalsy(); @@ -55,7 +73,7 @@ describe('Filter Reducer', () => { describe('ApplyFilterSuccess', () => { it('should set filter when reduced', () => { const filter = { filter: [{ name: 'a' }] } as FilterNavigation; - const action = new ApplyFilterSuccess({ + const action = applyFilterSuccess({ availableFilter: filter, searchParameter: 'b', }); diff --git a/src/app/core/store/shopping/filter/filter.reducer.ts b/src/app/core/store/shopping/filter/filter.reducer.ts index 9cbfd2072e..6ae6f55076 100644 --- a/src/app/core/store/shopping/filter/filter.reducer.ts +++ b/src/app/core/store/shopping/filter/filter.reducer.ts @@ -1,6 +1,14 @@ +import { createReducer, on } from '@ngrx/store'; + import { FilterNavigation } from 'ish-core/models/filter-navigation/filter-navigation.model'; -import { FilterActionTypes, FilterActions } from './filter.actions'; +import { + applyFilterSuccess, + loadFilterFail, + loadFilterForCategory, + loadFilterForSearch, + loadFilterSuccess, +} from './filter.actions'; export interface FilterState { availableFilter: FilterNavigation; @@ -10,32 +18,22 @@ export const initialState: FilterState = { availableFilter: undefined, }; -export function filterReducer(state = initialState, action: FilterActions): FilterState { - switch (action.type) { - case FilterActionTypes.LoadFilterForCategory: - case FilterActionTypes.LoadFilterForSearch: { - return { ...initialState }; - } - case FilterActionTypes.LoadFilterSuccess: { - return { - ...state, - availableFilter: action.payload.filterNavigation, - }; - } - case FilterActionTypes.LoadFilterFail: { - return { - ...state, - availableFilter: undefined, - }; - } - case FilterActionTypes.ApplyFilterSuccess: { - const { availableFilter } = action.payload; - return { - ...state, - availableFilter, - }; - } - } - - return state; -} +export const filterReducer = createReducer( + initialState, + on(loadFilterForCategory, loadFilterForSearch, () => ({ ...initialState })), + on(loadFilterSuccess, (state: FilterState, action) => ({ + ...state, + availableFilter: action.payload.filterNavigation, + })), + on(loadFilterFail, (state: FilterState) => ({ + ...state, + availableFilter: undefined, + })), + on(applyFilterSuccess, (state: FilterState, action) => { + const { availableFilter } = action.payload; + return { + ...state, + availableFilter, + }; + }) +); diff --git a/src/app/core/store/shopping/filter/filter.selectors.spec.ts b/src/app/core/store/shopping/filter/filter.selectors.spec.ts index 75a82f9466..30ffd7827c 100644 --- a/src/app/core/store/shopping/filter/filter.selectors.spec.ts +++ b/src/app/core/store/shopping/filter/filter.selectors.spec.ts @@ -6,7 +6,7 @@ import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; -import { LoadFilterFail, LoadFilterSuccess } from './filter.actions'; +import { loadFilterFail, loadFilterSuccess } from './filter.actions'; import { getAvailableFilter } from './filter.selectors'; describe('Filter Selectors', () => { @@ -29,7 +29,7 @@ describe('Filter Selectors', () => { describe('with LoadFilterSuccess state', () => { beforeEach(() => { - store$.dispatch(new LoadFilterSuccess({ filterNavigation: { filter: [{ name: 'a' }] } as FilterNavigation })); + store$.dispatch(loadFilterSuccess({ filterNavigation: { filter: [{ name: 'a' }] } as FilterNavigation })); }); it('should add the filter to the state', () => { expect(getAvailableFilter(store$.state)).toEqual({ filter: [{ name: 'a' }] } as FilterNavigation); @@ -38,7 +38,7 @@ describe('Filter Selectors', () => { describe('with LoadFilterFail state', () => { beforeEach(() => { - store$.dispatch(new LoadFilterFail({ error: {} as HttpError })); + store$.dispatch(loadFilterFail({ error: {} as HttpError })); }); it('should set undefined to the filter in the state', () => { expect(getAvailableFilter(store$.state)).toBeUndefined(); diff --git a/src/app/core/store/shopping/product-listing/product-listing.actions.ts b/src/app/core/store/shopping/product-listing/product-listing.actions.ts index db83bd57af..2cdde8994b 100644 --- a/src/app/core/store/shopping/product-listing/product-listing.actions.ts +++ b/src/app/core/store/shopping/product-listing/product-listing.actions.ts @@ -1,51 +1,32 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; import { ProductListingID, ProductListingType } from 'ish-core/models/product-listing/product-listing.model'; import { ViewType } from 'ish-core/models/viewtype/viewtype.types'; - -export enum ProductListingActionTypes { - SetProductListingPages = '[ProductListing] Set Product Listing Pages', - LoadMoreProducts = '[ProductListing] Load More Products', - LoadMoreProductsForParams = '[ProductListing Internal] Load More Products For Params', - SetProductListingPageSize = '[ProductListing] Set Product Listing Page Size', - SetViewType = '[ProductListing] Set View Type', - LoadPagesForMaster = '[ProductListing] Load Pages For Master', -} - -export class SetProductListingPages implements Action { - readonly type = ProductListingActionTypes.SetProductListingPages; - constructor(public payload: ProductListingType) {} -} - -export class SetProductListingPageSize implements Action { - readonly type = ProductListingActionTypes.SetProductListingPageSize; - constructor(public payload: { itemsPerPage: number }) {} -} - -export class LoadMoreProducts implements Action { - readonly type = ProductListingActionTypes.LoadMoreProducts; - constructor(public payload: { id: ProductListingID; page?: number }) {} -} - -export class LoadMoreProductsForParams implements Action { - readonly type = ProductListingActionTypes.LoadMoreProductsForParams; - constructor(public payload: { id: ProductListingID; page: number; sorting: string; filters: string }) {} -} - -export class SetViewType implements Action { - readonly type = ProductListingActionTypes.SetViewType; - constructor(public payload: { viewType: ViewType }) {} -} - -export class LoadPagesForMaster implements Action { - readonly type = ProductListingActionTypes.LoadPagesForMaster; - constructor(public payload: { id: ProductListingID; filters: string; sorting: string }) {} -} - -export type ProductListingAction = - | SetProductListingPages - | LoadMoreProducts - | LoadMoreProductsForParams - | SetProductListingPageSize - | SetViewType - | LoadPagesForMaster; +import { payload } from 'ish-core/utils/ngrx-creators'; + +export const setProductListingPages = createAction( + '[ProductListing] Set Product Listing Pages', + payload() +); + +export const setProductListingPageSize = createAction( + '[ProductListing] Set Product Listing Page Size', + payload<{ itemsPerPage: number }>() +); + +export const loadMoreProducts = createAction( + '[ProductListing] Load More Products', + payload<{ id: ProductListingID; page?: number }>() +); + +export const loadMoreProductsForParams = createAction( + '[ProductListing Internal] Load More Products For Params', + payload<{ id: ProductListingID; page: number; sorting: string; filters: string }>() +); + +export const setViewType = createAction('[ProductListing] Set View Type', payload<{ viewType: ViewType }>()); + +export const loadPagesForMaster = createAction( + '[ProductListing] Load Pages For Master', + payload<{ id: ProductListingID; filters: string; sorting: string }>() +); diff --git a/src/app/core/store/shopping/product-listing/product-listing.effects.spec.ts b/src/app/core/store/shopping/product-listing/product-listing.effects.spec.ts index 88612e25e6..5553d631fd 100644 --- a/src/app/core/store/shopping/product-listing/product-listing.effects.spec.ts +++ b/src/app/core/store/shopping/product-listing/product-listing.effects.spec.ts @@ -11,7 +11,7 @@ import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; -import { LoadMoreProducts } from './product-listing.actions'; +import { loadMoreProducts } from './product-listing.actions'; import { ProductListingEffects } from './product-listing.effects'; import { getProductListingItemsPerPage, getProductListingViewType } from './product-listing.selectors'; @@ -73,7 +73,7 @@ describe('Product Listing Effects', () => { })); it('should fire all necessary actions for search page', fakeAsync(() => { - store$.dispatch(new LoadMoreProducts({ id: { type: 'search', value: 'term' } })); + store$.dispatch(loadMoreProducts({ id: { type: 'search', value: 'term' } })); expect(store$.actionsArray()).toMatchInlineSnapshot(` [ProductListing] Load More Products: @@ -93,7 +93,7 @@ describe('Product Listing Effects', () => { })); it('should fire all necessary actions for family page', fakeAsync(() => { - store$.dispatch(new LoadMoreProducts({ id: { type: 'category', value: 'cat' } })); + store$.dispatch(loadMoreProducts({ id: { type: 'category', value: 'cat' } })); expect(store$.actionsArray()).toMatchInlineSnapshot(` [ProductListing] Load More Products: @@ -121,7 +121,7 @@ describe('Product Listing Effects', () => { })); it('should fire all necessary actions for search page', fakeAsync(() => { - store$.dispatch(new LoadMoreProducts({ id: { type: 'search', value: 'term' } })); + store$.dispatch(loadMoreProducts({ id: { type: 'search', value: 'term' } })); expect(store$.actionsArray()).toMatchInlineSnapshot(` [ProductListing] Load More Products: @@ -140,7 +140,7 @@ describe('Product Listing Effects', () => { })); it('should fire all necessary actions for family page', fakeAsync(() => { - store$.dispatch(new LoadMoreProducts({ id: { type: 'category', value: 'cat' } })); + store$.dispatch(loadMoreProducts({ id: { type: 'category', value: 'cat' } })); expect(store$.actionsArray()).toMatchInlineSnapshot(` [ProductListing] Load More Products: diff --git a/src/app/core/store/shopping/product-listing/product-listing.effects.ts b/src/app/core/store/shopping/product-listing/product-listing.effects.ts index b4b923feee..8c88669509 100644 --- a/src/app/core/store/shopping/product-listing/product-listing.effects.ts +++ b/src/app/core/store/shopping/product-listing/product-listing.effects.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import b64u from 'b64u'; import { isEqual } from 'lodash-es'; @@ -15,24 +15,23 @@ import { ViewType } from 'ish-core/models/viewtype/viewtype.types'; import { ProductMasterVariationsService } from 'ish-core/services/product-master-variations/product-master-variations.service'; import { selectQueryParam, selectQueryParams } from 'ish-core/store/core/router'; import { - ApplyFilter, - LoadFilterForCategory, - LoadFilterForSearch, - LoadFilterSuccess, - LoadProductsForFilter, + applyFilter, + loadFilterForCategory, + loadFilterForSearch, + loadFilterSuccess, + loadProductsForFilter, } from 'ish-core/store/shopping/filter'; -import { LoadProductsForCategory, getProduct } from 'ish-core/store/shopping/products'; -import { SearchProducts } from 'ish-core/store/shopping/search'; +import { getProduct, loadProductsForCategory } from 'ish-core/store/shopping/products'; +import { searchProducts } from 'ish-core/store/shopping/search'; import { mapToPayload, whenFalsy, whenTruthy } from 'ish-core/utils/operators'; import { - LoadMoreProducts, - LoadMoreProductsForParams, - LoadPagesForMaster, - ProductListingActionTypes, - SetProductListingPageSize, - SetProductListingPages, - SetViewType, + loadMoreProducts, + loadMoreProductsForParams, + loadPagesForMaster, + setProductListingPageSize, + setProductListingPages, + setViewType, } from './product-listing.actions'; import { getProductListingView, getProductListingViewType } from './product-listing.selectors'; @@ -47,148 +46,158 @@ export class ProductListingEffects { private productMasterVariationsService: ProductMasterVariationsService ) {} - @Effect() - initializePageSize$ = this.actions$.pipe( - take(1), - mapTo(new SetProductListingPageSize({ itemsPerPage: this.itemsPerPage })) + initializePageSize$ = createEffect(() => + this.actions$.pipe(take(1), mapTo(setProductListingPageSize({ itemsPerPage: this.itemsPerPage }))) ); - @Effect() - initializeDefaultViewType$ = this.store.pipe( - select(getProductListingViewType), - whenFalsy(), - mapTo(new SetViewType({ viewType: this.defaultViewType })) + initializeDefaultViewType$ = createEffect(() => + this.store.pipe( + select(getProductListingViewType), + whenFalsy(), + mapTo(setViewType({ viewType: this.defaultViewType })) + ) ); - @Effect() - setViewTypeFromQueryParam$ = this.store.pipe( - select(selectQueryParam('view')), - whenTruthy(), - distinctUntilChanged(), - map((viewType: ViewType) => new SetViewType({ viewType })) + setViewTypeFromQueryParam$ = createEffect(() => + this.store.pipe( + select(selectQueryParam('view')), + whenTruthy(), + distinctUntilChanged(), + map((viewType: ViewType) => setViewType({ viewType })) + ) ); - @Effect() - determineParams$ = this.actions$.pipe( - ofType(ProductListingActionTypes.LoadMoreProducts), - mapToPayload(), - switchMap(({ id, page }) => - this.store.pipe( - select(selectQueryParams), - map(params => ({ - id, - sorting: params.sorting || undefined, - page: +params.page || page || undefined, - filters: params.filters || undefined, - })) - ) - ), - distinctUntilChanged(isEqual), - map(({ id, filters, sorting, page }) => new LoadMoreProductsForParams({ id, filters, sorting, page })) + determineParams$ = createEffect(() => + this.actions$.pipe( + ofType(loadMoreProducts), + mapToPayload(), + switchMap(({ id, page }) => + this.store.pipe( + select(selectQueryParams), + map(params => ({ + id, + sorting: params.sorting || undefined, + page: +params.page || page || undefined, + filters: params.filters || undefined, + })) + ) + ), + distinctUntilChanged(isEqual), + map(({ id, filters, sorting, page }) => loadMoreProductsForParams({ id, filters, sorting, page })) + ) ); - @Effect() - loadMoreProducts$ = this.actions$.pipe( - ofType(ProductListingActionTypes.LoadMoreProductsForParams), - mapToPayload(), - switchMap(({ id, sorting, page, filters }) => - this.store.pipe( - select(getProductListingView, { ...id, sorting, filters }), - map(view => ({ id, sorting, page, filters, viewAvailable: !view.empty() && view.productsOfPage(page).length })) - ) - ), - map(({ id, sorting, page, filters, viewAvailable }) => { - if (viewAvailable) { - return new SetProductListingPages({ id: { sorting, filters, ...id } }); - } - if ( - filters && - // TODO: work-around for different products/hits-result without filters - (id.type !== 'search' || (id.type === 'search' && filters !== `&@QueryTerm=${id.value}&OnlineFlag=1`)) && - // TODO: work-around for client side computation of master variations - ['search', 'category'].includes(id.type) - ) { - const searchParameter = b64u.toBase64(b64u.encode(filters)); - return new LoadProductsForFilter({ id: { ...id, filters }, searchParameter }); - } else { - switch (id.type) { - case 'category': - return new LoadProductsForCategory({ categoryId: id.value, page, sorting }); - case 'search': - return new SearchProducts({ searchTerm: id.value, page, sorting }); - case 'master': - return new LoadPagesForMaster({ id, sorting, filters }); - default: - return; + loadMoreProducts$ = createEffect(() => + this.actions$.pipe( + ofType(loadMoreProductsForParams), + mapToPayload(), + switchMap(({ id, sorting, page, filters }) => + this.store.pipe( + select(getProductListingView, { ...id, sorting, filters }), + map(view => ({ + id, + sorting, + page, + filters, + viewAvailable: !view.empty() && view.productsOfPage(page).length, + })) + ) + ), + map(({ id, sorting, page, filters, viewAvailable }) => { + if (viewAvailable) { + return setProductListingPages({ id: { sorting, filters, ...id } }); + } + if ( + filters && + // TODO: work-around for different products/hits-result without filters + (id.type !== 'search' || (id.type === 'search' && filters !== `&@QueryTerm=${id.value}&OnlineFlag=1`)) && + // TODO: work-around for client side computation of master variations + ['search', 'category'].includes(id.type) + ) { + const searchParameter = b64u.toBase64(b64u.encode(filters)); + return loadProductsForFilter({ id: { ...id, filters }, searchParameter }); + } else { + switch (id.type) { + case 'category': + return loadProductsForCategory({ categoryId: id.value, page, sorting }); + case 'search': + return searchProducts({ searchTerm: id.value, page, sorting }); + case 'master': + return loadPagesForMaster({ id, sorting, filters }); + default: + return; + } } - } - }), - whenTruthy(), - distinctUntilChanged(isEqual) + }), + whenTruthy(), + distinctUntilChanged(isEqual) + ) ); - @Effect() - loadFilters$ = this.actions$.pipe( - ofType(ProductListingActionTypes.LoadMoreProductsForParams), - mapToPayload(), - map(({ id, filters }) => ({ type: id.type, value: id.value, filters })), - distinctUntilChanged(isEqual), - map(({ type, value, filters }) => { - if ( - filters && - // TODO: work-around for different products/hits-result without filters - (type !== 'search' || (type === 'search' && filters !== `&@QueryTerm=${value}&OnlineFlag=1`)) && - // TODO: work-around for client side computation of master variations - ['search', 'category'].includes(type) - ) { - const searchParameter = b64u.toBase64(b64u.encode(filters)); - return new ApplyFilter({ searchParameter }); - } else { - switch (type) { - case 'category': - return new LoadFilterForCategory({ uniqueId: value }); - case 'search': - return new LoadFilterForSearch({ searchTerm: value }); - case 'master': - return new LoadPagesForMaster({ id: { type, value }, sorting: undefined, filters }); - default: - return; + loadFilters$ = createEffect(() => + this.actions$.pipe( + ofType(loadMoreProductsForParams), + mapToPayload(), + map(({ id, filters }) => ({ type: id.type, value: id.value, filters })), + distinctUntilChanged(isEqual), + map(({ type, value, filters }) => { + if ( + filters && + // TODO: work-around for different products/hits-result without filters + (type !== 'search' || (type === 'search' && filters !== `&@QueryTerm=${value}&OnlineFlag=1`)) && + // TODO: work-around for client side computation of master variations + ['search', 'category'].includes(type) + ) { + const searchParameter = b64u.toBase64(b64u.encode(filters)); + return applyFilter({ searchParameter }); + } else { + switch (type) { + case 'category': + return loadFilterForCategory({ uniqueId: value }); + case 'search': + return loadFilterForSearch({ searchTerm: value }); + case 'master': + return loadPagesForMaster({ id: { type, value }, sorting: undefined, filters }); + default: + return; + } } - } - }), - whenTruthy() + }), + whenTruthy() + ) ); /** * client side computation of master variations * TODO: this is a work-around */ - @Effect() - loadPagesForMaster$ = this.actions$.pipe( - ofType(ProductListingActionTypes.LoadPagesForMaster), - mapToPayload(), - switchMap(({ id, filters }) => - this.store.pipe( - select(getProduct, { sku: id.value }), - filter(p => ProductHelper.isSufficientlyLoaded(p, ProductCompletenessLevel.Detail)), - filter(ProductHelper.hasVariations), - filter(ProductHelper.isMasterProduct), - take(1), - mergeMap(product => { - const { - filterNavigation, - products, - } = this.productMasterVariationsService.getFiltersAndFilteredVariationsForMasterProduct(product, filters); + loadPagesForMaster$ = createEffect(() => + this.actions$.pipe( + ofType(loadPagesForMaster), + mapToPayload(), + switchMap(({ id, filters }) => + this.store.pipe( + select(getProduct, { sku: id.value }), + filter(p => ProductHelper.isSufficientlyLoaded(p, ProductCompletenessLevel.Detail)), + filter(ProductHelper.hasVariations), + filter(ProductHelper.isMasterProduct), + take(1), + mergeMap(product => { + const { + filterNavigation, + products, + } = this.productMasterVariationsService.getFiltersAndFilteredVariationsForMasterProduct(product, filters); - return [ - new SetProductListingPages( - this.productListingMapper.createPages(products, id.type, id.value, { - filters: filters ? b64u.toBase64(b64u.encode(filters)) : undefined, - }) - ), - new LoadFilterSuccess({ filterNavigation }), - ]; - }) + return [ + setProductListingPages( + this.productListingMapper.createPages(products, id.type, id.value, { + filters: filters ? b64u.toBase64(b64u.encode(filters)) : undefined, + }) + ), + loadFilterSuccess({ filterNavigation }), + ]; + }) + ) ) ) ); diff --git a/src/app/core/store/shopping/product-listing/product-listing.reducer.ts b/src/app/core/store/shopping/product-listing/product-listing.reducer.ts index e7550e7371..8320a21f9a 100644 --- a/src/app/core/store/shopping/product-listing/product-listing.reducer.ts +++ b/src/app/core/store/shopping/product-listing/product-listing.reducer.ts @@ -1,12 +1,14 @@ import { EntityState, createEntityAdapter } from '@ngrx/entity'; +import { createReducer, on } from '@ngrx/store'; import { ProductListingID, ProductListingType } from 'ish-core/models/product-listing/product-listing.model'; import { ViewType } from 'ish-core/models/viewtype/viewtype.types'; -import { FilterActionTypes, FilterActions } from 'ish-core/store/shopping/filter'; -import { ProductsAction, ProductsActionTypes } from 'ish-core/store/shopping/products'; -import { SearchAction, SearchActionTypes } from 'ish-core/store/shopping/search'; +import { loadProductsForFilter } from 'ish-core/store/shopping/filter'; +import { loadProductsForCategory, loadProductsForCategoryFail } from 'ish-core/store/shopping/products'; +import { searchProducts, searchProductsFail } from 'ish-core/store/shopping/search'; +import { setLoadingOn } from 'ish-core/utils/ngrx-creators'; -import { ProductListingAction, ProductListingActionTypes } from './product-listing.actions'; +import { setProductListingPageSize, setProductListingPages, setViewType } from './product-listing.actions'; export function serializeProductListingID(id: ProductListingID) { return `${id.type}@${id.value}@${id.filters || id.sorting}`; @@ -68,39 +70,25 @@ function mergeCurrentSettings( return { ...currentSettings, [serializedId]: { ...oldSettings, ...newSettings } }; } -export function productListingReducer( - state = initialState, - action: ProductListingAction | SearchAction | ProductsAction | FilterActions -): ProductListingState { - switch (action.type) { - case ProductListingActionTypes.SetProductListingPageSize: - return { ...state, itemsPerPage: action.payload.itemsPerPage }; - - case ProductListingActionTypes.SetViewType: - return { ...state, viewType: action.payload.viewType }; - - case SearchActionTypes.SearchProducts: - case ProductsActionTypes.LoadProductsForCategory: - case FilterActionTypes.LoadProductsForFilter: - return { ...state, loading: true }; - - case SearchActionTypes.SearchProductsFail: - case ProductsActionTypes.LoadProductsForCategoryFail: - return { ...state, loading: false }; - - case ProductListingActionTypes.SetProductListingPages: { - const pages = - action.payload.pages || - calculatePages({ ...state.entities[serializeProductListingID(action.payload.id)], ...action.payload }); - - const currentSettings = mergeCurrentSettings(state.currentSettings, action.payload.id, { - sorting: action.payload.id.sorting, - filters: action.payload.id.filters, - }); - - return adapter.upsertOne({ ...action.payload, pages }, { ...state, loading: false, currentSettings }); - } - } - - return state; -} +export const productListingReducer = createReducer( + initialState, + on(setProductListingPageSize, (state: ProductListingState, action) => ({ + ...state, + itemsPerPage: action.payload.itemsPerPage, + })), + on(setViewType, (state: ProductListingState, action) => ({ ...state, viewType: action.payload.viewType })), + setLoadingOn(searchProducts, loadProductsForCategory, loadProductsForFilter), + on(searchProductsFail, loadProductsForCategoryFail, (state: ProductListingState) => ({ ...state, loading: false })), + on(setProductListingPages, (state: ProductListingState, action) => { + const pages = + action.payload.pages || + calculatePages({ ...state.entities[serializeProductListingID(action.payload.id)], ...action.payload }); + + const currentSettings = mergeCurrentSettings(state.currentSettings, action.payload.id, { + sorting: action.payload.id.sorting, + filters: action.payload.id.filters, + }); + + return adapter.upsertOne({ ...action.payload, pages }, { ...state, loading: false, currentSettings }); + }) +); diff --git a/src/app/core/store/shopping/product-listing/product-listing.selectors.spec.ts b/src/app/core/store/shopping/product-listing/product-listing.selectors.spec.ts index 0cee47f65e..c6de59ec5e 100644 --- a/src/app/core/store/shopping/product-listing/product-listing.selectors.spec.ts +++ b/src/app/core/store/shopping/product-listing/product-listing.selectors.spec.ts @@ -6,7 +6,7 @@ import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; -import { SetProductListingPageSize, SetProductListingPages } from './product-listing.actions'; +import { setProductListingPageSize, setProductListingPages } from './product-listing.actions'; import { getProductListingLoading, getProductListingView } from './product-listing.selectors'; describe('Product Listing Selectors', () => { @@ -72,9 +72,9 @@ describe('Product Listing Selectors', () => { describe('when first page was added', () => { beforeEach(() => { - store$.dispatch(new SetProductListingPageSize({ itemsPerPage: 2 })); + store$.dispatch(setProductListingPageSize({ itemsPerPage: 2 })); store$.dispatch( - new SetProductListingPages({ + setProductListingPages({ id: TEST_ID, itemCount: 4, sortKeys: ['by-name', 'by-date'], @@ -118,7 +118,7 @@ describe('Product Listing Selectors', () => { describe('when second (last) page was added', () => { beforeEach(() => { store$.dispatch( - new SetProductListingPages({ + setProductListingPages({ id: TEST_ID, itemCount: 4, sortKeys: ['by-name', 'by-date'], @@ -168,9 +168,9 @@ describe('Product Listing Selectors', () => { describe('when any page was added', () => { beforeEach(() => { - store$.dispatch(new SetProductListingPageSize({ itemsPerPage: 2 })); + store$.dispatch(setProductListingPageSize({ itemsPerPage: 2 })); store$.dispatch( - new SetProductListingPages({ + setProductListingPages({ id: TEST_ID, itemCount: 6, sortKeys: ['by-name', 'by-date'], @@ -217,9 +217,9 @@ describe('Product Listing Selectors', () => { let view: ProductListingView; beforeEach(() => { - store$.dispatch(new SetProductListingPageSize({ itemsPerPage: 2 })); + store$.dispatch(setProductListingPageSize({ itemsPerPage: 2 })); store$.dispatch( - new SetProductListingPages({ + setProductListingPages({ id: TEST_ID, itemCount: 61, sortKeys: [], @@ -290,9 +290,9 @@ describe('Product Listing Selectors', () => { let view: ProductListingView; beforeEach(() => { - store$.dispatch(new SetProductListingPageSize({ itemsPerPage: 2 })); + store$.dispatch(setProductListingPageSize({ itemsPerPage: 2 })); store$.dispatch( - new SetProductListingPages({ + setProductListingPages({ id: TEST_ID, itemCount: 6000, sortKeys: [], diff --git a/src/app/core/store/shopping/products/products.actions.ts b/src/app/core/store/shopping/products/products.actions.ts index 16c5146c5c..6910bac0f3 100644 --- a/src/app/core/store/shopping/products/products.actions.ts +++ b/src/app/core/store/shopping/products/products.actions.ts @@ -1,108 +1,57 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; -import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { ProductLinks } from 'ish-core/models/product-links/product-links.model'; import { Product, ProductCompletenessLevel, SkuQuantityType } from 'ish-core/models/product/product.model'; +import { httpError, payload } from 'ish-core/utils/ngrx-creators'; -export enum ProductsActionTypes { - LoadProduct = '[Shopping] Load Product', - LoadProductBundlesSuccess = '[Shopping] Load Product Bundles Success', - LoadProductFail = '[Shopping] Load Product Fail', - LoadProductIfNotLoaded = '[Shopping] Load Product if not Loaded', - LoadProductSuccess = '[Shopping] Load Product Success', - LoadProductsForCategory = '[Shopping] Load Products for Category', - LoadProductsForCategoryFail = '[Shopping] Load Products for Category Fail', - LoadProductVariations = '[Shopping] Load Product Variations', - LoadProductVariationsFail = '[Shopping] Load Product Variations Fail', - LoadProductVariationsSuccess = '[Shopping] Load Product Variations Success', - LoadRetailSetSuccess = '[Shopping] Load Retail Set Success', - LoadProductLinks = '[Shopping] Load Product Links', - LoadProductLinksFail = '[Shopping] Load Product Links Fail', - LoadProductLinksSuccess = '[Shopping] Load Product Links Success', -} +export const loadProduct = createAction('[Shopping] Load Product', payload<{ sku: string }>()); -export class LoadProduct implements Action { - readonly type = ProductsActionTypes.LoadProduct; - constructor(public payload: { sku: string }) {} -} +export const loadProductFail = createAction('[Shopping] Load Product Fail', httpError<{ sku: string }>()); -export class LoadProductFail implements Action { - readonly type = ProductsActionTypes.LoadProductFail; - constructor(public payload: { error: HttpError; sku: string }) {} -} +export const loadProductIfNotLoaded = createAction( + '[Shopping] Load Product if not Loaded', + payload<{ sku: string; level: ProductCompletenessLevel }>() +); -export class LoadProductIfNotLoaded implements Action { - readonly type = ProductsActionTypes.LoadProductIfNotLoaded; - constructor(public payload: { sku: string; level: ProductCompletenessLevel }) {} -} +export const loadProductSuccess = createAction('[Shopping] Load Product Success', payload<{ product: Product }>()); -export class LoadProductSuccess implements Action { - readonly type = ProductsActionTypes.LoadProductSuccess; - constructor(public payload: { product: Product }) {} -} +export const loadProductsForCategory = createAction( + '[Shopping] Load Products for Category', + payload<{ categoryId: string; page?: number; sorting?: string }>() +); -export class LoadProductsForCategory implements Action { - readonly type = ProductsActionTypes.LoadProductsForCategory; - constructor(public payload: { categoryId: string; page?: number; sorting?: string }) {} -} +export const loadProductsForCategoryFail = createAction( + '[Shopping] Load Products for Category Fail', + httpError<{ categoryId: string }>() +); -export class LoadProductsForCategoryFail implements Action { - readonly type = ProductsActionTypes.LoadProductsForCategoryFail; - constructor(public payload: { error: HttpError; categoryId: string }) {} -} +export const loadProductVariations = createAction('[Shopping] Load Product Variations', payload<{ sku: string }>()); -export class LoadProductVariations implements Action { - readonly type = ProductsActionTypes.LoadProductVariations; - constructor(public payload: { sku: string }) {} -} +export const loadProductVariationsFail = createAction( + '[Shopping] Load Product Variations Fail', + httpError<{ sku: string }>() +); -export class LoadProductVariationsFail implements Action { - readonly type = ProductsActionTypes.LoadProductVariationsFail; - constructor(public payload: { error: HttpError; sku: string }) {} -} +export const loadProductVariationsSuccess = createAction( + '[Shopping] Load Product Variations Success', + payload<{ sku: string; variations: string[]; defaultVariation: string }>() +); -export class LoadProductVariationsSuccess implements Action { - readonly type = ProductsActionTypes.LoadProductVariationsSuccess; - constructor(public payload: { sku: string; variations: string[]; defaultVariation: string }) {} -} +export const loadProductBundlesSuccess = createAction( + '[Shopping] Load Product Bundles Success', + payload<{ sku: string; bundledProducts: SkuQuantityType[] }>() +); -export class LoadProductBundlesSuccess implements Action { - readonly type = ProductsActionTypes.LoadProductBundlesSuccess; - constructor(public payload: { sku: string; bundledProducts: SkuQuantityType[] }) {} -} +export const loadRetailSetSuccess = createAction( + '[Shopping] Load Retail Set Success', + payload<{ sku: string; parts: string[] }>() +); -export class LoadRetailSetSuccess implements Action { - readonly type = ProductsActionTypes.LoadRetailSetSuccess; - constructor(public payload: { sku: string; parts: string[] }) {} -} +export const loadProductLinks = createAction('[Shopping] Load Product Links', payload<{ sku: string }>()); -export class LoadProductLinks implements Action { - readonly type = ProductsActionTypes.LoadProductLinks; - constructor(public payload: { sku: string }) {} -} +export const loadProductLinksFail = createAction('[Shopping] Load Product Links Fail', httpError<{ sku: string }>()); -export class LoadProductLinksFail implements Action { - readonly type = ProductsActionTypes.LoadProductLinksFail; - constructor(public payload: { error: HttpError; sku: string }) {} -} - -export class LoadProductLinksSuccess implements Action { - readonly type = ProductsActionTypes.LoadProductLinksSuccess; - constructor(public payload: { sku: string; links: ProductLinks }) {} -} - -export type ProductsAction = - | LoadProduct - | LoadProductBundlesSuccess - | LoadProductFail - | LoadProductIfNotLoaded - | LoadProductSuccess - | LoadProductsForCategory - | LoadProductsForCategoryFail - | LoadProductVariations - | LoadProductVariationsFail - | LoadProductVariationsSuccess - | LoadRetailSetSuccess - | LoadProductLinks - | LoadProductLinksFail - | LoadProductLinksSuccess; +export const loadProductLinksSuccess = createAction( + '[Shopping] Load Product Links Success', + payload<{ sku: string; links: ProductLinks }>() +); diff --git a/src/app/core/store/shopping/products/products.effects.spec.ts b/src/app/core/store/shopping/products/products.effects.spec.ts index 4366046a84..f913ac8bec 100644 --- a/src/app/core/store/shopping/products/products.effects.spec.ts +++ b/src/app/core/store/shopping/products/products.effects.spec.ts @@ -17,23 +17,23 @@ import { VariationProduct } from 'ish-core/models/product/product-variation.mode import { Product, ProductCompletenessLevel } from 'ish-core/models/product/product.model'; import { ProductsService } from 'ish-core/services/products/products.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; -import { LoadCategory } from 'ish-core/store/shopping/categories'; -import { SetProductListingPageSize, SetProductListingPages } from 'ish-core/store/shopping/product-listing'; +import { loadCategory } from 'ish-core/store/shopping/categories'; +import { setProductListingPageSize, setProductListingPages } from 'ish-core/store/shopping/product-listing'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { - LoadProduct, - LoadProductFail, - LoadProductIfNotLoaded, - LoadProductLinks, - LoadProductLinksFail, - LoadProductLinksSuccess, - LoadProductSuccess, - LoadProductVariations, - LoadProductVariationsFail, - LoadProductVariationsSuccess, - LoadProductsForCategory, - LoadProductsForCategoryFail, + loadProduct, + loadProductFail, + loadProductIfNotLoaded, + loadProductLinks, + loadProductLinksFail, + loadProductLinksSuccess, + loadProductSuccess, + loadProductVariations, + loadProductVariationsFail, + loadProductVariationsSuccess, + loadProductsForCategory, + loadProductsForCategoryFail, } from './products.actions'; import { ProductsEffects } from './products.effects'; @@ -97,13 +97,13 @@ describe('Products Effects', () => { store$ = TestBed.inject(Store); router = TestBed.inject(Router); location = TestBed.inject(Location); - store$.dispatch(new SetProductListingPageSize({ itemsPerPage: TestBed.inject(PRODUCT_LISTING_ITEMS_PER_PAGE) })); + store$.dispatch(setProductListingPageSize({ itemsPerPage: TestBed.inject(PRODUCT_LISTING_ITEMS_PER_PAGE) })); }); describe('loadProductBundles$', () => { it('should call the productsService for LoadProductBundles action', done => { const sku = 'P123'; - const action = new LoadProductSuccess({ product: { sku, type: 'Bundle' } as Product }); + const action = loadProductSuccess({ product: { sku, type: 'Bundle' } as Product }); actions$ = of(action); effects.loadProductBundles$.subscribe(() => { @@ -116,7 +116,7 @@ describe('Products Effects', () => { describe('loadProduct$', () => { it('should call the productsService for LoadProduct action', done => { const sku = 'P123'; - const action = new LoadProduct({ sku }); + const action = loadProduct({ sku }); actions$ = of(action); effects.loadProduct$.subscribe(() => { @@ -127,8 +127,8 @@ describe('Products Effects', () => { it('should map to action of type LoadProductSuccess', () => { const sku = 'P123'; - const action = new LoadProduct({ sku }); - const completion = new LoadProductSuccess({ product: { sku } as Product }); + const action = loadProduct({ sku }); + const completion = loadProductSuccess({ product: { sku } as Product }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -137,8 +137,8 @@ describe('Products Effects', () => { it('should map invalid request to action of type LoadProductFail', () => { const sku = 'invalid'; - const action = new LoadProduct({ sku }); - const completion = new LoadProductFail({ error: { message: 'invalid' } as HttpError, sku }); + const action = loadProduct({ sku }); + const completion = loadProductFail({ error: { message: 'invalid' } as HttpError, sku }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -148,7 +148,7 @@ describe('Products Effects', () => { describe('loadProductsForCategory$', () => { it('should call service for SKU list', done => { - actions$ = of(new LoadProductsForCategory({ categoryId: '123', sorting: 'name-asc' })); + actions$ = of(loadProductsForCategory({ categoryId: '123', sorting: 'name-asc' })); effects.loadProductsForCategory$.subscribe(() => { verify(productsServiceMock.getCategoryProducts('123', anyNumber(), 'name-asc')).once(); @@ -158,12 +158,12 @@ describe('Products Effects', () => { it('should trigger actions for loading content for the product list', () => { actions$ = hot('a', { - a: new LoadProductsForCategory({ categoryId: '123' }), + a: loadProductsForCategory({ categoryId: '123' }), }); const expectedValues = { - b: new LoadProductSuccess({ product: { sku: 'P222' } as Product }), - c: new LoadProductSuccess({ product: { sku: 'P333' } as Product }), - d: new SetProductListingPages({ + b: loadProductSuccess({ product: { sku: 'P222' } as Product }), + c: loadProductSuccess({ product: { sku: 'P333' } as Product }), + d: setProductListingPages({ id: { type: 'category', value: '123' }, itemCount: 2, sortKeys: ['name-asc', 'name-desc'], @@ -178,11 +178,11 @@ describe('Products Effects', () => { throwError({ message: 'ERROR' }) ); actions$ = hot('-a-a-a', { - a: new LoadProductsForCategory({ categoryId: '123' }), + a: loadProductsForCategory({ categoryId: '123' }), }); expect(effects.loadProductsForCategory$).toBeObservable( cold('-a-a-a', { - a: new LoadProductsForCategoryFail({ + a: loadProductsForCategoryFail({ error: { message: 'ERROR' } as HttpError, categoryId: '123', }), @@ -199,7 +199,7 @@ describe('Products Effects', () => { }); it('should call the productsService for getProductVariations', done => { - const action = new LoadProductVariations({ sku: 'MSKU' }); + const action = loadProductVariations({ sku: 'MSKU' }); actions$ = of(action); effects.loadProductVariations$.subscribe(() => { @@ -209,8 +209,8 @@ describe('Products Effects', () => { }); it('should map to action of type LoadProductVariationsSuccess', () => { - const action = new LoadProductVariations({ sku: 'MSKU' }); - const completion = new LoadProductVariationsSuccess({ + const action = loadProductVariations({ sku: 'MSKU' }); + const completion = loadProductVariationsSuccess({ sku: 'MSKU', variations: [], defaultVariation: undefined, @@ -223,8 +223,8 @@ describe('Products Effects', () => { it('should map invalid request to action of type LoadProductVariationsFail', () => { when(productsServiceMock.getProductVariations(anyString())).thenCall(() => throwError({ message: 'invalid' })); - const action = new LoadProductVariations({ sku: 'MSKU' }); - const completion = new LoadProductVariationsFail({ + const action = loadProductVariations({ sku: 'MSKU' }); + const completion = loadProductVariationsFail({ error: { message: 'invalid' } as HttpError, sku: 'MSKU', }); @@ -237,13 +237,13 @@ describe('Products Effects', () => { describe('loadMasterProductForProduct$', () => { it('should trigger LoadProduct action if LoadProductSuccess contains productMasterSKU', () => { - const action = new LoadProductSuccess({ + const action = loadProductSuccess({ product: { productMasterSKU: 'MSKU', type: 'VariationProduct', } as VariationProduct, }); - const completion = new LoadProductIfNotLoaded({ sku: 'MSKU', level: ProductCompletenessLevel.List }); + const completion = loadProductIfNotLoaded({ sku: 'MSKU', level: ProductCompletenessLevel.List }); actions$ = hot('-a', { a: action }); const expected$ = cold('-c', { c: completion }); @@ -251,9 +251,9 @@ describe('Products Effects', () => { }); it('should not trigger LoadProduct action if LoadProductSuccess contains productMasterSKU of loaded product', () => { - store$.dispatch(new LoadProductSuccess({ product: { sku: 'MSKU' } as Product })); + store$.dispatch(loadProductSuccess({ product: { sku: 'MSKU' } as Product })); - const action = new LoadProductSuccess({ + const action = loadProductSuccess({ product: { productMasterSKU: 'MSKU', type: 'VariationProduct', @@ -268,13 +268,13 @@ describe('Products Effects', () => { describe('loadProductVariationsForMasterProduct$', () => { it('should trigger LoadProductVariations action if LoadProductSuccess triggered for master product', () => { - const action = new LoadProductSuccess({ + const action = loadProductSuccess({ product: { sku: 'MSKU', type: 'VariationProductMaster', } as VariationProductMaster, }); - const completion = new LoadProductVariations({ sku: 'MSKU' }); + const completion = loadProductVariations({ sku: 'MSKU' }); actions$ = hot('-a', { a: action }); const expected$ = cold('-c', { c: completion }); @@ -287,10 +287,10 @@ describe('Products Effects', () => { type: 'VariationProductMaster', } as VariationProductMaster; - store$.dispatch(new LoadProductSuccess({ product })); - store$.dispatch(new LoadProductVariationsSuccess({ sku: 'MSKU', variations: ['VAR'], defaultVariation: 'VAR' })); + store$.dispatch(loadProductSuccess({ product })); + store$.dispatch(loadProductVariationsSuccess({ sku: 'MSKU', variations: ['VAR'], defaultVariation: 'VAR' })); - const action = new LoadProductSuccess({ product }); + const action = loadProductSuccess({ product }); actions$ = hot('-a', { a: action }); const expected$ = cold('-'); @@ -298,7 +298,7 @@ describe('Products Effects', () => { }); it('should not trigger LoadProductVariants action if loaded product is not of type VariationProductMaster.', () => { - const action = new LoadProductSuccess({ + const action = loadProductSuccess({ product: { sku: 'SKU', type: 'Product', @@ -347,7 +347,7 @@ describe('Products Effects', () => { describe('redirectIfErrorInProducts$', () => { beforeEach(() => { - store$.dispatch(new LoadProductFail({ sku: 'SKU', error: { status: 404 } as HttpError })); + store$.dispatch(loadProductFail({ sku: 'SKU', error: { status: 404 } as HttpError })); }); it('should redirect if triggered on product detail page', fakeAsync(() => { @@ -371,7 +371,7 @@ describe('Products Effects', () => { describe('redirectIfErrorInCategoryProducts$', () => { it('should redirect if triggered', fakeAsync(() => { - const action = new LoadProductsForCategoryFail({ + const action = loadProductsForCategoryFail({ categoryId: 'ID', error: { status: 404 } as HttpError, }); @@ -398,7 +398,7 @@ describe('Products Effects', () => { }) ); - actions$ = of(new LoadProductSuccess({ product: { sku: 'ABC', type: 'Bundle' } as Product })); + actions$ = of(loadProductSuccess({ product: { sku: 'ABC', type: 'Bundle' } as Product })); effects.loadProductBundles$.pipe(toArray()).subscribe(actions => { expect(actions).toMatchInlineSnapshot(` @@ -418,7 +418,7 @@ describe('Products Effects', () => { describe('loadRetailSetProductDetail$', () => { it('should trigger loading details if it is a retail set', done => { actions$ = of( - new LoadProductSuccess({ + loadProductSuccess({ product: { sku: 'ABC', type: 'RetailSet' } as Product, }) ); @@ -435,7 +435,7 @@ describe('Products Effects', () => { it('should do nothing if product is not a retail set', done => { actions$ = of( - new LoadProductSuccess({ + loadProductSuccess({ product: { sku: 'ABC', type: 'Product' } as Product, }) ); @@ -451,7 +451,7 @@ describe('Products Effects', () => { it('should load stubs and retail set reference when queried', done => { when(productsServiceMock.getRetailSetParts('ABC')).thenReturn(of([{ sku: 'A' }, { sku: 'B' }])); - actions$ = of(new LoadProductSuccess({ product: { sku: 'ABC', type: 'RetailSet' } as Product })); + actions$ = of(loadProductSuccess({ product: { sku: 'ABC', type: 'RetailSet' } as Product })); effects.loadPartsOfRetailSet$.pipe(toArray()).subscribe(actions => { expect(actions).toMatchInlineSnapshot(` @@ -474,10 +474,10 @@ describe('Products Effects', () => { of({ linkType: { products: ['prod'], categories: [] } }) ); - actions$ = hot('a', { a: new LoadProductLinks({ sku: 'ABC' }) }); + actions$ = hot('a', { a: loadProductLinks({ sku: 'ABC' }) }); expect(effects.loadProductLinks$).toBeObservable( cold('(a)', { - a: new LoadProductLinksSuccess({ + a: loadProductLinksSuccess({ sku: 'ABC', links: { linkType: { products: ['prod'], categories: [] } }, }), @@ -488,10 +488,10 @@ describe('Products Effects', () => { it('should send fail action in case of failure for load product links', () => { when(productsServiceMock.getProductLinks('ABC')).thenReturn(throwError({ message: 'ERROR' })); - actions$ = hot('a', { a: new LoadProductLinks({ sku: 'ABC' }) }); + actions$ = hot('a', { a: loadProductLinks({ sku: 'ABC' }) }); expect(effects.loadProductLinks$).toBeObservable( cold('(a)', { - a: new LoadProductLinksFail({ + a: loadProductLinksFail({ error: { message: 'ERROR' } as HttpError, sku: 'ABC', }), @@ -503,7 +503,7 @@ describe('Products Effects', () => { describe('loadLinkedCategories$', () => { it('should load category links reference when queried', () => { actions$ = hot('(a)', { - a: new LoadProductLinksSuccess({ + a: loadProductLinksSuccess({ sku: 'ABC', links: { linkType1: { products: [], categories: ['cat1', 'cat2'] }, @@ -513,13 +513,13 @@ describe('Products Effects', () => { }); expect(effects.loadLinkedCategories$).toBeObservable( cold('(abc)', { - a: new LoadCategory({ + a: loadCategory({ categoryId: 'cat1', }), - b: new LoadCategory({ + b: loadCategory({ categoryId: 'cat2', }), - c: new LoadCategory({ + c: loadCategory({ categoryId: 'cat3', }), }) @@ -530,7 +530,7 @@ describe('Products Effects', () => { describe('loadDefaultCategoryContextForProduct$', () => { it('should load a default category for the product if none is selected and product has one', done => { store$.dispatch( - new LoadProductSuccess({ + loadProductSuccess({ product: { sku: 'ABC', type: 'Product', defaultCategoryId: '123' } as Product, }) ); @@ -548,7 +548,7 @@ describe('Products Effects', () => { it('should not load a default category for the product if none is selected and product has none', done => { store$.dispatch( - new LoadProductSuccess({ + loadProductSuccess({ product: { sku: 'ABC', type: 'Product' } as Product, }) ); @@ -562,7 +562,7 @@ describe('Products Effects', () => { it('should not load a default category for the product if the product failed loading', done => { store$.dispatch( - new LoadProductFail({ + loadProductFail({ sku: 'ABC', error: { error: 'ERROR' } as HttpError, }) @@ -577,7 +577,7 @@ describe('Products Effects', () => { it('should not load a default category for the product if the category is taken from the context', done => { store$.dispatch( - new LoadProductSuccess({ + loadProductSuccess({ product: { sku: 'ABC', type: 'Product', defaultCategoryId: '123' } as Product, }) ); diff --git a/src/app/core/store/shopping/products/products.effects.ts b/src/app/core/store/shopping/products/products.effects.ts index bb04b41f89..35c85da74c 100644 --- a/src/app/core/store/shopping/products/products.effects.ts +++ b/src/app/core/store/shopping/products/products.effects.ts @@ -1,6 +1,6 @@ import { isPlatformBrowser } from '@angular/common'; import { Inject, Injectable, PLATFORM_ID } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Dictionary } from '@ngrx/entity'; import { Store, select } from '@ngrx/store'; import { identity } from 'rxjs'; @@ -25,8 +25,8 @@ import { Product, ProductCompletenessLevel, ProductHelper } from 'ish-core/model import { ofProductUrl } from 'ish-core/routing/product/product.route'; import { ProductsService } from 'ish-core/services/products/products.service'; import { selectRouteParam } from 'ish-core/store/core/router'; -import { LoadCategory } from 'ish-core/store/shopping/categories'; -import { SetProductListingPages } from 'ish-core/store/shopping/product-listing'; +import { loadCategory } from 'ish-core/store/shopping/categories'; +import { setProductListingPages } from 'ish-core/store/shopping/product-listing'; import { HttpStatusCodeService } from 'ish-core/utils/http-status-code/http-status-code.service'; import { mapErrorToAction, @@ -37,21 +37,20 @@ import { } from 'ish-core/utils/operators'; import { - LoadProduct, - LoadProductBundlesSuccess, - LoadProductFail, - LoadProductIfNotLoaded, - LoadProductLinks, - LoadProductLinksFail, - LoadProductLinksSuccess, - LoadProductSuccess, - LoadProductVariations, - LoadProductVariationsFail, - LoadProductVariationsSuccess, - LoadProductsForCategory, - LoadProductsForCategoryFail, - LoadRetailSetSuccess, - ProductsActionTypes, + loadProduct, + loadProductBundlesSuccess, + loadProductFail, + loadProductIfNotLoaded, + loadProductLinks, + loadProductLinksFail, + loadProductLinksSuccess, + loadProductSuccess, + loadProductVariations, + loadProductVariationsFail, + loadProductVariationsSuccess, + loadProductsForCategory, + loadProductsForCategoryFail, + loadRetailSetSuccess, } from './products.actions'; import { getProductEntities, getSelectedProduct } from './products.selectors'; @@ -66,29 +65,31 @@ export class ProductsEffects { @Inject(PLATFORM_ID) private platformId: string ) {} - @Effect() - loadProduct$ = this.actions$.pipe( - ofType(ProductsActionTypes.LoadProduct), - mapToPayloadProperty('sku'), - mergeMap(sku => - this.productsService.getProduct(sku).pipe( - map(product => new LoadProductSuccess({ product })), - mapErrorToAction(LoadProductFail, { sku }) + loadProduct$ = createEffect(() => + this.actions$.pipe( + ofType(loadProduct), + mapToPayloadProperty('sku'), + mergeMap(sku => + this.productsService.getProduct(sku).pipe( + map(product => loadProductSuccess({ product })), + mapErrorToAction(loadProductFail, { sku }) + ) ) ) ); - @Effect() - loadProductIfNotLoaded$ = this.actions$.pipe( - ofType(ProductsActionTypes.LoadProductIfNotLoaded), - mapToPayload(), - withLatestFrom(this.store.pipe(select(getProductEntities))), - filter(([{ sku, level }, entities]) => !ProductHelper.isSufficientlyLoaded(entities[sku], level)), - groupBy(([{ sku }]) => sku), - mergeMap(group$ => - group$.pipe( - this.throttleOnBrowser(), - map(([{ sku }]) => new LoadProduct({ sku })) + loadProductIfNotLoaded$ = createEffect(() => + this.actions$.pipe( + ofType(loadProductIfNotLoaded), + mapToPayload(), + withLatestFrom(this.store.pipe(select(getProductEntities))), + filter(([{ sku, level }, entities]) => !ProductHelper.isSufficientlyLoaded(entities[sku], level)), + groupBy(([{ sku }]) => sku), + mergeMap(group$ => + group$.pipe( + this.throttleOnBrowser(), + map(([{ sku }]) => loadProduct({ sku })) + ) ) ) ); @@ -96,46 +97,48 @@ export class ProductsEffects { /** * retrieve products for category incremental respecting paging */ - @Effect() - loadProductsForCategory$ = this.actions$.pipe( - ofType(ProductsActionTypes.LoadProductsForCategory), - mapToPayload(), - map(payload => ({ ...payload, page: payload.page ? payload.page : 1 })), - concatMap(({ categoryId, page, sorting }) => - this.productsService.getCategoryProducts(categoryId, page, sorting).pipe( - concatMap(({ total, products, sortKeys }) => [ - ...products.map(product => new LoadProductSuccess({ product })), - new SetProductListingPages( - this.productListingMapper.createPages( - products.map(p => p.sku), - 'category', - categoryId, - { - startPage: page, - sortKeys, - sorting, - itemCount: total, - } - ) - ), - ]), - mapErrorToAction(LoadProductsForCategoryFail, { categoryId }) + loadProductsForCategory$ = createEffect(() => + this.actions$.pipe( + ofType(loadProductsForCategory), + mapToPayload(), + map(payload => ({ ...payload, page: payload.page ? payload.page : 1 })), + concatMap(({ categoryId, page, sorting }) => + this.productsService.getCategoryProducts(categoryId, page, sorting).pipe( + concatMap(({ total, products, sortKeys }) => [ + ...products.map(product => loadProductSuccess({ product })), + setProductListingPages( + this.productListingMapper.createPages( + products.map(p => p.sku), + 'category', + categoryId, + { + startPage: page, + sortKeys, + sorting, + itemCount: total, + } + ) + ), + ]), + mapErrorToAction(loadProductsForCategoryFail, { categoryId }) + ) ) ) ); - @Effect() - loadProductBundles$ = this.actions$.pipe( - ofType(ProductsActionTypes.LoadProductSuccess), - mapToPayloadProperty('product'), - filter(product => ProductHelper.isProductBundle(product)), - mergeMap(({ sku }) => - this.productsService.getProductBundles(sku).pipe( - mergeMap(({ stubs, bundledProducts }) => [ - ...stubs.map((product: Product) => new LoadProductSuccess({ product })), - new LoadProductBundlesSuccess({ sku, bundledProducts }), - ]), - mapErrorToAction(LoadProductFail, { sku }) + loadProductBundles$ = createEffect(() => + this.actions$.pipe( + ofType(loadProductSuccess), + mapToPayloadProperty('product'), + filter(product => ProductHelper.isProductBundle(product)), + mergeMap(({ sku }) => + this.productsService.getProductBundles(sku).pipe( + mergeMap(({ stubs, bundledProducts }) => [ + ...stubs.map((product: Product) => loadProductSuccess({ product })), + loadProductBundlesSuccess({ sku, bundledProducts }), + ]), + mapErrorToAction(loadProductFail, { sku }) + ) ) ) ); @@ -143,21 +146,22 @@ export class ProductsEffects { /** * The load product variations effect. */ - @Effect() - loadProductVariations$ = this.actions$.pipe( - ofType(ProductsActionTypes.LoadProductVariations), - mapToPayloadProperty('sku'), - mergeMap(sku => - this.productsService.getProductVariations(sku).pipe( - mergeMap(({ products: variations, defaultVariation }) => [ - ...variations.map((product: Product) => new LoadProductSuccess({ product })), - new LoadProductVariationsSuccess({ - sku, - variations: variations.map(p => p.sku), - defaultVariation, - }), - ]), - mapErrorToAction(LoadProductVariationsFail, { sku }) + loadProductVariations$ = createEffect(() => + this.actions$.pipe( + ofType(loadProductVariations), + mapToPayloadProperty('sku'), + mergeMap(sku => + this.productsService.getProductVariations(sku).pipe( + mergeMap(({ products: variations, defaultVariation }) => [ + ...variations.map((product: Product) => loadProductSuccess({ product })), + loadProductVariationsSuccess({ + sku, + variations: variations.map(p => p.sku), + defaultVariation, + }), + ]), + mapErrorToAction(loadProductVariationsFail, { sku }) + ) ) ) ); @@ -166,25 +170,25 @@ export class ProductsEffects { * Trigger load product action if productMasterSKU is set in product success action payload. * Ignores products that are already present. */ - @Effect() - loadMasterProductForProduct$ = this.actions$.pipe( - ofType(ProductsActionTypes.LoadProductSuccess), - mapToPayloadProperty('product'), - filter(product => ProductHelper.isVariationProduct(product)), - withLatestFrom(this.store.pipe(select(getProductEntities))), - filter( - ([product, entities]: [VariationProduct, Dictionary]) => !entities[product.productMasterSKU] - ), - groupBy(([product]) => product.productMasterSKU), - mergeMap(groups => - groups.pipe( - this.throttleOnBrowser(), - map( - ([product]) => - new LoadProductIfNotLoaded({ + loadMasterProductForProduct$ = createEffect(() => + this.actions$.pipe( + ofType(loadProductSuccess), + mapToPayloadProperty('product'), + filter(product => ProductHelper.isVariationProduct(product)), + withLatestFrom(this.store.pipe(select(getProductEntities))), + filter( + ([product, entities]: [VariationProduct, Dictionary]) => !entities[product.productMasterSKU] + ), + groupBy(([product]) => product.productMasterSKU), + mergeMap(groups => + groups.pipe( + this.throttleOnBrowser(), + map(([product]) => + loadProductIfNotLoaded({ sku: product.productMasterSKU, level: ProductCompletenessLevel.List, }) + ) ) ) ) @@ -194,21 +198,22 @@ export class ProductsEffects { * Trigger load product variations action on product success action for master products. * Ignores product variation entries for products that are already present. */ - @Effect() - loadProductVariationsForMasterProduct$ = this.actions$.pipe( - ofType(ProductsActionTypes.LoadProductSuccess), - mapToPayloadProperty('product'), - filter(product => ProductHelper.isMasterProduct(product)), - withLatestFrom(this.store.pipe(select(getProductEntities))), - filter( - ([product, entities]: [VariationProductMaster, Dictionary]) => - !entities[product.sku] || !entities[product.sku].variationSKUs - ), - groupBy(([product]) => product.sku), - mergeMap(groups => - groups.pipe( - this.throttleOnBrowser(), - map(([product]) => new LoadProductVariations({ sku: product.sku })) + loadProductVariationsForMasterProduct$ = createEffect(() => + this.actions$.pipe( + ofType(loadProductSuccess), + mapToPayloadProperty('product'), + filter(product => ProductHelper.isMasterProduct(product)), + withLatestFrom(this.store.pipe(select(getProductEntities))), + filter( + ([product, entities]: [VariationProductMaster, Dictionary]) => + !entities[product.sku] || !entities[product.sku].variationSKUs + ), + groupBy(([product]) => product.sku), + mergeMap(groups => + groups.pipe( + this.throttleOnBrowser(), + map(([product]) => loadProductVariations({ sku: product.sku })) + ) ) ) ); @@ -217,98 +222,109 @@ export class ProductsEffects { * reloads product when it is selected (usually product detail page) * change to {@link LoadProductIfNotLoaded} if no reload is needed */ - @Effect() - selectedProduct$ = this.store.pipe( - select(selectRouteParam('sku')), - whenTruthy(), - map(sku => new LoadProduct({ sku })) + selectedProduct$ = createEffect(() => + this.store.pipe( + select(selectRouteParam('sku')), + whenTruthy(), + map(sku => loadProduct({ sku })) + ) ); - @Effect() - loadDefaultCategoryContextForProduct$ = this.store.pipe( - ofProductUrl(), - select(getSelectedProduct), - withLatestFrom(this.store.pipe(select(selectRouteParam('categoryUniqueId')))), - map(([product, categoryUniqueId]) => !categoryUniqueId && product), - filter(p => !ProductHelper.isFailedLoading(p)), - mapToProperty('defaultCategoryId'), - whenTruthy(), - distinctUntilChanged(), - map(categoryId => new LoadCategory({ categoryId })) + loadDefaultCategoryContextForProduct$ = createEffect(() => + this.store.pipe( + ofProductUrl(), + select(getSelectedProduct), + withLatestFrom(this.store.pipe(select(selectRouteParam('categoryUniqueId')))), + map(([product, categoryUniqueId]) => !categoryUniqueId && product), + filter(p => !ProductHelper.isFailedLoading(p)), + mapToProperty('defaultCategoryId'), + whenTruthy(), + distinctUntilChanged(), + map(categoryId => loadCategory({ categoryId })) + ) ); - @Effect() - loadRetailSetProductDetail$ = this.actions$.pipe( - ofType(ProductsActionTypes.LoadProductSuccess), - mapToPayloadProperty('product'), - filter(ProductHelper.isRetailSet), - mapToProperty('sku'), - map( - sku => - new LoadProductIfNotLoaded({ + loadRetailSetProductDetail$ = createEffect(() => + this.actions$.pipe( + ofType(loadProductSuccess), + mapToPayloadProperty('product'), + filter(ProductHelper.isRetailSet), + mapToProperty('sku'), + map(sku => + loadProductIfNotLoaded({ sku, level: ProductCompletenessLevel.Detail, }) + ) ) ); - @Effect() - loadPartsOfRetailSet$ = this.actions$.pipe( - ofType(ProductsActionTypes.LoadProductSuccess), - mapToPayloadProperty('product'), - filter(ProductHelper.isRetailSet), - mapToProperty('sku'), - mergeMap(sku => - this.productsService - .getRetailSetParts(sku) - .pipe( - mergeMap(stubs => [ - ...stubs.map((product: Product) => new LoadProductSuccess({ product })), - new LoadRetailSetSuccess({ sku, parts: stubs.map(p => p.sku) }), - ]) - ) + loadPartsOfRetailSet$ = createEffect(() => + this.actions$.pipe( + ofType(loadProductSuccess), + mapToPayloadProperty('product'), + filter(ProductHelper.isRetailSet), + mapToProperty('sku'), + mergeMap(sku => + this.productsService + .getRetailSetParts(sku) + .pipe( + mergeMap(stubs => [ + ...stubs.map((product: Product) => loadProductSuccess({ product })), + loadRetailSetSuccess({ sku, parts: stubs.map(p => p.sku) }), + ]) + ) + ) ) ); - @Effect({ dispatch: false }) - redirectIfErrorInProducts$ = this.store.pipe( - ofProductUrl(), - select(getSelectedProduct), - whenTruthy(), - distinctUntilKeyChanged('sku'), - filter(ProductHelper.isFailedLoading), - tap(() => this.httpStatusCodeService.setStatusAndRedirect(404)) + redirectIfErrorInProducts$ = createEffect( + () => + this.store.pipe( + ofProductUrl(), + select(getSelectedProduct), + whenTruthy(), + distinctUntilKeyChanged('sku'), + filter(ProductHelper.isFailedLoading), + tap(() => this.httpStatusCodeService.setStatusAndRedirect(404)) + ), + { dispatch: false } ); - @Effect({ dispatch: false }) - redirectIfErrorInCategoryProducts$ = this.actions$.pipe( - ofType(ProductsActionTypes.LoadProductsForCategoryFail), - tap(() => this.httpStatusCodeService.setStatusAndRedirect(404)) + redirectIfErrorInCategoryProducts$ = createEffect( + () => + this.actions$.pipe( + ofType(loadProductsForCategoryFail), + tap(() => this.httpStatusCodeService.setStatusAndRedirect(404)) + ), + { dispatch: false } ); - @Effect() - loadProductLinks$ = this.actions$.pipe( - ofType(ProductsActionTypes.LoadProductLinks), - mapToPayloadProperty('sku'), - distinct(), - mergeMap(sku => - this.productsService.getProductLinks(sku).pipe( - map(links => new LoadProductLinksSuccess({ sku, links })), - mapErrorToAction(LoadProductLinksFail, { sku }) + loadProductLinks$ = createEffect(() => + this.actions$.pipe( + ofType(loadProductLinks), + mapToPayloadProperty('sku'), + distinct(), + mergeMap(sku => + this.productsService.getProductLinks(sku).pipe( + map(links => loadProductLinksSuccess({ sku, links })), + mapErrorToAction(loadProductLinksFail, { sku }) + ) ) ) ); - @Effect() - loadLinkedCategories$ = this.actions$.pipe( - ofType(ProductsActionTypes.LoadProductLinksSuccess), - mapToPayloadProperty('links'), - map(links => - Object.keys(links) - .reduce((acc, val) => [...acc, ...(links[val].categories || [])], []) - .filter((val, idx, arr) => arr.indexOf(val) === idx) - ), - mergeMap(ids => ids.map(categoryId => new LoadCategory({ categoryId }))) + loadLinkedCategories$ = createEffect(() => + this.actions$.pipe( + ofType(loadProductLinksSuccess), + mapToPayloadProperty('links'), + map(links => + Object.keys(links) + .reduce((acc, val) => [...acc, ...(links[val].categories || [])], []) + .filter((val, idx, arr) => arr.indexOf(val) === idx) + ), + mergeMap(ids => ids.map(categoryId => loadCategory({ categoryId }))) + ) ); private throttleOnBrowser = () => (isPlatformBrowser(this.platformId) ? throttleTime(3000) : map(identity)); diff --git a/src/app/core/store/shopping/products/products.reducer.spec.ts b/src/app/core/store/shopping/products/products.reducer.spec.ts index 343de2f0b7..dc315b2463 100644 --- a/src/app/core/store/shopping/products/products.reducer.spec.ts +++ b/src/app/core/store/shopping/products/products.reducer.spec.ts @@ -2,18 +2,42 @@ import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { Product } from 'ish-core/models/product/product.model'; import { - LoadProductFail, - LoadProductSuccess, - LoadProductVariationsFail, - LoadProductVariationsSuccess, - ProductsAction, + loadProduct, + loadProductBundlesSuccess, + loadProductFail, + loadProductIfNotLoaded, + loadProductLinks, + loadProductLinksFail, + loadProductLinksSuccess, + loadProductSuccess, + loadProductVariations, + loadProductVariationsFail, + loadProductVariationsSuccess, + loadProductsForCategory, + loadProductsForCategoryFail, + loadRetailSetSuccess, } from './products.actions'; import { ProductsState, initialState, productsReducer } from './products.reducer'; describe('Products Reducer', () => { describe('undefined action', () => { it('should return the default state when previous state is undefined', () => { - const action = {} as ProductsAction; + const action = {} as ReturnType< + | typeof loadProduct + | typeof loadProductBundlesSuccess + | typeof loadProductFail + | typeof loadProductIfNotLoaded + | typeof loadProductSuccess + | typeof loadProductsForCategory + | typeof loadProductsForCategoryFail + | typeof loadProductVariations + | typeof loadProductVariationsFail + | typeof loadProductVariationsSuccess + | typeof loadRetailSetSuccess + | typeof loadProductLinks + | typeof loadProductLinksFail + | typeof loadProductLinksSuccess + >; const state = productsReducer(undefined, action); expect(state).toBe(initialState); @@ -25,7 +49,7 @@ describe('Products Reducer', () => { let state: ProductsState; beforeEach(() => { - const action = new LoadProductFail({ error: {} as HttpError, sku: 'invalid' }); + const action = loadProductFail({ error: {} as HttpError, sku: 'invalid' }); state = productsReducer(initialState, action); }); @@ -37,7 +61,7 @@ describe('Products Reducer', () => { describe('followed by LoadProductSuccess', () => { beforeEach(() => { const product = { sku: 'invalid' } as Product; - const action = new LoadProductSuccess({ product }); + const action = loadProductSuccess({ product }); state = productsReducer(initialState, action); }); @@ -62,7 +86,7 @@ describe('Products Reducer', () => { }); it('should insert product if not exists', () => { - const action = new LoadProductSuccess({ product }); + const action = loadProductSuccess({ product }); const state = productsReducer(initialState, action); expect(state.ids).toHaveLength(1); @@ -70,7 +94,7 @@ describe('Products Reducer', () => { }); it('should merge product updates when new info is available', () => { - const action1 = new LoadProductSuccess({ product }); + const action1 = loadProductSuccess({ product }); const state1 = productsReducer(initialState, action1); const updatedProduct = { @@ -81,7 +105,7 @@ describe('Products Reducer', () => { inStock: false, } as Product; - const action2 = new LoadProductSuccess({ product: updatedProduct }); + const action2 = loadProductSuccess({ product: updatedProduct }); const state2 = productsReducer(state1, action2); expect(state2.ids).toHaveLength(1); @@ -103,11 +127,11 @@ describe('Products Reducer', () => { describe('LoadProductVariationsSuccess action', () => { it('should set product variation data when reducing', () => { const product = { sku: 'SKU' } as Product; - let state = productsReducer(initialState, new LoadProductSuccess({ product })); + let state = productsReducer(initialState, loadProductSuccess({ product })); state = productsReducer( state, - new LoadProductVariationsSuccess({ + loadProductVariationsSuccess({ sku: 'SKU', variations: ['VAR'], defaultVariation: 'VAR', @@ -121,7 +145,7 @@ describe('Products Reducer', () => { describe('LoadProductVariationsFail action', () => { it('should put sku on failed list', () => { const error = { message: 'invalid' } as HttpError; - const action = new LoadProductVariationsFail({ error, sku: 'SKU' }); + const action = loadProductVariationsFail({ error, sku: 'SKU' }); const state = productsReducer(initialState, action); expect(state.failed).toContain('SKU'); diff --git a/src/app/core/store/shopping/products/products.reducer.ts b/src/app/core/store/shopping/products/products.reducer.ts index 57b2d0149f..e9a608c9aa 100644 --- a/src/app/core/store/shopping/products/products.reducer.ts +++ b/src/app/core/store/shopping/products/products.reducer.ts @@ -1,8 +1,17 @@ import { EntityState, createEntityAdapter } from '@ngrx/entity'; +import { createReducer, on } from '@ngrx/store'; import { AllProductTypes } from 'ish-core/models/product/product.model'; -import { ProductsAction, ProductsActionTypes } from './products.actions'; +import { + loadProductBundlesSuccess, + loadProductFail, + loadProductLinksSuccess, + loadProductSuccess, + loadProductVariationsFail, + loadProductVariationsSuccess, + loadRetailSetSuccess, +} from './products.actions'; export const productAdapter = createEntityAdapter({ selectId: product => product.sku, @@ -24,60 +33,49 @@ function removeFailed(failed: string[], sku: string): string[] { return failed.filter(val => val !== sku); } -export function productsReducer(state = initialState, action: ProductsAction): ProductsState { - switch (action.type) { - case ProductsActionTypes.LoadProductFail: - case ProductsActionTypes.LoadProductVariationsFail: { - return { - ...state, - failed: addFailed(state.failed, action.payload.sku), - }; +export const productsReducer = createReducer( + initialState, + on(loadProductFail, loadProductVariationsFail, (state: ProductsState, action) => ({ + ...state, + failed: addFailed(state.failed, action.payload.sku), + })), + on(loadProductSuccess, (state: ProductsState, action) => { + const product = action.payload.product; + const oldProduct = state.entities[product.sku] || { completenessLevel: 0 }; + + const newProduct = { ...product }; + if (product.completenessLevel || (oldProduct && oldProduct.completenessLevel)) { + newProduct.completenessLevel = Math.max(product.completenessLevel, oldProduct.completenessLevel); } - case ProductsActionTypes.LoadProductSuccess: { - const product = action.payload.product; - const oldProduct = state.entities[product.sku] || { completenessLevel: 0 }; - - const newProduct = { ...product }; - if (product.completenessLevel || (oldProduct && oldProduct.completenessLevel)) { - newProduct.completenessLevel = Math.max(product.completenessLevel, oldProduct.completenessLevel); - } - - return productAdapter.upsertOne(newProduct, { - ...state, - loading: false, - failed: removeFailed(state.failed, product.sku), - }); - } - - case ProductsActionTypes.LoadProductVariationsSuccess: { - return productAdapter.updateOne( - { - id: action.payload.sku, - changes: { variationSKUs: action.payload.variations, defaultVariationSKU: action.payload.defaultVariation }, - }, - { ...state, loading: false } - ); - } - - case ProductsActionTypes.LoadProductBundlesSuccess: { - return productAdapter.updateOne( - { id: action.payload.sku, changes: { bundledProducts: action.payload.bundledProducts } }, - { ...state, loading: false } - ); - } - - case ProductsActionTypes.LoadRetailSetSuccess: { - return productAdapter.updateOne({ id: action.payload.sku, changes: { partSKUs: action.payload.parts } }, state); - } - - case ProductsActionTypes.LoadProductLinksSuccess: { - return productAdapter.updateOne( - { id: action.payload.sku, changes: { links: action.payload.links } }, - { ...state, loading: false } - ); - } - } - - return state; -} + return productAdapter.upsertOne(newProduct, { + ...state, + loading: false, + failed: removeFailed(state.failed, product.sku), + }); + }), + on(loadProductVariationsSuccess, (state: ProductsState, action) => + productAdapter.updateOne( + { + id: action.payload.sku, + changes: { variationSKUs: action.payload.variations, defaultVariationSKU: action.payload.defaultVariation }, + }, + { ...state, loading: false } + ) + ), + on(loadProductBundlesSuccess, (state: ProductsState, action) => + productAdapter.updateOne( + { id: action.payload.sku, changes: { bundledProducts: action.payload.bundledProducts } }, + { ...state, loading: false } + ) + ), + on(loadRetailSetSuccess, (state: ProductsState, action) => + productAdapter.updateOne({ id: action.payload.sku, changes: { partSKUs: action.payload.parts } }, state) + ), + on(loadProductLinksSuccess, (state: ProductsState, action) => + productAdapter.updateOne( + { id: action.payload.sku, changes: { links: action.payload.links } }, + { ...state, loading: false } + ) + ) +); diff --git a/src/app/core/store/shopping/products/products.selectors.spec.ts b/src/app/core/store/shopping/products/products.selectors.spec.ts index 82d0238541..ffb118af0d 100644 --- a/src/app/core/store/shopping/products/products.selectors.spec.ts +++ b/src/app/core/store/shopping/products/products.selectors.spec.ts @@ -10,15 +10,15 @@ import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.modu import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; import { - LoadProduct, - LoadProductBundlesSuccess, - LoadProductFail, - LoadProductLinksSuccess, - LoadProductSuccess, - LoadProductVariations, - LoadProductVariationsFail, - LoadProductVariationsSuccess, - LoadRetailSetSuccess, + loadProduct, + loadProductBundlesSuccess, + loadProductFail, + loadProductLinksSuccess, + loadProductSuccess, + loadProductVariations, + loadProductVariationsFail, + loadProductVariationsSuccess, + loadRetailSetSuccess, } from './products.actions'; import { getProduct, getProductEntities, getProductLinks, getProducts, getSelectedProduct } from './products.selectors'; @@ -60,12 +60,12 @@ describe('Products Selectors', () => { describe('loading a product', () => { beforeEach(() => { - store$.dispatch(new LoadProduct({ sku: '' })); + store$.dispatch(loadProduct({ sku: '' })); }); describe('and reporting success', () => { beforeEach(() => { - store$.dispatch(new LoadProductSuccess({ product: prod })); + store$.dispatch(loadProductSuccess({ product: prod })); }); it('should add product to state', () => { @@ -75,7 +75,7 @@ describe('Products Selectors', () => { describe('and reporting failure', () => { beforeEach(() => { - store$.dispatch(new LoadProductFail({ error: { message: 'error' } as HttpError, sku: 'invalid' })); + store$.dispatch(loadProductFail({ error: { message: 'error' } as HttpError, sku: 'invalid' })); }); it('should not have loaded product on error', () => { @@ -90,7 +90,7 @@ describe('Products Selectors', () => { describe('state with a product', () => { beforeEach(() => { - store$.dispatch(new LoadProductSuccess({ product: prod })); + store$.dispatch(loadProductSuccess({ product: prod })); }); describe('but no current router state', () => { @@ -121,9 +121,9 @@ describe('Products Selectors', () => { describe('when loading bundles', () => { it('should contain the product bundle information on the product', () => { - store$.dispatch(new LoadProductSuccess({ product: { sku: 'ABC' } as Product })); + store$.dispatch(loadProductSuccess({ product: { sku: 'ABC' } as Product })); store$.dispatch( - new LoadProductBundlesSuccess({ + loadProductBundlesSuccess({ sku: 'ABC', bundledProducts: [ { sku: 'A', quantity: 1 }, @@ -152,9 +152,9 @@ describe('Products Selectors', () => { describe('when loading retail sets', () => { it('should contain the product retail set information on the product', () => { - store$.dispatch(new LoadProductSuccess({ product: { sku: 'ABC' } as Product })); + store$.dispatch(loadProductSuccess({ product: { sku: 'ABC' } as Product })); store$.dispatch( - new LoadRetailSetSuccess({ + loadRetailSetSuccess({ sku: 'ABC', parts: ['A', 'B'], }) @@ -174,13 +174,13 @@ describe('Products Selectors', () => { describe('loading product variations', () => { beforeEach(() => { - store$.dispatch(new LoadProductSuccess({ product: { sku: 'SKU', type: 'VariationProductMaster' } as Product })); - store$.dispatch(new LoadProductVariations({ sku: 'SKU' })); + store$.dispatch(loadProductSuccess({ product: { sku: 'SKU', type: 'VariationProductMaster' } as Product })); + store$.dispatch(loadProductVariations({ sku: 'SKU' })); }); describe('and reporting success', () => { beforeEach(() => { - store$.dispatch(new LoadProductVariationsSuccess({ sku: 'SKU', variations: ['VAR'], defaultVariation: 'VAR' })); + store$.dispatch(loadProductVariationsSuccess({ sku: 'SKU', variations: ['VAR'], defaultVariation: 'VAR' })); }); it('should add variations to state', () => { @@ -199,7 +199,7 @@ describe('Products Selectors', () => { describe('and reporting failure', () => { beforeEach(() => { - store$.dispatch(new LoadProductVariationsFail({ error: { message: 'error' } as HttpError, sku: 'SKU' })); + store$.dispatch(loadProductVariationsFail({ error: { message: 'error' } as HttpError, sku: 'SKU' })); }); it('should not have loaded product variations on error', () => { @@ -210,9 +210,9 @@ describe('Products Selectors', () => { describe('state with multiple products', () => { beforeEach(() => { - store$.dispatch(new LoadProductSuccess({ product: { sku: 'SKU1', name: 'sku1' } as Product })); - store$.dispatch(new LoadProductSuccess({ product: { sku: 'SKU2', name: 'sku2' } as Product })); - store$.dispatch(new LoadProductSuccess({ product: { sku: 'SKU3', name: 'sku3' } as Product })); + store$.dispatch(loadProductSuccess({ product: { sku: 'SKU1', name: 'sku1' } as Product })); + store$.dispatch(loadProductSuccess({ product: { sku: 'SKU2', name: 'sku2' } as Product })); + store$.dispatch(loadProductSuccess({ product: { sku: 'SKU3', name: 'sku3' } as Product })); }); it('should select various products on entites selector', () => { @@ -236,9 +236,9 @@ describe('Products Selectors', () => { describe('when loading product links', () => { it('should contain the product link information on the product', () => { - store$.dispatch(new LoadProductSuccess({ product: { sku: 'ABC' } as Product })); + store$.dispatch(loadProductSuccess({ product: { sku: 'ABC' } as Product })); store$.dispatch( - new LoadProductLinksSuccess({ + loadProductLinksSuccess({ sku: 'ABC', links: { linkType: { products: ['prod'], categories: ['cat'] } }, }) diff --git a/src/app/core/store/shopping/promotions/promotions.actions.ts b/src/app/core/store/shopping/promotions/promotions.actions.ts index ec99ee9d4c..4342d4a1b7 100644 --- a/src/app/core/store/shopping/promotions/promotions.actions.ts +++ b/src/app/core/store/shopping/promotions/promotions.actions.ts @@ -1,27 +1,13 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; -import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { Promotion } from 'ish-core/models/promotion/promotion.model'; +import { httpError, payload } from 'ish-core/utils/ngrx-creators'; -export enum PromotionsActionTypes { - LoadPromotion = '[Promotions Internal] Load Promotion', - LoadPromotionFail = '[Promotions API] Load Promotion Fail', - LoadPromotionSuccess = '[Promotions API] Load Promotion Success', -} +export const loadPromotion = createAction('[Promotions Internal] Load Promotion', payload<{ promoId: string }>()); -export class LoadPromotion implements Action { - readonly type = PromotionsActionTypes.LoadPromotion; - constructor(public payload: { promoId: string }) {} -} +export const loadPromotionFail = createAction('[Promotions API] Load Promotion Fail', httpError<{ promoId: string }>()); -export class LoadPromotionFail implements Action { - readonly type = PromotionsActionTypes.LoadPromotionFail; - constructor(public payload: { error: HttpError; promoId: string }) {} -} - -export class LoadPromotionSuccess implements Action { - readonly type = PromotionsActionTypes.LoadPromotionSuccess; - constructor(public payload: { promotion: Promotion }) {} -} - -export type PromotionsAction = LoadPromotion | LoadPromotionFail | LoadPromotionSuccess; +export const loadPromotionSuccess = createAction( + '[Promotions API] Load Promotion Success', + payload<{ promotion: Promotion }>() +); diff --git a/src/app/core/store/shopping/promotions/promotions.effects.spec.ts b/src/app/core/store/shopping/promotions/promotions.effects.spec.ts index 3e7bbfb710..4dc1c76221 100644 --- a/src/app/core/store/shopping/promotions/promotions.effects.spec.ts +++ b/src/app/core/store/shopping/promotions/promotions.effects.spec.ts @@ -12,7 +12,7 @@ import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { Promotion } from 'ish-core/models/promotion/promotion.model'; import { PromotionsService } from 'ish-core/services/promotions/promotions.service'; -import { LoadPromotion, LoadPromotionFail, LoadPromotionSuccess } from './promotions.actions'; +import { loadPromotion, loadPromotionFail, loadPromotionSuccess } from './promotions.actions'; import { PromotionsEffects } from './promotions.effects'; describe('Promotions Effects', () => { @@ -50,7 +50,7 @@ describe('Promotions Effects', () => { describe('loadPromotion$', () => { it('should call the promotionsService for LoadPromotion action', done => { const promoId = 'P123'; - const action = new LoadPromotion({ promoId }); + const action = loadPromotion({ promoId }); actions$ = of(action); effects.loadPromotion$.subscribe(() => { @@ -62,8 +62,8 @@ describe('Promotions Effects', () => { it('should map to action of type LoadPromotionSuccess only once', () => { const id = 'P123'; const promoId = 'P123'; - const action = new LoadPromotion({ promoId }); - const completion = new LoadPromotionSuccess({ promotion: { id } as Promotion }); + const action = loadPromotion({ promoId }); + const completion = loadPromotionSuccess({ promotion: { id } as Promotion }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c----', { c: completion }); @@ -72,8 +72,8 @@ describe('Promotions Effects', () => { it('should map invalid request to action of type LoadPromotionFail only once', () => { const promoId = 'invalid'; - const action = new LoadPromotion({ promoId }); - const completion = new LoadPromotionFail({ error: { message: 'invalid' } as HttpError, promoId }); + const action = loadPromotion({ promoId }); + const completion = loadPromotionFail({ error: { message: 'invalid' } as HttpError, promoId }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c----', { c: completion }); diff --git a/src/app/core/store/shopping/promotions/promotions.effects.ts b/src/app/core/store/shopping/promotions/promotions.effects.ts index ce2fd501f5..2e351b26d1 100644 --- a/src/app/core/store/shopping/promotions/promotions.effects.ts +++ b/src/app/core/store/shopping/promotions/promotions.effects.ts @@ -1,29 +1,30 @@ import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { distinct, map, mergeMap } from 'rxjs/operators'; import { PromotionsService } from 'ish-core/services/promotions/promotions.service'; import { mapErrorToAction, mapToPayloadProperty } from 'ish-core/utils/operators'; -import { LoadPromotion, LoadPromotionFail, LoadPromotionSuccess, PromotionsActionTypes } from './promotions.actions'; +import { loadPromotion, loadPromotionFail, loadPromotionSuccess } from './promotions.actions'; @Injectable() export class PromotionsEffects { constructor(private actions$: Actions, private promotionsService: PromotionsService) {} - @Effect() - loadPromotion$ = this.actions$.pipe( - ofType(PromotionsActionTypes.LoadPromotion), - mapToPayloadProperty('promoId'), - // trigger the promotion REST call only once for each distinct promotion id (per application session) - distinct(), - mergeMap( - promoId => - this.promotionsService.getPromotion(promoId).pipe( - map(promotion => new LoadPromotionSuccess({ promotion })), - mapErrorToAction(LoadPromotionFail, { promoId }) - ), - 5 + loadPromotion$ = createEffect(() => + this.actions$.pipe( + ofType(loadPromotion), + mapToPayloadProperty('promoId'), + // trigger the promotion REST call only once for each distinct promotion id (per application session) + distinct(), + mergeMap( + promoId => + this.promotionsService.getPromotion(promoId).pipe( + map(promotion => loadPromotionSuccess({ promotion })), + mapErrorToAction(loadPromotionFail, { promoId }) + ), + 5 + ) ) ); } diff --git a/src/app/core/store/shopping/promotions/promotions.reducer.spec.ts b/src/app/core/store/shopping/promotions/promotions.reducer.spec.ts index 7c1e2d9adc..a014a9eec5 100644 --- a/src/app/core/store/shopping/promotions/promotions.reducer.spec.ts +++ b/src/app/core/store/shopping/promotions/promotions.reducer.spec.ts @@ -1,13 +1,13 @@ import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { Promotion } from 'ish-core/models/promotion/promotion.model'; -import { LoadPromotionFail, LoadPromotionSuccess, PromotionsAction } from './promotions.actions'; +import { loadPromotion, loadPromotionFail, loadPromotionSuccess } from './promotions.actions'; import { PromotionsState, initialState, promotionsReducer } from './promotions.reducer'; describe('Promotions Reducer', () => { describe('undefined action', () => { it('should return the default state when previous state is undefined', () => { - const action = {} as PromotionsAction; + const action = {} as ReturnType; const state = promotionsReducer(undefined, action); expect(state).toBe(initialState); @@ -19,7 +19,7 @@ describe('Promotions Reducer', () => { let state: PromotionsState; beforeEach(() => { - const action = new LoadPromotionFail({ error: {} as HttpError, promoId: 'erroneous_promo' }); + const action = loadPromotionFail({ error: {} as HttpError, promoId: 'erroneous_promo' }); state = promotionsReducer(initialState, action); }); @@ -30,7 +30,7 @@ describe('Promotions Reducer', () => { describe('followed by LoadPromotionSuccess', () => { beforeEach(() => { const promotion = { id: 'successfull_promo' } as Promotion; - const action = new LoadPromotionSuccess({ promotion }); + const action = loadPromotionSuccess({ promotion }); state = promotionsReducer(initialState, action); }); @@ -52,7 +52,7 @@ describe('Promotions Reducer', () => { }); it('should insert promotion if not exists', () => { - const action = new LoadPromotionSuccess({ promotion }); + const action = loadPromotionSuccess({ promotion }); const state = promotionsReducer(initialState, action); expect(state.ids).toHaveLength(1); @@ -60,14 +60,14 @@ describe('Promotions Reducer', () => { }); it('should update promotion if already exists', () => { - const action1 = new LoadPromotionSuccess({ promotion }); + const action1 = loadPromotionSuccess({ promotion }); const state1 = promotionsReducer(initialState, action1); const updatedPromotion = { id: '111' } as Promotion; updatedPromotion.name = 'Updated promotion name'; updatedPromotion.title = 'Updated promotion title'; - const action2 = new LoadPromotionSuccess({ promotion: updatedPromotion }); + const action2 = loadPromotionSuccess({ promotion: updatedPromotion }); const state2 = promotionsReducer(state1, action2); expect(state2.ids).toHaveLength(1); diff --git a/src/app/core/store/shopping/promotions/promotions.reducer.ts b/src/app/core/store/shopping/promotions/promotions.reducer.ts index 3ffe39ef7a..77ffe592dc 100644 --- a/src/app/core/store/shopping/promotions/promotions.reducer.ts +++ b/src/app/core/store/shopping/promotions/promotions.reducer.ts @@ -1,8 +1,9 @@ import { EntityState, createEntityAdapter } from '@ngrx/entity'; +import { createReducer, on } from '@ngrx/store'; import { Promotion } from 'ish-core/models/promotion/promotion.model'; -import { PromotionsAction, PromotionsActionTypes } from './promotions.actions'; +import { loadPromotionSuccess } from './promotions.actions'; export const promotionAdapter = createEntityAdapter({ selectId: promotion => promotion.id, @@ -12,13 +13,10 @@ export interface PromotionsState extends EntityState {} export const initialState: PromotionsState = promotionAdapter.getInitialState({}); -export function promotionsReducer(state = initialState, action: PromotionsAction): PromotionsState { - switch (action.type) { - case PromotionsActionTypes.LoadPromotionSuccess: { - const promotion = action.payload.promotion; - return promotionAdapter.upsertOne(promotion, state); - } - } - - return state; -} +export const promotionsReducer = createReducer( + initialState, + on(loadPromotionSuccess, (state: PromotionsState, action) => { + const promotion = action.payload.promotion; + return promotionAdapter.upsertOne(promotion, state); + }) +); diff --git a/src/app/core/store/shopping/promotions/promotions.selectors.spec.ts b/src/app/core/store/shopping/promotions/promotions.selectors.spec.ts index 9ef5ac3b08..3e52ee8264 100644 --- a/src/app/core/store/shopping/promotions/promotions.selectors.spec.ts +++ b/src/app/core/store/shopping/promotions/promotions.selectors.spec.ts @@ -6,7 +6,7 @@ import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; -import { LoadPromotionFail, LoadPromotionSuccess } from './promotions.actions'; +import { loadPromotionFail, loadPromotionSuccess } from './promotions.actions'; import { getPromotion, getPromotions } from './promotions.selectors'; describe('Promotions Selectors', () => { @@ -36,7 +36,7 @@ describe('Promotions Selectors', () => { describe('loading a promotion', () => { describe('and reporting success', () => { beforeEach(() => { - store$.dispatch(new LoadPromotionSuccess({ promotion: promo })); + store$.dispatch(loadPromotionSuccess({ promotion: promo })); }); it('should put the promotion to the state', () => { @@ -46,9 +46,7 @@ describe('Promotions Selectors', () => { describe('and reporting failure', () => { beforeEach(() => { - store$.dispatch( - new LoadPromotionFail({ error: { message: 'error' } as HttpError, promoId: 'erroneous_promo' }) - ); + store$.dispatch(loadPromotionFail({ error: { message: 'error' } as HttpError, promoId: 'erroneous_promo' })); }); it('should not have loaded promotion on error', () => { @@ -59,8 +57,8 @@ describe('Promotions Selectors', () => { describe('state with a promotion', () => { beforeEach(() => { - store$.dispatch(new LoadPromotionSuccess({ promotion: promo })); - store$.dispatch(new LoadPromotionSuccess({ promotion: promo1 })); + store$.dispatch(loadPromotionSuccess({ promotion: promo })); + store$.dispatch(loadPromotionSuccess({ promotion: promo1 })); }); describe('but no current router state', () => { diff --git a/src/app/core/store/shopping/recently/recently.actions.ts b/src/app/core/store/shopping/recently/recently.actions.ts index 62a4310c3b..27c8f531b9 100644 --- a/src/app/core/store/shopping/recently/recently.actions.ts +++ b/src/app/core/store/shopping/recently/recently.actions.ts @@ -1,17 +1,10 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; -export enum RecentlyActionTypes { - AddToRecently = '[Recently Viewed] Add Product to Recently', - ClearRecently = '[Recently Viewed] Clear Recently', -} +import { payload } from 'ish-core/utils/ngrx-creators'; -export class AddToRecently implements Action { - readonly type = RecentlyActionTypes.AddToRecently; - constructor(public payload: { sku: string; group?: string }) {} -} +export const addToRecently = createAction( + '[Recently Viewed] Add Product to Recently', + payload<{ sku: string; group?: string }>() +); -export class ClearRecently implements Action { - readonly type = RecentlyActionTypes.ClearRecently; -} - -export type RecentlyAction = AddToRecently | ClearRecently; +export const clearRecently = createAction('[Recently Viewed] Clear Recently'); diff --git a/src/app/core/store/shopping/recently/recently.effects.spec.ts b/src/app/core/store/shopping/recently/recently.effects.spec.ts index 8fcfe4316e..1ee60059ec 100644 --- a/src/app/core/store/shopping/recently/recently.effects.spec.ts +++ b/src/app/core/store/shopping/recently/recently.effects.spec.ts @@ -6,7 +6,7 @@ import { FeatureToggleModule } from 'ish-core/feature-toggle.module'; import { ProductView } from 'ish-core/models/product-view/product-view.model'; import { getSelectedProduct } from 'ish-core/store/shopping/products'; -import { AddToRecently } from './recently.actions'; +import { addToRecently } from './recently.actions'; import { RecentlyEffects } from './recently.effects'; describe('Recently Effects', () => { @@ -27,7 +27,7 @@ describe('Recently Effects', () => { it('should fire when product is in store and selected', () => { store$.overrideSelector(getSelectedProduct, { sku: 'A' } as ProductView); - expect(effects.viewedProduct$).toBeObservable(cold('a', { a: new AddToRecently({ sku: 'A' }) })); + expect(effects.viewedProduct$).toBeObservable(cold('a', { a: addToRecently({ sku: 'A' }) })); }); it('should not fire when product failed loading', () => { diff --git a/src/app/core/store/shopping/recently/recently.effects.ts b/src/app/core/store/shopping/recently/recently.effects.ts index 9107738568..664496fa6e 100644 --- a/src/app/core/store/shopping/recently/recently.effects.ts +++ b/src/app/core/store/shopping/recently/recently.effects.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { Effect } from '@ngrx/effects'; +import { createEffect } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { distinctUntilKeyChanged, filter, map } from 'rxjs/operators'; @@ -8,26 +8,27 @@ import { getSelectedProduct } from 'ish-core/store/shopping/products/products.se import { FeatureToggleService } from 'ish-core/utils/feature-toggle/feature-toggle.service'; import { whenTruthy } from 'ish-core/utils/operators'; -import { AddToRecently } from './recently.actions'; +import { addToRecently } from './recently.actions'; @Injectable() export class RecentlyEffects { constructor(private store: Store, private featureToggleService: FeatureToggleService) {} - @Effect() - viewedProduct$ = this.store.pipe( - select(getSelectedProduct), - whenTruthy(), - filter(p => !ProductHelper.isFailedLoading(p)), - distinctUntilKeyChanged('sku'), - filter( - product => - this.featureToggleService.enabled('advancedVariationHandling') || !ProductHelper.isMasterProduct(product) - ), - map(product => ({ - sku: product.sku, - group: (ProductHelper.isVariationProduct(product) && product.productMasterSKU) || undefined, - })), - map(({ sku, group }) => new AddToRecently({ sku, group })) + viewedProduct$ = createEffect(() => + this.store.pipe( + select(getSelectedProduct), + whenTruthy(), + filter(p => !ProductHelper.isFailedLoading(p)), + distinctUntilKeyChanged('sku'), + filter( + product => + this.featureToggleService.enabled('advancedVariationHandling') || !ProductHelper.isMasterProduct(product) + ), + map(product => ({ + sku: product.sku, + group: (ProductHelper.isVariationProduct(product) && product.productMasterSKU) || undefined, + })), + map(({ sku, group }) => addToRecently({ sku, group })) + ) ); } diff --git a/src/app/core/store/shopping/recently/recently.reducer.ts b/src/app/core/store/shopping/recently/recently.reducer.ts index b2d5958983..88521d9fe1 100644 --- a/src/app/core/store/shopping/recently/recently.reducer.ts +++ b/src/app/core/store/shopping/recently/recently.reducer.ts @@ -1,4 +1,6 @@ -import { RecentlyAction, RecentlyActionTypes } from './recently.actions'; +import { createReducer, on } from '@ngrx/store'; + +import { addToRecently, clearRecently } from './recently.actions'; export interface RecentlyState { products: { sku: string; group?: string }[]; @@ -8,20 +10,16 @@ const initialState: RecentlyState = { products: [], }; -export function recentlyReducer(state = initialState, action: RecentlyAction): RecentlyState { - switch (action.type) { - case RecentlyActionTypes.AddToRecently: { - const products = [action.payload, ...state.products]; - - return { ...state, products }; - } +export const recentlyReducer = createReducer( + initialState, + on(addToRecently, (state: RecentlyState, action) => { + const products = [action.payload, ...state.products]; - case RecentlyActionTypes.ClearRecently: { - const products = []; + return { ...state, products }; + }), + on(clearRecently, (state: RecentlyState) => { + const products = []; - return { ...state, products }; - } - } - - return state; -} + return { ...state, products }; + }) +); diff --git a/src/app/core/store/shopping/recently/recently.selectors.spec.ts b/src/app/core/store/shopping/recently/recently.selectors.spec.ts index 0b604a7c7f..346e0267b2 100644 --- a/src/app/core/store/shopping/recently/recently.selectors.spec.ts +++ b/src/app/core/store/shopping/recently/recently.selectors.spec.ts @@ -6,11 +6,11 @@ import { RouterTestingModule } from '@angular/router/testing'; import { VariationProduct } from 'ish-core/models/product/product-variation.model'; import { Product } from 'ish-core/models/product/product.model'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; -import { LoadProductSuccess } from 'ish-core/store/shopping/products'; +import { loadProductSuccess } from 'ish-core/store/shopping/products'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; -import { ClearRecently } from './recently.actions'; +import { clearRecently } from './recently.actions'; import { RecentlyEffects } from './recently.effects'; import { getMostRecentlyViewedProducts, getRecentlyViewedProducts } from './recently.selectors'; @@ -44,7 +44,7 @@ describe('Recently Selectors', () => { describe('after short shopping spree', () => { beforeEach(fakeAsync(() => { ['A', 'B', 'C', 'D', 'E', 'F', 'G'].forEach(sku => - store$.dispatch(new LoadProductSuccess({ product: { sku } as Product })) + store$.dispatch(loadProductSuccess({ product: { sku } as Product })) ); ['A', 'B', 'F', 'C', 'A', 'D', 'E', 'D', 'A', 'B', 'A'].forEach(sku => { router.navigateByUrl('/product/' + sku); @@ -61,7 +61,7 @@ describe('Recently Selectors', () => { describe('when clearing the state', () => { beforeEach(() => { - store$.dispatch(new ClearRecently()); + store$.dispatch(clearRecently()); }); it('should select nothing for an empty state', () => { @@ -73,10 +73,10 @@ describe('Recently Selectors', () => { describe('after viewing various variation', () => { beforeEach(fakeAsync(() => { - store$.dispatch(new LoadProductSuccess({ product: { sku: 'B' } as Product })); + store$.dispatch(loadProductSuccess({ product: { sku: 'B' } as Product })); ['A1', 'A2', 'A3'].forEach(sku => store$.dispatch( - new LoadProductSuccess({ + loadProductSuccess({ product: { sku, type: 'VariationProduct', productMasterSKU: 'A' } as VariationProduct, }) ) @@ -96,7 +96,7 @@ describe('Recently Selectors', () => { describe('when clearing the state', () => { beforeEach(() => { - store$.dispatch(new ClearRecently()); + store$.dispatch(clearRecently()); }); it('should select nothing for an empty state', () => { diff --git a/src/app/core/store/shopping/search/search.actions.ts b/src/app/core/store/shopping/search/search.actions.ts index 586bdcf6c3..b1fad73ea5 100644 --- a/src/app/core/store/shopping/search/search.actions.ts +++ b/src/app/core/store/shopping/search/search.actions.ts @@ -1,33 +1,21 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; -import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { SuggestTerm } from 'ish-core/models/suggest-term/suggest-term.model'; +import { httpError, payload } from 'ish-core/utils/ngrx-creators'; -export enum SearchActionTypes { - SearchProducts = '[Shopping] Search Products', - SearchProductsFail = '[Shopping] Search Products Fail', - SuggestSearch = '[Suggest Search] Load Search Suggestions', - SuggestSearchSuccess = '[Suggest Search Internal] Return Search Suggestions', -} +export const searchProducts = createAction( + '[Shopping] Search Products', + payload<{ searchTerm: string; page?: number; sorting?: string }>() +); -export class SearchProducts implements Action { - readonly type = SearchActionTypes.SearchProducts; - constructor(public payload: { searchTerm: string; page?: number; sorting?: string }) {} -} +export const searchProductsFail = createAction('[Shopping] Search Products Fail', httpError()); -export class SearchProductsFail implements Action { - readonly type = SearchActionTypes.SearchProductsFail; - constructor(public payload: { error: HttpError }) {} -} +export const suggestSearch = createAction( + '[Suggest Search] Load Search Suggestions', + payload<{ searchTerm: string }>() +); -export class SuggestSearch implements Action { - readonly type = SearchActionTypes.SuggestSearch; - constructor(public payload: { searchTerm: string }) {} -} - -export class SuggestSearchSuccess implements Action { - readonly type = SearchActionTypes.SuggestSearchSuccess; - constructor(public payload: { searchTerm: string; suggests: SuggestTerm[] }) {} -} - -export type SearchAction = SearchProducts | SearchProductsFail | SuggestSearch | SuggestSearchSuccess; +export const suggestSearchSuccess = createAction( + '[Suggest Search Internal] Return Search Suggestions', + payload<{ searchTerm: string; suggests: SuggestTerm[] }>() +); diff --git a/src/app/core/store/shopping/search/search.effects.spec.ts b/src/app/core/store/shopping/search/search.effects.spec.ts index 9400591936..c1d2680f07 100644 --- a/src/app/core/store/shopping/search/search.effects.spec.ts +++ b/src/app/core/store/shopping/search/search.effects.spec.ts @@ -16,12 +16,12 @@ import { ApiService } from 'ish-core/services/api/api.service'; import { ProductsService } from 'ish-core/services/products/products.service'; import { SuggestService } from 'ish-core/services/suggest/suggest.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; -import { LoadMoreProducts, SetProductListingPageSize } from 'ish-core/store/shopping/product-listing'; +import { loadMoreProducts, setProductListingPageSize } from 'ish-core/store/shopping/product-listing'; import { ProductListingEffects } from 'ish-core/store/shopping/product-listing/product-listing.effects'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; -import { SearchProductsFail, SuggestSearch } from './search.actions'; +import { searchProductsFail, suggestSearch } from './search.actions'; import { SearchEffects } from './search.effects'; describe('Search Effects', () => { @@ -81,7 +81,7 @@ describe('Search Effects', () => { location = TestBed.inject(Location); router = TestBed.inject(Router); - store$.dispatch(new SetProductListingPageSize({ itemsPerPage: TestBed.inject(PRODUCT_LISTING_ITEMS_PER_PAGE) })); + store$.dispatch(setProductListingPageSize({ itemsPerPage: TestBed.inject(PRODUCT_LISTING_ITEMS_PER_PAGE) })); }); describe('triggerSearch$', () => { @@ -100,7 +100,7 @@ describe('Search Effects', () => { describe('suggestSearch$', () => { it('should not fire when search term is falsy', fakeAsync(() => { - const action = new SuggestSearch({ searchTerm: undefined }); + const action = suggestSearch({ searchTerm: undefined }); store$.dispatch(action); tick(5000); @@ -109,7 +109,7 @@ describe('Search Effects', () => { })); it('should not fire when search term is empty', fakeAsync(() => { - const action = new SuggestSearch({ searchTerm: '' }); + const action = suggestSearch({ searchTerm: '' }); store$.dispatch(action); tick(5000); @@ -118,7 +118,7 @@ describe('Search Effects', () => { })); it('should return search terms when available', fakeAsync(() => { - const action = new SuggestSearch({ searchTerm: 'g' }); + const action = suggestSearch({ searchTerm: 'g' }); store$.dispatch(action); tick(5000); @@ -127,11 +127,11 @@ describe('Search Effects', () => { })); it('should debounce correctly when search term is entered stepwise', fakeAsync(() => { - store$.dispatch(new SuggestSearch({ searchTerm: 'g' })); + store$.dispatch(suggestSearch({ searchTerm: 'g' })); tick(50); - store$.dispatch(new SuggestSearch({ searchTerm: 'goo' })); + store$.dispatch(suggestSearch({ searchTerm: 'goo' })); tick(100); - store$.dispatch(new SuggestSearch({ searchTerm: 'good' })); + store$.dispatch(suggestSearch({ searchTerm: 'good' })); tick(200); verify(suggestServiceMock.search(anyString())).never(); @@ -141,10 +141,10 @@ describe('Search Effects', () => { })); it('should send only once if search term is entered multiple times', fakeAsync(() => { - store$.dispatch(new SuggestSearch({ searchTerm: 'good' })); + store$.dispatch(suggestSearch({ searchTerm: 'good' })); tick(2000); verify(suggestServiceMock.search('good')).once(); - store$.dispatch(new SuggestSearch({ searchTerm: 'good' })); + store$.dispatch(suggestSearch({ searchTerm: 'good' })); tick(2000); verify(suggestServiceMock.search('good')).once(); @@ -153,7 +153,7 @@ describe('Search Effects', () => { it('should not fire action when error is encountered at service level', fakeAsync(() => { when(suggestServiceMock.search(anyString())).thenReturn(throwError({ message: 'ERROR' })); - store$.dispatch(new SuggestSearch({ searchTerm: 'good' })); + store$.dispatch(suggestSearch({ searchTerm: 'good' })); tick(4000); effects.suggestSearch$.subscribe(fail, fail, fail); @@ -162,7 +162,7 @@ describe('Search Effects', () => { })); it('should fire all necessary actions for suggest-search', fakeAsync(() => { - store$.dispatch(new SuggestSearch({ searchTerm: 'good' })); + store$.dispatch(suggestSearch({ searchTerm: 'good' })); tick(500); // debounceTime expect(store$.actionsArray(/\[Suggest Search/)).toMatchInlineSnapshot(` [Suggest Search] Load Search Suggestions: @@ -173,7 +173,7 @@ describe('Search Effects', () => { `); // 2nd term to because distinctUntilChanged - store$.dispatch(new SuggestSearch({ searchTerm: 'goo' })); + store$.dispatch(suggestSearch({ searchTerm: 'goo' })); tick(500); expect(store$.actionsArray(/\[Suggest Search/)).toMatchInlineSnapshot(` [Suggest Search] Load Search Suggestions: @@ -189,7 +189,7 @@ describe('Search Effects', () => { `); // test cache: search->api->success & search->success->api->success - store$.dispatch(new SuggestSearch({ searchTerm: 'good' })); + store$.dispatch(suggestSearch({ searchTerm: 'good' })); tick(500); expect(store$.actionsArray(/\[Suggest Search/)).toMatchInlineSnapshot(` [Suggest Search] Load Search Suggestions: @@ -213,7 +213,7 @@ describe('Search Effects', () => { describe('redirectIfSearchProductFail$', () => { it('should redirect if triggered', fakeAsync(() => { - const action = new SearchProductsFail({ error: { status: 404 } as HttpError }); + const action = searchProductsFail({ error: { status: 404 } as HttpError }); store$.dispatch(action); @@ -231,10 +231,10 @@ describe('Search Effects', () => { verify(productsServiceMock.searchProducts(searchTerm, 1, anything())).once(); - store$.dispatch(new LoadMoreProducts({ id: { type: 'search', value: searchTerm }, page: 2 })); + store$.dispatch(loadMoreProducts({ id: { type: 'search', value: searchTerm }, page: 2 })); verify(productsServiceMock.searchProducts(searchTerm, 2, anything())).once(); - store$.dispatch(new LoadMoreProducts({ id: { type: 'search', value: searchTerm }, page: 3 })); + store$.dispatch(loadMoreProducts({ id: { type: 'search', value: searchTerm }, page: 3 })); verify(productsServiceMock.searchProducts(searchTerm, 3, anything())).once(); })); }); diff --git a/src/app/core/store/shopping/search/search.effects.ts b/src/app/core/store/shopping/search/search.effects.ts index b3f85c7875..f90dca148d 100644 --- a/src/app/core/store/shopping/search/search.effects.ts +++ b/src/app/core/store/shopping/search/search.effects.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { routerNavigatedAction } from '@ngrx/router-store'; import { Store, select } from '@ngrx/store'; import { isEqual } from 'lodash-es'; @@ -10,18 +10,12 @@ import { ProductListingMapper } from 'ish-core/models/product-listing/product-li import { ProductsService } from 'ish-core/services/products/products.service'; import { SuggestService } from 'ish-core/services/suggest/suggest.service'; import { ofUrl, selectRouteParam } from 'ish-core/store/core/router'; -import { LoadMoreProducts, SetProductListingPages } from 'ish-core/store/shopping/product-listing'; -import { LoadProductSuccess } from 'ish-core/store/shopping/products'; +import { loadMoreProducts, setProductListingPages } from 'ish-core/store/shopping/product-listing'; +import { loadProductSuccess } from 'ish-core/store/shopping/products'; import { HttpStatusCodeService } from 'ish-core/utils/http-status-code/http-status-code.service'; import { mapErrorToAction, mapToPayload, mapToPayloadProperty, whenTruthy } from 'ish-core/utils/operators'; -import { - SearchActionTypes, - SearchProducts, - SearchProductsFail, - SuggestSearch, - SuggestSearchSuccess, -} from './search.actions'; +import { searchProducts, searchProductsFail, suggestSearch, suggestSearchSuccess } from './search.actions'; @Injectable() export class SearchEffects { @@ -37,63 +31,69 @@ export class SearchEffects { /** * Effect that listens for search route changes and triggers a search action. */ - @Effect() - triggerSearch$ = this.store.pipe( - ofUrl(/^\/search.*/), - select(selectRouteParam('searchTerm')), - sample(this.actions$.pipe(ofType(routerNavigatedAction))), - whenTruthy(), - map(searchTerm => new LoadMoreProducts({ id: { type: 'search', value: searchTerm } })), - distinctUntilChanged(isEqual) + triggerSearch$ = createEffect(() => + this.store.pipe( + ofUrl(/^\/search.*/), + select(selectRouteParam('searchTerm')), + sample(this.actions$.pipe(ofType(routerNavigatedAction))), + whenTruthy(), + map(searchTerm => loadMoreProducts({ id: { type: 'search', value: searchTerm } })), + distinctUntilChanged(isEqual) + ) ); - @Effect() - searchProducts$ = this.actions$.pipe( - ofType(SearchActionTypes.SearchProducts), - mapToPayload(), - map(payload => ({ ...payload, page: payload.page ? payload.page : 1 })), - concatMap(({ searchTerm, page, sorting }) => - this.productsService.searchProducts(searchTerm, page, sorting).pipe( - concatMap(({ total, products, sortKeys }) => [ - ...products.map(product => new LoadProductSuccess({ product })), - new SetProductListingPages( - this.productListingMapper.createPages( - products.map(p => p.sku), - 'search', - searchTerm, - { - startPage: page, - sorting, - sortKeys, - itemCount: total, - } - ) - ), - ]), - mapErrorToAction(SearchProductsFail) + searchProducts$ = createEffect(() => + this.actions$.pipe( + ofType(searchProducts), + mapToPayload(), + map(payload => ({ ...payload, page: payload.page ? payload.page : 1 })), + concatMap(({ searchTerm, page, sorting }) => + this.productsService.searchProducts(searchTerm, page, sorting).pipe( + concatMap(({ total, products, sortKeys }) => [ + ...products.map(product => loadProductSuccess({ product })), + setProductListingPages( + this.productListingMapper.createPages( + products.map(p => p.sku), + 'search', + searchTerm, + { + startPage: page, + sorting, + sortKeys, + itemCount: total, + } + ) + ), + ]), + mapErrorToAction(searchProductsFail) + ) ) ) ); - @Effect() - suggestSearch$ = this.actions$.pipe( - ofType(SearchActionTypes.SuggestSearch), - mapToPayloadProperty('searchTerm'), - debounceTime(400), - distinctUntilChanged(), - whenTruthy(), - switchMap(searchTerm => - this.suggestService.search(searchTerm).pipe( - map(suggests => new SuggestSearchSuccess({ searchTerm, suggests })), - // tslint:disable-next-line:ban - catchError(() => EMPTY) + suggestSearch$ = createEffect(() => + this.actions$.pipe( + ofType(suggestSearch), + mapToPayloadProperty('searchTerm'), + debounceTime(400), + distinctUntilChanged(), + whenTruthy(), + switchMap(searchTerm => + this.suggestService.search(searchTerm).pipe( + map(suggests => suggestSearchSuccess({ searchTerm, suggests })), + // tslint:disable-next-line:ban + catchError(() => EMPTY) + ) ) ) ); - @Effect({ dispatch: false }) - redirectIfSearchProductFail$ = this.actions$.pipe( - ofType(SearchActionTypes.SearchProductsFail), - tap(() => this.httpStatusCodeService.setStatusAndRedirect(404)) + redirectIfSearchProductFail$ = createEffect( + () => + this.actions$.pipe( + ofType(searchProductsFail), + tap(() => this.httpStatusCodeService.setStatusAndRedirect(404)) + ), + { dispatch: false } ); } diff --git a/src/app/core/store/shopping/search/search.reducer.ts b/src/app/core/store/shopping/search/search.reducer.ts index 17696dfddb..a61caa6574 100644 --- a/src/app/core/store/shopping/search/search.reducer.ts +++ b/src/app/core/store/shopping/search/search.reducer.ts @@ -1,8 +1,9 @@ import { EntityState, createEntityAdapter } from '@ngrx/entity'; +import { createReducer, on } from '@ngrx/store'; import { SuggestTerm } from 'ish-core/models/suggest-term/suggest-term.model'; -import { SearchAction, SearchActionTypes } from './search.actions'; +import { suggestSearchSuccess } from './search.actions'; interface SuggestSearch { searchTerm: string; @@ -17,12 +18,7 @@ export interface SearchState extends EntityState {} const initialState: SearchState = searchAdapter.getInitialState({}); -export function searchReducer(state = initialState, action: SearchAction): SearchState { - switch (action.type) { - case SearchActionTypes.SuggestSearchSuccess: { - return searchAdapter.upsertOne(action.payload, state); - } - } - - return state; -} +export const searchReducer = createReducer( + initialState, + on(suggestSearchSuccess, (state: SearchState, action) => searchAdapter.upsertOne(action.payload, state)) +); diff --git a/src/app/core/store/shopping/shopping-store.spec.ts b/src/app/core/store/shopping/shopping-store.spec.ts index bc18708b18..7838f92d85 100644 --- a/src/app/core/store/shopping/shopping-store.spec.ts +++ b/src/app/core/store/shopping/shopping-store.spec.ts @@ -38,7 +38,7 @@ import { categoryTree } from 'ish-core/utils/dev/test-data-utils'; import { getCategoryTree, getSelectedCategory } from './categories'; import { getProductEntities, getSelectedProduct } from './products'; import { getRecentlyViewedProducts } from './recently'; -import { SuggestSearch } from './search'; +import { suggestSearch } from './search'; import { ShoppingStoreModule } from './shopping-store.module'; const getCategoryIds = createSelector(getCategoryTree, tree => Object.keys(tree.nodes)); @@ -281,7 +281,7 @@ describe('Shopping Store', () => { describe('and looking for suggestions', () => { beforeEach(fakeAsync(() => { store.reset(); - store.dispatch(new SuggestSearch({ searchTerm: 'some' })); + store.dispatch(suggestSearch({ searchTerm: 'some' })); tick(5000); })); diff --git a/src/app/core/utils/meta-reducers.spec.ts b/src/app/core/utils/meta-reducers.spec.ts index 6671270f78..33ae38f0a5 100644 --- a/src/app/core/utils/meta-reducers.spec.ts +++ b/src/app/core/utils/meta-reducers.spec.ts @@ -1,7 +1,7 @@ import { Action, combineReducers } from '@ngrx/store'; import { identity } from 'rxjs'; -import { LogoutUser } from 'ish-core/store/customer/user'; +import { logoutUser } from 'ish-core/store/customer/user'; import { resetOnLogoutMeta } from './meta-reducers'; @@ -20,12 +20,12 @@ describe('Meta Reducers', () => { }); it('should reset state when reducing LogoutUser action', () => { - const result = resetOnLogoutMeta(identity)(state, new LogoutUser()); + const result = resetOnLogoutMeta(identity)(state, logoutUser()); expect(result).toBeEmpty(); }); it('should reset and delegate to reducer initial state when reducing LogoutUser action', () => { - const result = resetOnLogoutMeta(reducer)(state, new LogoutUser()); + const result = resetOnLogoutMeta(reducer)(state, logoutUser()); expect(result).toEqual({ a: 'initialA', b: 'initialB' }); }); diff --git a/src/app/core/utils/meta-reducers.ts b/src/app/core/utils/meta-reducers.ts index e9633b2445..4aec01016e 100644 --- a/src/app/core/utils/meta-reducers.ts +++ b/src/app/core/utils/meta-reducers.ts @@ -1,10 +1,10 @@ import { Action, ActionReducer } from '@ngrx/store'; -import { UserActionTypes } from 'ish-core/store/customer/user'; +import { logoutUser } from 'ish-core/store/customer/user'; export function resetOnLogoutMeta(reducer: ActionReducer<{}>): ActionReducer<{}> { return (state: {}, action: Action) => { - if (action.type === UserActionTypes.LogoutUser) { + if (action.type === logoutUser.type) { return reducer({}, action); } return reducer(state, action); diff --git a/src/app/core/utils/ngrx-creators.ts b/src/app/core/utils/ngrx-creators.ts new file mode 100644 index 0000000000..947a29321f --- /dev/null +++ b/src/app/core/utils/ngrx-creators.ts @@ -0,0 +1,38 @@ +import { ActionCreator, On, on } from '@ngrx/store'; + +import { HttpError } from 'ish-core/models/http-error/http-error.model'; + +export function payload

() { + return (args: P) => ({ payload: { ...args } }); +} + +export function httpError

() { + return (args: { error: HttpError } & P) => ({ payload: { ...args } }); +} + +export function setLoadingOn(...actionCreators: ActionCreator[]): On { + const stateFnc = (state: S) => ({ + ...state, + loading: true, + }); + if (actionCreators.length === 1) { + return on(actionCreators[0], stateFnc); + } else { + return on(actionCreators[0], ...actionCreators.splice(1), stateFnc); + } +} + +export function setErrorOn( + ...actionCreators: ActionCreator[] +): On { + const stateFnc = (state: S, action: { payload: { error: HttpError }; type: string }) => ({ + ...state, + error: action.payload.error, + loading: false, + }); + if (actionCreators.length === 1) { + return on(actionCreators[0], stateFnc); + } else { + return on(actionCreators[0], ...actionCreators.splice(1), stateFnc); + } +} diff --git a/src/app/core/utils/operators.spec.ts b/src/app/core/utils/operators.spec.ts index 4df7a4dd4a..0bdae1427b 100644 --- a/src/app/core/utils/operators.spec.ts +++ b/src/app/core/utils/operators.spec.ts @@ -1,9 +1,10 @@ import { HttpErrorResponse, HttpHeaders } from '@angular/common/http'; -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; import { cold, hot } from 'jest-marbles'; import { of } from 'rxjs'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; +import { httpError } from 'ish-core/utils/ngrx-creators'; import { distinctCompareWith, mapErrorToAction, mapToProperty } from './operators'; @@ -27,10 +28,7 @@ describe('Operators', () => { }); describe('mapErrorToAction', () => { - class DummyFail implements Action { - type = 'dummy'; - constructor(public payload: { error: HttpError }) {} - } + const dummyFail = createAction('dummy', httpError()); it('should catch HttpErrorResponses and convert them to Fail actions', () => { const error = new HttpErrorResponse({ @@ -59,7 +57,7 @@ describe('Operators', () => { }, }); - expect(input$.pipe(mapErrorToAction(DummyFail))).toBeObservable(resu$); + expect(input$.pipe(mapErrorToAction(dummyFail))).toBeObservable(resu$); }); }); diff --git a/src/app/core/utils/operators.ts b/src/app/core/utils/operators.ts index 7ac7e6ce9d..d02e0b7a20 100644 --- a/src/app/core/utils/operators.ts +++ b/src/app/core/utils/operators.ts @@ -19,7 +19,7 @@ export function distinctCompareWith(observable: Observable): OperatorFunct } // tslint:disable-next-line:no-any -export function mapErrorToAction(actionType: new (error: { error: HttpError }) => T, extras?: any) { +export function mapErrorToAction(actionType: (props: { error: HttpError }) => T, extras?: any) { return (source$: Observable) => source$.pipe( // tslint:disable-next-line:ban ban-types @@ -39,7 +39,7 @@ export function mapErrorToAction(actionType: new (error: { error: HttpErro ) { console.error(err); } - const errorAction = new actionType({ error: HttpErrorMapper.fromError(err), ...extras }); + const errorAction = actionType({ error: HttpErrorMapper.fromError(err), ...extras }); return of(errorAction); }) ); diff --git a/src/app/extensions/order-templates/facades/order-templates.facade.ts b/src/app/extensions/order-templates/facades/order-templates.facade.ts index bfd1aa81e9..5abf18d821 100644 --- a/src/app/extensions/order-templates/facades/order-templates.facade.ts +++ b/src/app/extensions/order-templates/facades/order-templates.facade.ts @@ -6,18 +6,18 @@ import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { OrderTemplate, OrderTemplateHeader } from '../models/order-template/order-template.model'; import { - AddBasketToNewOrderTemplate, - AddProductToNewOrderTemplate, - AddProductToOrderTemplate, - CreateOrderTemplate, - DeleteOrderTemplate, - MoveItemToOrderTemplate, - RemoveItemFromOrderTemplate, - UpdateOrderTemplate, + addBasketToNewOrderTemplate, + addProductToNewOrderTemplate, + addProductToOrderTemplate, + createOrderTemplate, + deleteOrderTemplate, getAllOrderTemplates, getOrderTemplateError, getOrderTemplateLoading, getSelectedOrderTemplateDetails, + moveItemToOrderTemplate, + removeItemFromOrderTemplate, + updateOrderTemplate, } from '../store/order-template'; @Injectable({ providedIn: 'root' }) @@ -30,27 +30,27 @@ export class OrderTemplatesFacade { orderTemplateError$: Observable = this.store.pipe(select(getOrderTemplateError)); addOrderTemplate(orderTemplate: OrderTemplateHeader): void | HttpError { - this.store.dispatch(new CreateOrderTemplate({ orderTemplate })); + this.store.dispatch(createOrderTemplate({ orderTemplate })); } addBasketToNewOrderTemplate(orderTemplate: OrderTemplateHeader): void | HttpError { - this.store.dispatch(new AddBasketToNewOrderTemplate({ orderTemplate })); + this.store.dispatch(addBasketToNewOrderTemplate({ orderTemplate })); } deleteOrderTemplate(id: string): void { - this.store.dispatch(new DeleteOrderTemplate({ orderTemplateId: id })); + this.store.dispatch(deleteOrderTemplate({ orderTemplateId: id })); } updateOrderTemplate(orderTemplate: OrderTemplate): void { - this.store.dispatch(new UpdateOrderTemplate({ orderTemplate })); + this.store.dispatch(updateOrderTemplate({ orderTemplate })); } addProductToNewOrderTemplate(title: string, sku: string, quantity?: number): void { - this.store.dispatch(new AddProductToNewOrderTemplate({ title, sku, quantity })); + this.store.dispatch(addProductToNewOrderTemplate({ title, sku, quantity })); } addProductToOrderTemplate(orderTemplateId: string, sku: string, quantity?: number): void { - this.store.dispatch(new AddProductToOrderTemplate({ orderTemplateId, sku, quantity })); + this.store.dispatch(addProductToOrderTemplate({ orderTemplateId, sku, quantity })); } moveItemToOrderTemplate( @@ -60,7 +60,7 @@ export class OrderTemplatesFacade { quantity: number ): void { this.store.dispatch( - new MoveItemToOrderTemplate({ + moveItemToOrderTemplate({ source: { id: sourceorderTemplateId }, target: { id: targetorderTemplateId, sku, quantity }, }) @@ -69,11 +69,11 @@ export class OrderTemplatesFacade { moveItemToNewOrderTemplate(sourceOrderTemplateId: string, title: string, sku: string, quantity: number): void { this.store.dispatch( - new MoveItemToOrderTemplate({ source: { id: sourceOrderTemplateId }, target: { title, sku, quantity } }) + moveItemToOrderTemplate({ source: { id: sourceOrderTemplateId }, target: { title, sku, quantity } }) ); } removeProductFromOrderTemplate(orderTemplateId: string, sku: string): void { - this.store.dispatch(new RemoveItemFromOrderTemplate({ orderTemplateId, sku })); + this.store.dispatch(removeItemFromOrderTemplate({ orderTemplateId, sku })); } } diff --git a/src/app/extensions/order-templates/store/order-template/order-template.actions.ts b/src/app/extensions/order-templates/store/order-template/order-template.actions.ts index ad5beb764b..434aa48e16 100644 --- a/src/app/extensions/order-templates/store/order-template/order-template.actions.ts +++ b/src/app/extensions/order-templates/store/order-template/order-template.actions.ts @@ -1,190 +1,110 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; -import { HttpError } from 'ish-core/models/http-error/http-error.model'; +import { httpError, payload } from 'ish-core/utils/ngrx-creators'; import { OrderTemplate, OrderTemplateHeader } from '../../models/order-template/order-template.model'; -export enum OrderTemplatesActionTypes { - LoadOrderTemplates = '[Order Templates Internal] Load Order Templates', - LoadOrderTemplatesSuccess = '[Order Templates API] Load Order Templates Success', - LoadOrderTemplatesFail = '[Order Templates API] Load Order Templates Fail', - - CreateOrderTemplate = '[Order Templates] Create Order Template', - CreateOrderTemplateSuccess = '[Order Templates API] Create Order Template Success', - CreateOrderTemplateFail = '[Order Templates API] Create Order Template Fail', - CreateOrderTemplateWithItems = '[Order Templates] Create Order Template with basket items', - - UpdateOrderTemplate = '[Order Templates] Update Order Template', - UpdateOrderTemplateSuccess = '[Order Templates API] Update Order Template Success', - UpdateOrderTemplateFail = '[Order Templates API] Update Order Template Fail', - - DeleteOrderTemplate = '[Order Templates] Delete Order Template', - DeleteOrderTemplateSuccess = '[Order Templates API] Delete Order Template Success', - DeleteOrderTemplateFail = '[Order Templates API] Delete Order Template Fail', - - AddProductToOrderTemplate = '[Order Templates] Add Item to Order Template', - AddProductToOrderTemplateSuccess = '[Order Templates API] Add Item to Order Template Success', - AddProductToOrderTemplateFail = '[Order Templates API] Add Item to Order Template Fail', - - AddProductToNewOrderTemplate = '[Order Templates Internal] Add Product To New Order Template', - - MoveItemToOrderTemplate = '[Order Templates] Move Item to another Order Template', - - RemoveItemFromOrderTemplate = '[Order Templates] Remove Item from Order Template', - RemoveItemFromOrderTemplateSuccess = '[Order Templates API] Remove Item from Order Template Success', - RemoveItemFromOrderTemplateFail = '[Order Templates API] Remove Item from Order Template Fail', - - SelectOrderTemplate = '[Order Templates Internal] Select Order Template', - - AddBasketToNewOrderTemplate = '[Order Templates] Add basket to New Order Template]', - AddBasketToNewOrderTemplateSuccess = '[Order Templates] Add basket to New Order Template Success]', - AddBasketToNewOrderTemplateFail = '[Order Templates] Add basket to New Order Template Fail]', -} - -export class LoadOrderTemplates implements Action { - readonly type = OrderTemplatesActionTypes.LoadOrderTemplates; -} - -export class LoadOrderTemplatesSuccess implements Action { - readonly type = OrderTemplatesActionTypes.LoadOrderTemplatesSuccess; - constructor(public payload: { orderTemplates: OrderTemplate[] }) {} -} - -export class LoadOrderTemplatesFail implements Action { - readonly type = OrderTemplatesActionTypes.LoadOrderTemplatesFail; - constructor(public payload: { error: HttpError }) {} -} - -export class CreateOrderTemplate implements Action { - readonly type = OrderTemplatesActionTypes.CreateOrderTemplate; - constructor(public payload: { orderTemplate: OrderTemplateHeader }) {} -} - -export class CreateOrderTemplateSuccess implements Action { - readonly type = OrderTemplatesActionTypes.CreateOrderTemplateSuccess; - constructor(public payload: { orderTemplate: OrderTemplate }) {} -} - -export class CreateOrderTemplateFail implements Action { - readonly type = OrderTemplatesActionTypes.CreateOrderTemplateFail; - constructor(public payload: { error: HttpError }) {} -} - -export class UpdateOrderTemplate implements Action { - readonly type = OrderTemplatesActionTypes.UpdateOrderTemplate; - constructor(public payload: { orderTemplate: OrderTemplate }) {} -} - -export class UpdateOrderTemplateSuccess implements Action { - readonly type = OrderTemplatesActionTypes.UpdateOrderTemplateSuccess; - constructor(public payload: { orderTemplate: OrderTemplate }) {} -} - -export class UpdateOrderTemplateFail implements Action { - readonly type = OrderTemplatesActionTypes.UpdateOrderTemplateFail; - constructor(public payload: { error: HttpError }) {} -} - -export class DeleteOrderTemplate implements Action { - readonly type = OrderTemplatesActionTypes.DeleteOrderTemplate; - constructor(public payload: { orderTemplateId: string }) {} -} - -export class DeleteOrderTemplateSuccess implements Action { - readonly type = OrderTemplatesActionTypes.DeleteOrderTemplateSuccess; - - constructor(public payload: { orderTemplateId: string }) {} -} - -export class DeleteOrderTemplateFail implements Action { - readonly type = OrderTemplatesActionTypes.DeleteOrderTemplateFail; - constructor(public payload: { error: HttpError }) {} -} - -export class AddProductToOrderTemplate implements Action { - readonly type = OrderTemplatesActionTypes.AddProductToOrderTemplate; - constructor(public payload: { orderTemplateId: string; sku: string; quantity?: number }) {} -} - -export class AddProductToOrderTemplateSuccess implements Action { - readonly type = OrderTemplatesActionTypes.AddProductToOrderTemplateSuccess; - constructor(public payload: { orderTemplate: OrderTemplate }) {} -} - -export class AddProductToOrderTemplateFail implements Action { - readonly type = OrderTemplatesActionTypes.AddProductToOrderTemplateFail; - constructor(public payload: { error: HttpError }) {} -} - -export class AddProductToNewOrderTemplate implements Action { - readonly type = OrderTemplatesActionTypes.AddProductToNewOrderTemplate; - constructor(public payload: { title: string; sku: string; quantity?: number }) {} -} - -export class MoveItemToOrderTemplate implements Action { - readonly type = OrderTemplatesActionTypes.MoveItemToOrderTemplate; - constructor( - public payload: { source: { id: string }; target: { id?: string; title?: string; sku: string; quantity: number } } - ) {} -} - -export class RemoveItemFromOrderTemplate implements Action { - readonly type = OrderTemplatesActionTypes.RemoveItemFromOrderTemplate; - constructor(public payload: { orderTemplateId: string; sku: string }) {} -} - -export class RemoveItemFromOrderTemplateSuccess implements Action { - readonly type = OrderTemplatesActionTypes.RemoveItemFromOrderTemplateSuccess; - constructor(public payload: { orderTemplate: OrderTemplate }) {} -} - -export class RemoveItemFromOrderTemplateFail implements Action { - readonly type = OrderTemplatesActionTypes.RemoveItemFromOrderTemplateFail; - constructor(public payload: { error: HttpError }) {} -} - -export class SelectOrderTemplate implements Action { - readonly type = OrderTemplatesActionTypes.SelectOrderTemplate; - constructor(public payload: { id: string }) {} -} - -export class AddBasketToNewOrderTemplate implements Action { - readonly type = OrderTemplatesActionTypes.AddBasketToNewOrderTemplate; - constructor(public payload: { orderTemplate: OrderTemplateHeader }) {} -} - -export class AddBasketToNewOrderTemplateFail implements Action { - readonly type = OrderTemplatesActionTypes.AddBasketToNewOrderTemplateFail; - constructor(public payload: { error: HttpError }) {} -} - -export class AddBasketToNewOrderTemplateSuccess implements Action { - readonly type = OrderTemplatesActionTypes.AddBasketToNewOrderTemplateSuccess; - constructor(public payload: { orderTemplate: OrderTemplate }) {} -} - -export type OrderTemplateAction = - | LoadOrderTemplates - | LoadOrderTemplatesSuccess - | LoadOrderTemplatesFail - | CreateOrderTemplate - | CreateOrderTemplateSuccess - | CreateOrderTemplateFail - | UpdateOrderTemplate - | UpdateOrderTemplateSuccess - | UpdateOrderTemplateFail - | DeleteOrderTemplate - | DeleteOrderTemplateSuccess - | DeleteOrderTemplateFail - | AddProductToOrderTemplate - | AddProductToOrderTemplateSuccess - | AddProductToOrderTemplateFail - | AddProductToNewOrderTemplate - | MoveItemToOrderTemplate - | RemoveItemFromOrderTemplate - | RemoveItemFromOrderTemplateSuccess - | RemoveItemFromOrderTemplateFail - | SelectOrderTemplate - | AddBasketToNewOrderTemplate - | AddBasketToNewOrderTemplateSuccess - | AddBasketToNewOrderTemplateFail; +export const loadOrderTemplates = createAction('[Order Templates Internal] Load Order Templates'); + +export const loadOrderTemplatesSuccess = createAction( + '[Order Templates API] Load Order Templates Success', + payload<{ orderTemplates: OrderTemplate[] }>() +); + +export const loadOrderTemplatesFail = createAction('[Order Templates API] Load Order Templates Fail', httpError()); + +export const createOrderTemplate = createAction( + '[Order Templates] Create Order Template', + payload<{ orderTemplate: OrderTemplateHeader }>() +); + +export const createOrderTemplateSuccess = createAction( + '[Order Templates API] Create Order Template Success', + payload<{ orderTemplate: OrderTemplate }>() +); + +export const createOrderTemplateFail = createAction('[Order Templates API] Create Order Template Fail', httpError()); + +export const updateOrderTemplate = createAction( + '[Order Templates] Update Order Template', + payload<{ orderTemplate: OrderTemplate }>() +); + +export const updateOrderTemplateSuccess = createAction( + '[Order Templates API] Update Order Template Success', + payload<{ orderTemplate: OrderTemplate }>() +); + +export const updateOrderTemplateFail = createAction('[Order Templates API] Update Order Template Fail', httpError()); + +export const deleteOrderTemplate = createAction( + '[Order Templates] Delete Order Template', + payload<{ orderTemplateId: string }>() +); + +export const deleteOrderTemplateSuccess = createAction( + '[Order Templates API] Delete Order Template Success', + payload<{ orderTemplateId: string }>() +); + +export const deleteOrderTemplateFail = createAction('[Order Templates API] Delete Order Template Fail', httpError()); + +export const addProductToOrderTemplate = createAction( + '[Order Templates] Add Item to Order Template', + payload<{ orderTemplateId: string; sku: string; quantity?: number }>() +); + +export const addProductToOrderTemplateSuccess = createAction( + '[Order Templates API] Add Item to Order Template Success', + payload<{ orderTemplate: OrderTemplate }>() +); + +export const addProductToOrderTemplateFail = createAction( + '[Order Templates API] Add Item to Order Template Fail', + httpError() +); + +export const addProductToNewOrderTemplate = createAction( + '[Order Templates Internal] Add Product To New Order Template', + payload<{ title: string; sku: string; quantity?: number }>() +); + +export const moveItemToOrderTemplate = createAction( + '[Order Templates] Move Item to another Order Template', + payload<{ source: { id: string }; target: { id?: string; title?: string; sku: string; quantity: number } }>() +); + +export const removeItemFromOrderTemplate = createAction( + '[Order Templates] Remove Item from Order Template', + payload<{ orderTemplateId: string; sku: string }>() +); + +export const removeItemFromOrderTemplateSuccess = createAction( + '[Order Templates API] Remove Item from Order Template Success', + payload<{ orderTemplate: OrderTemplate }>() +); + +export const removeItemFromOrderTemplateFail = createAction( + '[Order Templates API] Remove Item from Order Template Fail', + httpError() +); + +export const selectOrderTemplate = createAction( + '[Order Templates Internal] Select Order Template', + payload<{ id: string }>() +); + +export const addBasketToNewOrderTemplate = createAction( + '[Order Templates] Add basket to New Order Template]', + payload<{ orderTemplate: OrderTemplateHeader }>() +); + +export const addBasketToNewOrderTemplateFail = createAction( + '[Order Templates] Add basket to New Order Template Fail]', + httpError() +); + +export const addBasketToNewOrderTemplateSuccess = createAction( + '[Order Templates] Add basket to New Order Template Success]', + payload<{ orderTemplate: OrderTemplate }>() +); diff --git a/src/app/extensions/order-templates/store/order-template/order-template.effects.spec.ts b/src/app/extensions/order-templates/store/order-template/order-template.effects.spec.ts index 41116e607c..fb39af766a 100644 --- a/src/app/extensions/order-templates/store/order-template/order-template.effects.spec.ts +++ b/src/app/extensions/order-templates/store/order-template/order-template.effects.spec.ts @@ -12,37 +12,36 @@ import { FeatureToggleModule } from 'ish-core/feature-toggle.module'; import { Customer } from 'ish-core/models/customer/customer.model'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; -import { DisplaySuccessMessage } from 'ish-core/store/core/messages'; +import { displaySuccessMessage } from 'ish-core/store/core/messages'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; -import { LoginUserSuccess } from 'ish-core/store/customer/user'; +import { loginUserSuccess } from 'ish-core/store/customer/user'; import { OrderTemplate } from '../../models/order-template/order-template.model'; import { OrderTemplateService } from '../../services/order-template/order-template.service'; import { OrderTemplatesStoreModule } from '../order-templates-store.module'; import { - AddProductToNewOrderTemplate, - AddProductToOrderTemplate, - AddProductToOrderTemplateFail, - AddProductToOrderTemplateSuccess, - CreateOrderTemplate, - CreateOrderTemplateFail, - CreateOrderTemplateSuccess, - DeleteOrderTemplate, - DeleteOrderTemplateFail, - DeleteOrderTemplateSuccess, - LoadOrderTemplates, - LoadOrderTemplatesFail, - LoadOrderTemplatesSuccess, - MoveItemToOrderTemplate, - OrderTemplatesActionTypes, - RemoveItemFromOrderTemplate, - RemoveItemFromOrderTemplateFail, - RemoveItemFromOrderTemplateSuccess, - SelectOrderTemplate, - UpdateOrderTemplate, - UpdateOrderTemplateFail, - UpdateOrderTemplateSuccess, + addProductToNewOrderTemplate, + addProductToOrderTemplate, + addProductToOrderTemplateFail, + addProductToOrderTemplateSuccess, + createOrderTemplate, + createOrderTemplateFail, + createOrderTemplateSuccess, + deleteOrderTemplate, + deleteOrderTemplateFail, + deleteOrderTemplateSuccess, + loadOrderTemplates, + loadOrderTemplatesFail, + loadOrderTemplatesSuccess, + moveItemToOrderTemplate, + removeItemFromOrderTemplate, + removeItemFromOrderTemplateFail, + removeItemFromOrderTemplateSuccess, + selectOrderTemplate, + updateOrderTemplate, + updateOrderTemplateFail, + updateOrderTemplateSuccess, } from './order-template.actions'; import { OrderTemplateEffects } from './order-template.effects'; @@ -100,12 +99,12 @@ describe('Order Template Effects', () => { describe('loadOrderTemplate$', () => { beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); + store$.dispatch(loginUserSuccess({ customer })); when(orderTemplateServiceMock.getOrderTemplates()).thenReturn(of(orderTemplates)); }); it('should call the OrderTemplateService for loadOrderTemplate', done => { - const action = new LoadOrderTemplates(); + const action = loadOrderTemplates(); actions$ = of(action); effects.loadOrderTemplates$.subscribe(() => { @@ -115,8 +114,8 @@ describe('Order Template Effects', () => { }); it('should map to actions of type LoadOrderTemplatesSuccess', () => { - const action = new LoadOrderTemplates(); - const completion = new LoadOrderTemplatesSuccess({ + const action = loadOrderTemplates(); + const completion = loadOrderTemplatesSuccess({ orderTemplates, }); actions$ = hot('-a-a-a', { a: action }); @@ -128,8 +127,8 @@ describe('Order Template Effects', () => { it('should map failed calls to actions of type LoadOrderTemplateFail', () => { const error = { message: 'invalid' } as HttpError; when(orderTemplateServiceMock.getOrderTemplates()).thenReturn(throwError(error)); - const action = new LoadOrderTemplates(); - const completion = new LoadOrderTemplatesFail({ + const action = loadOrderTemplates(); + const completion = loadOrderTemplatesFail({ error, }); actions$ = hot('-a-a-a', { a: action }); @@ -151,12 +150,12 @@ describe('Order Template Effects', () => { public: false, }; beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); + store$.dispatch(loginUserSuccess({ customer })); when(orderTemplateServiceMock.createOrderTemplate(anything())).thenReturn(of(orderTemplateData[0])); }); it('should call the OrderTemplateService for createOrderTemplate', done => { - const action = new CreateOrderTemplate({ orderTemplate: createOrderTemplateData }); + const action = createOrderTemplate({ orderTemplate: createOrderTemplateData }); actions$ = of(action); effects.createOrderTemplate$.subscribe(() => { @@ -166,11 +165,11 @@ describe('Order Template Effects', () => { }); it('should map to actions of type CreateOrderTemplateSuccess and SuccessMessage', () => { - const action = new CreateOrderTemplate({ orderTemplate: createOrderTemplateData }); - const completion1 = new CreateOrderTemplateSuccess({ + const action = createOrderTemplate({ orderTemplate: createOrderTemplateData }); + const completion1 = createOrderTemplateSuccess({ orderTemplate: orderTemplateData[0], }); - const completion2 = new DisplaySuccessMessage({ + const completion2 = displaySuccessMessage({ message: 'account.order_template.new_order_template.confirmation', messageParams: { 0: createOrderTemplateData.title }, }); @@ -182,8 +181,8 @@ describe('Order Template Effects', () => { it('should map failed calls to actions of type CreateOrderTemplateFail', () => { const error = { message: 'invalid' } as HttpError; when(orderTemplateServiceMock.createOrderTemplate(anything())).thenReturn(throwError(error)); - const action = new CreateOrderTemplate({ orderTemplate: createOrderTemplateData }); - const completion = new CreateOrderTemplateFail({ + const action = createOrderTemplate({ orderTemplate: createOrderTemplateData }); + const completion = createOrderTemplateFail({ error, }); actions$ = hot('-a-a-a', { a: action }); @@ -196,13 +195,13 @@ describe('Order Template Effects', () => { describe('deleteOrderTemplate$', () => { const id = orderTemplates[0].id; beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); - store$.dispatch(new CreateOrderTemplateSuccess({ orderTemplate: orderTemplates[0] })); + store$.dispatch(loginUserSuccess({ customer })); + store$.dispatch(createOrderTemplateSuccess({ orderTemplate: orderTemplates[0] })); when(orderTemplateServiceMock.deleteOrderTemplate(anyString())).thenReturn(of(undefined)); }); it('should call the OrderTemplateService for deleteOrderTemplate', done => { - const action = new DeleteOrderTemplate({ orderTemplateId: id }); + const action = deleteOrderTemplate({ orderTemplateId: id }); actions$ = of(action); effects.deleteOrderTemplate$.subscribe(() => { @@ -212,9 +211,9 @@ describe('Order Template Effects', () => { }); it('should map to actions of type DeleteOrderTemplateSuccess', () => { - const action = new DeleteOrderTemplate({ orderTemplateId: id }); - const completion1 = new DeleteOrderTemplateSuccess({ orderTemplateId: id }); - const completion2 = new DisplaySuccessMessage({ + const action = deleteOrderTemplate({ orderTemplateId: id }); + const completion1 = deleteOrderTemplateSuccess({ orderTemplateId: id }); + const completion2 = displaySuccessMessage({ message: 'account.order_template.delete_order_template.confirmation', messageParams: { 0: orderTemplates[0].title }, }); @@ -226,8 +225,8 @@ describe('Order Template Effects', () => { it('should map failed calls to actions of type DeleteOrderTemplateFail', () => { const error = { message: 'invalid' } as HttpError; when(orderTemplateServiceMock.deleteOrderTemplate(anyString())).thenReturn(throwError(error)); - const action = new DeleteOrderTemplate({ orderTemplateId: id }); - const completion = new DeleteOrderTemplateFail({ + const action = deleteOrderTemplate({ orderTemplateId: id }); + const completion = deleteOrderTemplateFail({ error, }); actions$ = hot('-a-a-a', { a: action }); @@ -247,12 +246,12 @@ describe('Order Template Effects', () => { }, ]; beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); + store$.dispatch(loginUserSuccess({ customer })); when(orderTemplateServiceMock.updateOrderTemplate(anything())).thenReturn(of(orderTemplateDetailData[0])); }); it('should call the OrderTemplateService for updateOrderTemplate', done => { - const action = new UpdateOrderTemplate({ orderTemplate: orderTemplateDetailData[0] }); + const action = updateOrderTemplate({ orderTemplate: orderTemplateDetailData[0] }); actions$ = of(action); effects.updateOrderTemplate$.subscribe(() => { @@ -262,9 +261,9 @@ describe('Order Template Effects', () => { }); it('should map to actions of type UpdateOrderTemplateSuccess', () => { - const action = new UpdateOrderTemplate({ orderTemplate: orderTemplateDetailData[0] }); - const completion1 = new UpdateOrderTemplateSuccess({ orderTemplate: orderTemplateDetailData[0] }); - const completion2 = new DisplaySuccessMessage({ + const action = updateOrderTemplate({ orderTemplate: orderTemplateDetailData[0] }); + const completion1 = updateOrderTemplateSuccess({ orderTemplate: orderTemplateDetailData[0] }); + const completion2 = displaySuccessMessage({ message: 'account.order_templates.edit.confirmation', messageParams: { 0: orderTemplateDetailData[0].title }, }); @@ -276,8 +275,8 @@ describe('Order Template Effects', () => { it('should map failed calls to actions of type UpdateOrderTemplateFail', () => { const error = { message: 'invalid' } as HttpError; when(orderTemplateServiceMock.updateOrderTemplate(anything())).thenReturn(throwError(error)); - const action = new UpdateOrderTemplate({ orderTemplate: orderTemplateDetailData[0] }); - const completion = new UpdateOrderTemplateFail({ + const action = updateOrderTemplate({ orderTemplate: orderTemplateDetailData[0] }); + const completion = updateOrderTemplateFail({ error, }); actions$ = hot('-a-a-a', { a: action }); @@ -294,14 +293,14 @@ describe('Order Template Effects', () => { }; beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); + store$.dispatch(loginUserSuccess({ customer })); when(orderTemplateServiceMock.addProductToOrderTemplate(anyString(), anyString(), anyNumber())).thenReturn( of(orderTemplates[0]) ); }); it('should call the OrderTemplateService for addProductToOrderTemplate', done => { - const action = new AddProductToOrderTemplate(payload); + const action = addProductToOrderTemplate(payload); actions$ = of(action); effects.addProductToOrderTemplate$.subscribe(() => { @@ -313,8 +312,8 @@ describe('Order Template Effects', () => { }); it('should map to actions of type AddProductToOrderTemplateSuccess', () => { - const action = new AddProductToOrderTemplate(payload); - const completion = new AddProductToOrderTemplateSuccess({ orderTemplate: orderTemplates[0] }); + const action = addProductToOrderTemplate(payload); + const completion = addProductToOrderTemplateSuccess({ orderTemplate: orderTemplates[0] }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); expect(effects.addProductToOrderTemplate$).toBeObservable(expected$); @@ -325,8 +324,8 @@ describe('Order Template Effects', () => { when(orderTemplateServiceMock.addProductToOrderTemplate(anyString(), anyString(), anything())).thenReturn( throwError(error) ); - const action = new AddProductToOrderTemplate(payload); - const completion = new AddProductToOrderTemplateFail({ + const action = addProductToOrderTemplate(payload); + const completion = addProductToOrderTemplateFail({ error, }); actions$ = hot('-a-a-a', { a: action }); @@ -348,14 +347,14 @@ describe('Order Template Effects', () => { public: false, }; beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); + store$.dispatch(loginUserSuccess({ customer })); when(orderTemplateServiceMock.createOrderTemplate(anything())).thenReturn(of(orderTemplate)); }); it('should map to actions of types CreateOrderTemplateSuccess and AddProductToOrderTemplate', () => { - const action = new AddProductToNewOrderTemplate(payload); - const completion1 = new CreateOrderTemplateSuccess({ orderTemplate }); - const completion2 = new AddProductToOrderTemplate({ orderTemplateId: orderTemplate.id, sku: payload.sku }); - const completion3 = new SelectOrderTemplate({ id: orderTemplate.id }); + const action = addProductToNewOrderTemplate(payload); + const completion1 = createOrderTemplateSuccess({ orderTemplate }); + const completion2 = addProductToOrderTemplate({ orderTemplateId: orderTemplate.id, sku: payload.sku }); + const completion3 = selectOrderTemplate({ id: orderTemplate.id }); actions$ = hot('-a-----a-----a', { a: action }); const expected$ = cold('-(bcd)-(bcd)-(bcd)', { b: completion1, c: completion2, d: completion3 }); expect(effects.addProductToNewOrderTemplate$).toBeObservable(expected$); @@ -363,8 +362,8 @@ describe('Order Template Effects', () => { it('should map failed calls to actions of type CreateOrderTemplateFail', () => { const error = { message: 'invalid' } as HttpError; when(orderTemplateServiceMock.createOrderTemplate(anything())).thenReturn(throwError(error)); - const action = new AddProductToNewOrderTemplate(payload); - const completion = new CreateOrderTemplateFail({ + const action = addProductToNewOrderTemplate(payload); + const completion = createOrderTemplateFail({ error, }); actions$ = hot('-a-a-a', { a: action }); @@ -390,17 +389,17 @@ describe('Order Template Effects', () => { public: false, }; beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); + store$.dispatch(loginUserSuccess({ customer })); when(orderTemplateServiceMock.createOrderTemplate(anything())).thenReturn(of(orderTemplate)); }); it('should map to actions of types AddProductToNewOrderTemplate and RemoveItemFromOrderTemplate if there is no target id given', () => { - const action = new MoveItemToOrderTemplate(payload1); - const completion1 = new AddProductToNewOrderTemplate({ + const action = moveItemToOrderTemplate(payload1); + const completion1 = addProductToNewOrderTemplate({ title: payload1.target.title, sku: payload1.target.sku, quantity: payload1.target.quantity, }); - const completion2 = new RemoveItemFromOrderTemplate({ + const completion2 = removeItemFromOrderTemplate({ orderTemplateId: payload1.source.id, sku: payload1.target.sku, }); @@ -409,13 +408,13 @@ describe('Order Template Effects', () => { expect(effects.moveItemToOrderTemplate$).toBeObservable(expected$); }); it('should map to actions of types AddProductToOrderTemplate and RemoveItemFromOrderTemplate if there is a target id given', () => { - const action = new MoveItemToOrderTemplate(payload2); - const completion1 = new AddProductToOrderTemplate({ + const action = moveItemToOrderTemplate(payload2); + const completion1 = addProductToOrderTemplate({ orderTemplateId: orderTemplate.id, sku: payload1.target.sku, quantity: payload1.target.quantity, }); - const completion2 = new RemoveItemFromOrderTemplate({ + const completion2 = removeItemFromOrderTemplate({ orderTemplateId: payload1.source.id, sku: payload1.target.sku, }); @@ -438,14 +437,14 @@ describe('Order Template Effects', () => { public: false, }; beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); + store$.dispatch(loginUserSuccess({ customer })); when(orderTemplateServiceMock.removeProductFromOrderTemplate(anyString(), anyString())).thenReturn( of(orderTemplate) ); }); it('should call the OrderTemplateService for removeProductFromOrderTemplate', done => { - const action = new RemoveItemFromOrderTemplate(payload); + const action = removeItemFromOrderTemplate(payload); actions$ = of(action); effects.removeProductFromOrderTemplate$.subscribe(() => { @@ -454,8 +453,8 @@ describe('Order Template Effects', () => { }); }); it('should map to actions of type RemoveItemFromOrderTemplateSuccess', () => { - const action = new RemoveItemFromOrderTemplate(payload); - const completion = new RemoveItemFromOrderTemplateSuccess({ orderTemplate }); + const action = removeItemFromOrderTemplate(payload); + const completion = removeItemFromOrderTemplateSuccess({ orderTemplate }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); expect(effects.removeProductFromOrderTemplate$).toBeObservable(expected$); @@ -465,8 +464,8 @@ describe('Order Template Effects', () => { when(orderTemplateServiceMock.removeProductFromOrderTemplate(anyString(), anyString())).thenReturn( throwError(error) ); - const action = new RemoveItemFromOrderTemplate(payload); - const completion = new RemoveItemFromOrderTemplateFail({ + const action = removeItemFromOrderTemplate(payload); + const completion = removeItemFromOrderTemplateFail({ error, }); actions$ = hot('-a-a-a', { a: action }); @@ -496,18 +495,18 @@ describe('Order Template Effects', () => { }); it('should call OrderTemplatesService after login action was dispatched', done => { effects.loadOrderTemplatesAfterLogin$.subscribe(action => { - expect(action.type).toEqual(OrderTemplatesActionTypes.LoadOrderTemplates); + expect(action.type).toEqual(loadOrderTemplates.type); done(); }); - store$.dispatch(new LoginUserSuccess({ customer })); + store$.dispatch(loginUserSuccess({ customer })); }); }); describe('setOrderTemplateBreadcrumb$', () => { beforeEach(() => { - store$.dispatch(new LoadOrderTemplatesSuccess({ orderTemplates })); - store$.dispatch(new SelectOrderTemplate({ id: orderTemplates[0].id })); + store$.dispatch(loadOrderTemplatesSuccess({ orderTemplates })); + store$.dispatch(selectOrderTemplate({ id: orderTemplates[0].id })); }); it('should set the breadcrumb of the selected Order Template', done => { diff --git a/src/app/extensions/order-templates/store/order-template/order-template.effects.ts b/src/app/extensions/order-templates/store/order-template/order-template.effects.ts index cc7800c0a2..926acde020 100644 --- a/src/app/extensions/order-templates/store/order-template/order-template.effects.ts +++ b/src/app/extensions/order-templates/store/order-template/order-template.effects.ts @@ -1,12 +1,12 @@ import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { concat } from 'rxjs'; import { concatMap, filter, last, map, mapTo, mergeMap, switchMap, withLatestFrom } from 'rxjs/operators'; -import { DisplaySuccessMessage } from 'ish-core/store/core/messages'; +import { displaySuccessMessage } from 'ish-core/store/core/messages'; import { selectRouteParam } from 'ish-core/store/core/router'; -import { SetBreadcrumbData } from 'ish-core/store/core/viewconf'; +import { setBreadcrumbData } from 'ish-core/store/core/viewconf'; import { getCurrentBasket } from 'ish-core/store/customer/basket'; import { getUserAuthorized } from 'ish-core/store/customer/user'; import { @@ -21,31 +21,30 @@ import { OrderTemplate, OrderTemplateHeader } from '../../models/order-template/ import { OrderTemplateService } from '../../services/order-template/order-template.service'; import { - AddBasketToNewOrderTemplate, - AddBasketToNewOrderTemplateFail, - AddBasketToNewOrderTemplateSuccess, - AddProductToNewOrderTemplate, - AddProductToOrderTemplate, - AddProductToOrderTemplateFail, - AddProductToOrderTemplateSuccess, - CreateOrderTemplate, - CreateOrderTemplateFail, - CreateOrderTemplateSuccess, - DeleteOrderTemplate, - DeleteOrderTemplateFail, - DeleteOrderTemplateSuccess, - LoadOrderTemplates, - LoadOrderTemplatesFail, - LoadOrderTemplatesSuccess, - MoveItemToOrderTemplate, - OrderTemplatesActionTypes, - RemoveItemFromOrderTemplate, - RemoveItemFromOrderTemplateFail, - RemoveItemFromOrderTemplateSuccess, - SelectOrderTemplate, - UpdateOrderTemplate, - UpdateOrderTemplateFail, - UpdateOrderTemplateSuccess, + addBasketToNewOrderTemplate, + addBasketToNewOrderTemplateFail, + addBasketToNewOrderTemplateSuccess, + addProductToNewOrderTemplate, + addProductToOrderTemplate, + addProductToOrderTemplateFail, + addProductToOrderTemplateSuccess, + createOrderTemplate, + createOrderTemplateFail, + createOrderTemplateSuccess, + deleteOrderTemplate, + deleteOrderTemplateFail, + deleteOrderTemplateSuccess, + loadOrderTemplates, + loadOrderTemplatesFail, + loadOrderTemplatesSuccess, + moveItemToOrderTemplate, + removeItemFromOrderTemplate, + removeItemFromOrderTemplateFail, + removeItemFromOrderTemplateSuccess, + selectOrderTemplate, + updateOrderTemplate, + updateOrderTemplateFail, + updateOrderTemplateSuccess, } from './order-template.actions'; import { getOrderTemplateDetails, @@ -57,225 +56,234 @@ import { export class OrderTemplateEffects { constructor(private actions$: Actions, private orderTemplateService: OrderTemplateService, private store: Store) {} - @Effect() - loadOrderTemplates$ = this.actions$.pipe( - ofType(OrderTemplatesActionTypes.LoadOrderTemplates), - withLatestFrom(this.store.pipe(select(getUserAuthorized))), - filter(([, authorized]) => authorized), - switchMap(() => - this.orderTemplateService.getOrderTemplates().pipe( - map(orderTemplates => new LoadOrderTemplatesSuccess({ orderTemplates })), - mapErrorToAction(LoadOrderTemplatesFail) + loadOrderTemplates$ = createEffect(() => + this.actions$.pipe( + ofType(loadOrderTemplates), + withLatestFrom(this.store.pipe(select(getUserAuthorized))), + filter(([, authorized]) => authorized), + switchMap(() => + this.orderTemplateService.getOrderTemplates().pipe( + map(orderTemplates => loadOrderTemplatesSuccess({ orderTemplates })), + mapErrorToAction(loadOrderTemplatesFail) + ) ) ) ); - @Effect() - createOrderTemplate$ = this.actions$.pipe( - ofType(OrderTemplatesActionTypes.CreateOrderTemplate), - mapToPayloadProperty('orderTemplate'), - mergeMap((orderTemplateData: OrderTemplateHeader) => - this.orderTemplateService.createOrderTemplate(orderTemplateData).pipe( - mergeMap(orderTemplate => [ - new CreateOrderTemplateSuccess({ orderTemplate }), - new DisplaySuccessMessage({ - message: 'account.order_template.new_order_template.confirmation', - messageParams: { 0: orderTemplate.title }, - }), - ]), - mapErrorToAction(CreateOrderTemplateFail) + createOrderTemplate$ = createEffect(() => + this.actions$.pipe( + ofType(createOrderTemplate), + mapToPayloadProperty('orderTemplate'), + mergeMap((orderTemplateData: OrderTemplateHeader) => + this.orderTemplateService.createOrderTemplate(orderTemplateData).pipe( + mergeMap(orderTemplate => [ + createOrderTemplateSuccess({ orderTemplate }), + displaySuccessMessage({ + message: 'account.order_template.new_order_template.confirmation', + messageParams: { 0: orderTemplate.title }, + }), + ]), + mapErrorToAction(createOrderTemplateFail) + ) ) ) ); - @Effect() - addBasketToNewOrderTemplate$ = this.actions$.pipe( - ofType(OrderTemplatesActionTypes.AddBasketToNewOrderTemplate), - mapToPayload(), - mergeMap(payload => - this.orderTemplateService - .createOrderTemplate({ - title: payload.orderTemplate.title, - }) - .pipe( - withLatestFrom(this.store.pipe(select(getCurrentBasket))), - // use created order template data to dispatch addProduct action - concatMap(([orderTemplate, currentBasket]) => - concat( - ...currentBasket.lineItems.map(lineItem => - this.orderTemplateService.addProductToOrderTemplate( - orderTemplate.id, - lineItem.productSKU, - lineItem.quantity.value + addBasketToNewOrderTemplate$ = createEffect(() => + this.actions$.pipe( + ofType(addBasketToNewOrderTemplate), + mapToPayload(), + mergeMap(payload => + this.orderTemplateService + .createOrderTemplate({ + title: payload.orderTemplate.title, + }) + .pipe( + withLatestFrom(this.store.pipe(select(getCurrentBasket))), + // use created order template data to dispatch addProduct action + concatMap(([orderTemplate, currentBasket]) => + concat( + ...currentBasket.lineItems.map(lineItem => + this.orderTemplateService.addProductToOrderTemplate( + orderTemplate.id, + lineItem.productSKU, + lineItem.quantity.value + ) ) + ).pipe( + last(), + concatMap(newOrderTemplate => [ + addBasketToNewOrderTemplateSuccess({ orderTemplate: newOrderTemplate }), + displaySuccessMessage({ + message: 'account.order_template.new_from_basket_confirm.heading', + messageParams: { 0: orderTemplate.title }, + }), + ]), + mapErrorToAction(addBasketToNewOrderTemplateFail) ) - ).pipe( - last(), - concatMap(newOrderTemplate => [ - new AddBasketToNewOrderTemplateSuccess({ orderTemplate: newOrderTemplate }), - new DisplaySuccessMessage({ - message: 'account.order_template.new_from_basket_confirm.heading', - messageParams: { 0: orderTemplate.title }, - }), - ]), - mapErrorToAction(AddBasketToNewOrderTemplateFail) ) ) - ) - ), - mapErrorToAction(CreateOrderTemplateFail) + ), + mapErrorToAction(createOrderTemplateFail) + ) ); - @Effect() - deleteOrderTemplate$ = this.actions$.pipe( - ofType(OrderTemplatesActionTypes.DeleteOrderTemplate), - mapToPayloadProperty('orderTemplateId'), - mergeMap(orderTemplateId => this.store.pipe(select(getOrderTemplateDetails, { id: orderTemplateId }))), - whenTruthy(), - map(orderTemplate => ({ orderTemplateId: orderTemplate.id, title: orderTemplate.title })), - mergeMap(({ orderTemplateId, title }) => - this.orderTemplateService.deleteOrderTemplate(orderTemplateId).pipe( - mergeMap(() => [ - new DeleteOrderTemplateSuccess({ orderTemplateId }), - new DisplaySuccessMessage({ - message: 'account.order_template.delete_order_template.confirmation', - messageParams: { 0: title }, - }), - ]), - mapErrorToAction(DeleteOrderTemplateFail) + deleteOrderTemplate$ = createEffect(() => + this.actions$.pipe( + ofType(deleteOrderTemplate), + mapToPayloadProperty('orderTemplateId'), + mergeMap(orderTemplateId => this.store.pipe(select(getOrderTemplateDetails, { id: orderTemplateId }))), + whenTruthy(), + map(orderTemplate => ({ orderTemplateId: orderTemplate.id, title: orderTemplate.title })), + mergeMap(({ orderTemplateId, title }) => + this.orderTemplateService.deleteOrderTemplate(orderTemplateId).pipe( + mergeMap(() => [ + deleteOrderTemplateSuccess({ orderTemplateId }), + displaySuccessMessage({ + message: 'account.order_template.delete_order_template.confirmation', + messageParams: { 0: title }, + }), + ]), + mapErrorToAction(deleteOrderTemplateFail) + ) ) ) ); - @Effect() - updateOrderTemplate$ = this.actions$.pipe( - ofType(OrderTemplatesActionTypes.UpdateOrderTemplate), - mapToPayloadProperty('orderTemplate'), - mergeMap((newOrderTemplate: OrderTemplate) => - this.orderTemplateService.updateOrderTemplate(newOrderTemplate).pipe( - mergeMap(orderTemplate => [ - new UpdateOrderTemplateSuccess({ orderTemplate }), - new DisplaySuccessMessage({ - message: 'account.order_templates.edit.confirmation', - messageParams: { 0: orderTemplate.title }, - }), - ]), - mapErrorToAction(UpdateOrderTemplateFail) + updateOrderTemplate$ = createEffect(() => + this.actions$.pipe( + ofType(updateOrderTemplate), + mapToPayloadProperty('orderTemplate'), + mergeMap((newOrderTemplate: OrderTemplate) => + this.orderTemplateService.updateOrderTemplate(newOrderTemplate).pipe( + mergeMap(orderTemplate => [ + updateOrderTemplateSuccess({ orderTemplate }), + displaySuccessMessage({ + message: 'account.order_templates.edit.confirmation', + messageParams: { 0: orderTemplate.title }, + }), + ]), + mapErrorToAction(updateOrderTemplateFail) + ) ) ) ); - @Effect() - addProductToOrderTemplate$ = this.actions$.pipe( - ofType(OrderTemplatesActionTypes.AddProductToOrderTemplate), - mapToPayload(), - mergeMap(payload => - this.orderTemplateService.addProductToOrderTemplate(payload.orderTemplateId, payload.sku, payload.quantity).pipe( - map(orderTemplate => new AddProductToOrderTemplateSuccess({ orderTemplate })), - mapErrorToAction(AddProductToOrderTemplateFail) + addProductToOrderTemplate$ = createEffect(() => + this.actions$.pipe( + ofType(addProductToOrderTemplate), + mapToPayload(), + mergeMap(payload => + this.orderTemplateService + .addProductToOrderTemplate(payload.orderTemplateId, payload.sku, payload.quantity) + .pipe( + map(orderTemplate => addProductToOrderTemplateSuccess({ orderTemplate })), + mapErrorToAction(addProductToOrderTemplateFail) + ) ) ) ); - @Effect() - addProductToNewOrderTemplate$ = this.actions$.pipe( - ofType(OrderTemplatesActionTypes.AddProductToNewOrderTemplate), - mapToPayload(), - mergeMap(payload => - this.orderTemplateService - .createOrderTemplate({ - title: payload.title, - }) - .pipe( - // use created order template data to dispatch addProduct action - mergeMap(orderTemplate => [ - new CreateOrderTemplateSuccess({ orderTemplate }), - new AddProductToOrderTemplate({ - orderTemplateId: orderTemplate.id, - sku: payload.sku, - quantity: payload.quantity, - }), - new SelectOrderTemplate({ id: orderTemplate.id }), - ]), - mapErrorToAction(CreateOrderTemplateFail) - ) + addProductToNewOrderTemplate$ = createEffect(() => + this.actions$.pipe( + ofType(addProductToNewOrderTemplate), + mapToPayload(), + mergeMap(payload => + this.orderTemplateService + .createOrderTemplate({ + title: payload.title, + }) + .pipe( + // use created order template data to dispatch addProduct action + mergeMap(orderTemplate => [ + createOrderTemplateSuccess({ orderTemplate }), + addProductToOrderTemplate({ + orderTemplateId: orderTemplate.id, + sku: payload.sku, + quantity: payload.quantity, + }), + selectOrderTemplate({ id: orderTemplate.id }), + ]), + mapErrorToAction(createOrderTemplateFail) + ) + ) ) ); - @Effect() - moveItemToOrderTemplate$ = this.actions$.pipe( - ofType(OrderTemplatesActionTypes.MoveItemToOrderTemplate), - mapToPayload(), - mergeMap(payload => { - if (!payload.target.id) { - return [ - new AddProductToNewOrderTemplate({ - title: payload.target.title, - sku: payload.target.sku, - quantity: payload.target.quantity, - }), - new RemoveItemFromOrderTemplate({ - orderTemplateId: payload.source.id, - sku: payload.target.sku, - }), - ]; - } else { - return [ - new AddProductToOrderTemplate({ - orderTemplateId: payload.target.id, - sku: payload.target.sku, - quantity: payload.target.quantity, - }), - new RemoveItemFromOrderTemplate({ - orderTemplateId: payload.source.id, - sku: payload.target.sku, - }), - ]; - } - }) + moveItemToOrderTemplate$ = createEffect(() => + this.actions$.pipe( + ofType(moveItemToOrderTemplate), + mapToPayload(), + mergeMap(payload => { + if (!payload.target.id) { + return [ + addProductToNewOrderTemplate({ + title: payload.target.title, + sku: payload.target.sku, + quantity: payload.target.quantity, + }), + removeItemFromOrderTemplate({ + orderTemplateId: payload.source.id, + sku: payload.target.sku, + }), + ]; + } else { + return [ + addProductToOrderTemplate({ + orderTemplateId: payload.target.id, + sku: payload.target.sku, + quantity: payload.target.quantity, + }), + removeItemFromOrderTemplate({ + orderTemplateId: payload.source.id, + sku: payload.target.sku, + }), + ]; + } + }) + ) ); - @Effect() - removeProductFromOrderTemplate$ = this.actions$.pipe( - ofType(OrderTemplatesActionTypes.RemoveItemFromOrderTemplate), - mapToPayload(), - mergeMap(payload => - this.orderTemplateService.removeProductFromOrderTemplate(payload.orderTemplateId, payload.sku).pipe( - map(orderTemplate => new RemoveItemFromOrderTemplateSuccess({ orderTemplate })), - mapErrorToAction(RemoveItemFromOrderTemplateFail) + removeProductFromOrderTemplate$ = createEffect(() => + this.actions$.pipe( + ofType(removeItemFromOrderTemplate), + mapToPayload(), + mergeMap(payload => + this.orderTemplateService.removeProductFromOrderTemplate(payload.orderTemplateId, payload.sku).pipe( + map(orderTemplate => removeItemFromOrderTemplateSuccess({ orderTemplate })), + mapErrorToAction(removeItemFromOrderTemplateFail) + ) ) ) ); - @Effect() - routeListenerForSelectedOrderTemplate$ = this.store.pipe( - select(selectRouteParam('orderTemplateName')), - distinctCompareWith(this.store.pipe(select(getSelectedOrderTemplateId))), - map(id => new SelectOrderTemplate({ id })) + routeListenerForSelectedOrderTemplate$ = createEffect(() => + this.store.pipe( + select(selectRouteParam('orderTemplateName')), + distinctCompareWith(this.store.pipe(select(getSelectedOrderTemplateId))), + map(id => selectOrderTemplate({ id })) + ) ); /** * Trigger LoadOrderTemplates action after LoginUserSuccess. */ - @Effect() - loadOrderTemplatesAfterLogin$ = this.store.pipe( - select(getUserAuthorized), - whenTruthy(), - mapTo(new LoadOrderTemplates()) + loadOrderTemplatesAfterLogin$ = createEffect(() => + this.store.pipe(select(getUserAuthorized), whenTruthy(), mapTo(loadOrderTemplates())) ); - @Effect() - setOrderTemplateBreadcrumb$ = this.store.pipe( - select(getSelectedOrderTemplateDetails), - whenTruthy(), - map( - orderTemplate => - new SetBreadcrumbData({ + setOrderTemplateBreadcrumb$ = createEffect(() => + this.store.pipe( + select(getSelectedOrderTemplateDetails), + whenTruthy(), + map(orderTemplate => + setBreadcrumbData({ breadcrumbData: [ { key: 'account.ordertemplates.link', link: '/account/order-templates' }, { text: orderTemplate.title }, ], }) + ) ) ); } diff --git a/src/app/extensions/order-templates/store/order-template/order-template.reducer.ts b/src/app/extensions/order-templates/store/order-template/order-template.reducer.ts index 523e30f29e..2cc4416442 100644 --- a/src/app/extensions/order-templates/store/order-template/order-template.reducer.ts +++ b/src/app/extensions/order-templates/store/order-template/order-template.reducer.ts @@ -1,10 +1,31 @@ import { EntityState, createEntityAdapter } from '@ngrx/entity'; +import { createReducer, on } from '@ngrx/store'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; +import { setLoadingOn } from 'ish-core/utils/ngrx-creators'; import { OrderTemplate } from '../../models/order-template/order-template.model'; -import { OrderTemplateAction, OrderTemplatesActionTypes } from './order-template.actions'; +import { + addBasketToNewOrderTemplate, + addBasketToNewOrderTemplateFail, + addBasketToNewOrderTemplateSuccess, + addProductToOrderTemplateSuccess, + createOrderTemplate, + createOrderTemplateFail, + createOrderTemplateSuccess, + deleteOrderTemplate, + deleteOrderTemplateFail, + deleteOrderTemplateSuccess, + loadOrderTemplates, + loadOrderTemplatesFail, + loadOrderTemplatesSuccess, + removeItemFromOrderTemplateSuccess, + selectOrderTemplate, + updateOrderTemplate, + updateOrderTemplateFail, + updateOrderTemplateSuccess, +} from './order-template.actions'; export interface OrderTemplateState extends EntityState { loading: boolean; @@ -22,23 +43,22 @@ export const initialState: OrderTemplateState = orderTemplateAdapter.getInitialS error: undefined, }); -export function orderTemplateReducer(state = initialState, action: OrderTemplateAction): OrderTemplateState { - switch (action.type) { - case OrderTemplatesActionTypes.LoadOrderTemplates: - case OrderTemplatesActionTypes.CreateOrderTemplate: - case OrderTemplatesActionTypes.AddBasketToNewOrderTemplate: - case OrderTemplatesActionTypes.DeleteOrderTemplate: - case OrderTemplatesActionTypes.UpdateOrderTemplate: { - return { - ...state, - loading: true, - }; - } - case OrderTemplatesActionTypes.LoadOrderTemplatesFail: - case OrderTemplatesActionTypes.DeleteOrderTemplateFail: - case OrderTemplatesActionTypes.CreateOrderTemplateFail: - case OrderTemplatesActionTypes.AddBasketToNewOrderTemplateFail: - case OrderTemplatesActionTypes.UpdateOrderTemplateFail: { +export const orderTemplateReducer = createReducer( + initialState, + setLoadingOn( + loadOrderTemplates, + createOrderTemplate, + addBasketToNewOrderTemplate, + deleteOrderTemplate, + updateOrderTemplate + ), + on( + loadOrderTemplatesFail, + deleteOrderTemplateFail, + createOrderTemplateFail, + addBasketToNewOrderTemplateFail, + updateOrderTemplateFail, + (state: OrderTemplateState, action) => { const { error } = action.payload; return { ...state, @@ -47,19 +67,21 @@ export function orderTemplateReducer(state = initialState, action: OrderTemplate selected: undefined, }; } - - case OrderTemplatesActionTypes.LoadOrderTemplatesSuccess: { - const { orderTemplates } = action.payload; - return orderTemplateAdapter.setAll(orderTemplates, { - ...state, - loading: false, - }); - } - case OrderTemplatesActionTypes.AddBasketToNewOrderTemplateSuccess: - case OrderTemplatesActionTypes.CreateOrderTemplateSuccess: - case OrderTemplatesActionTypes.UpdateOrderTemplateSuccess: - case OrderTemplatesActionTypes.AddProductToOrderTemplateSuccess: - case OrderTemplatesActionTypes.RemoveItemFromOrderTemplateSuccess: { + ), + on(loadOrderTemplatesSuccess, (state: OrderTemplateState, action) => { + const { orderTemplates } = action.payload; + return orderTemplateAdapter.setAll(orderTemplates, { + ...state, + loading: false, + }); + }), + on( + addBasketToNewOrderTemplateSuccess, + createOrderTemplateSuccess, + updateOrderTemplateSuccess, + addProductToOrderTemplateSuccess, + removeItemFromOrderTemplateSuccess, + (state: OrderTemplateState, action) => { const { orderTemplate } = action.payload; return orderTemplateAdapter.upsertOne(orderTemplate, { @@ -67,23 +89,19 @@ export function orderTemplateReducer(state = initialState, action: OrderTemplate loading: false, }); } - - case OrderTemplatesActionTypes.DeleteOrderTemplateSuccess: { - const { orderTemplateId } = action.payload; - return orderTemplateAdapter.removeOne(orderTemplateId, { - ...state, - loading: false, - }); - } - - case OrderTemplatesActionTypes.SelectOrderTemplate: { - const { id } = action.payload; - return { - ...state, - selected: id, - }; - } - } - - return state; -} + ), + on(deleteOrderTemplateSuccess, (state: OrderTemplateState, action) => { + const { orderTemplateId } = action.payload; + return orderTemplateAdapter.removeOne(orderTemplateId, { + ...state, + loading: false, + }); + }), + on(selectOrderTemplate, (state: OrderTemplateState, action) => { + const { id } = action.payload; + return { + ...state, + selected: id, + }; + }) +); diff --git a/src/app/extensions/order-templates/store/order-template/order-template.selectors.spec.ts b/src/app/extensions/order-templates/store/order-template/order-template.selectors.spec.ts index bbb841db48..09ba495a46 100644 --- a/src/app/extensions/order-templates/store/order-template/order-template.selectors.spec.ts +++ b/src/app/extensions/order-templates/store/order-template/order-template.selectors.spec.ts @@ -7,19 +7,19 @@ import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ng import { OrderTemplatesStoreModule } from '../order-templates-store.module'; import { - CreateOrderTemplate, - CreateOrderTemplateFail, - CreateOrderTemplateSuccess, - DeleteOrderTemplate, - DeleteOrderTemplateFail, - DeleteOrderTemplateSuccess, - LoadOrderTemplates, - LoadOrderTemplatesFail, - LoadOrderTemplatesSuccess, - SelectOrderTemplate, - UpdateOrderTemplate, - UpdateOrderTemplateFail, - UpdateOrderTemplateSuccess, + createOrderTemplate, + createOrderTemplateFail, + createOrderTemplateSuccess, + deleteOrderTemplate, + deleteOrderTemplateFail, + deleteOrderTemplateSuccess, + loadOrderTemplates, + loadOrderTemplatesFail, + loadOrderTemplatesSuccess, + selectOrderTemplate, + updateOrderTemplate, + updateOrderTemplateFail, + updateOrderTemplateSuccess, } from './order-template.actions'; import { getAllOrderTemplates, @@ -71,7 +71,7 @@ describe('Order Template Selectors', () => { describe('loading order templates', () => { describe('LoadOrderTemplates', () => { - const loadOrderTemplateAction = new LoadOrderTemplates(); + const loadOrderTemplateAction = loadOrderTemplates(); beforeEach(() => { store$.dispatch(loadOrderTemplateAction); @@ -83,7 +83,7 @@ describe('Order Template Selectors', () => { }); describe('LoadOrderTemplatesSuccess', () => { - const loadOrderTemplateSuccessAction = new LoadOrderTemplatesSuccess({ orderTemplates }); + const loadOrderTemplateSuccessAction = loadOrderTemplatesSuccess({ orderTemplates }); beforeEach(() => { store$.dispatch(loadOrderTemplateSuccessAction); @@ -99,7 +99,7 @@ describe('Order Template Selectors', () => { }); describe('LoadOrderTemplatesFail', () => { - const loadOrderTemplatesFailAction = new LoadOrderTemplatesFail({ error: { message: 'invalid' } as HttpError }); + const loadOrderTemplatesFailAction = loadOrderTemplatesFail({ error: { message: 'invalid' } as HttpError }); beforeEach(() => { store$.dispatch(loadOrderTemplatesFailAction); @@ -117,7 +117,7 @@ describe('Order Template Selectors', () => { describe('create a order template', () => { describe('CreateOrderTemplate', () => { - const createOrderTemplateAction = new CreateOrderTemplate({ + const createOrderTemplateAction = createOrderTemplate({ orderTemplate: { title: 'create title', }, @@ -133,7 +133,7 @@ describe('Order Template Selectors', () => { }); describe('CreateOrderTemplateSuccess', () => { - const createOrderTemplateSuccessAction = new CreateOrderTemplateSuccess({ orderTemplate: orderTemplates[0] }); + const createOrderTemplateSuccessAction = createOrderTemplateSuccess({ orderTemplate: orderTemplates[0] }); beforeEach(() => { store$.dispatch(createOrderTemplateSuccessAction); @@ -149,7 +149,7 @@ describe('Order Template Selectors', () => { }); describe('CreateOrderTemplatetFail', () => { - const createOrderTemplateFailAction = new CreateOrderTemplateFail({ error: { message: 'invalid' } as HttpError }); + const createOrderTemplateFailAction = createOrderTemplateFail({ error: { message: 'invalid' } as HttpError }); beforeEach(() => { store$.dispatch(createOrderTemplateFailAction); @@ -167,7 +167,7 @@ describe('Order Template Selectors', () => { describe('delete a order template', () => { describe('DeleteOrderTemplate', () => { - const deleteOrderTemplateAction = new DeleteOrderTemplate({ orderTemplateId: 'id' }); + const deleteOrderTemplateAction = deleteOrderTemplate({ orderTemplateId: 'id' }); beforeEach(() => { store$.dispatch(deleteOrderTemplateAction); @@ -179,8 +179,8 @@ describe('Order Template Selectors', () => { }); describe('DeleteOrderTemplateSuccess', () => { - const loadOrderTemplateSuccessAction = new LoadOrderTemplatesSuccess({ orderTemplates }); - const deleteOrderTemplateSuccessAction = new DeleteOrderTemplateSuccess({ + const loadOrderTemplateSuccessAction = loadOrderTemplatesSuccess({ orderTemplates }); + const deleteOrderTemplateSuccessAction = deleteOrderTemplateSuccess({ orderTemplateId: orderTemplates[0].id, }); @@ -199,7 +199,7 @@ describe('Order Template Selectors', () => { }); describe('DeleteOrderTemplateFail', () => { - const deleteOrderTemplateFailAction = new DeleteOrderTemplateFail({ error: { message: 'invalid' } as HttpError }); + const deleteOrderTemplateFailAction = deleteOrderTemplateFail({ error: { message: 'invalid' } as HttpError }); beforeEach(() => { store$.dispatch(deleteOrderTemplateFailAction); @@ -217,7 +217,7 @@ describe('Order Template Selectors', () => { describe('updating a order template', () => { describe('UpdateOrderTemplate', () => { - const updateOrderTemplateAction = new UpdateOrderTemplate({ orderTemplate: orderTemplates[0] }); + const updateOrderTemplateAction = updateOrderTemplate({ orderTemplate: orderTemplates[0] }); beforeEach(() => { store$.dispatch(updateOrderTemplateAction); @@ -233,10 +233,10 @@ describe('Order Template Selectors', () => { ...orderTemplates[0], title: 'new title', }; - const updateOrderTemplateSuccessAction = new UpdateOrderTemplateSuccess({ + const updateOrderTemplateSuccessAction = updateOrderTemplateSuccess({ orderTemplate: updated, }); - const loadOrderTemplateSuccess = new LoadOrderTemplatesSuccess({ orderTemplates }); + const loadOrderTemplateSuccess = loadOrderTemplatesSuccess({ orderTemplates }); it('should set loading to false', () => { store$.dispatch(updateOrderTemplateSuccessAction); @@ -253,7 +253,7 @@ describe('Order Template Selectors', () => { }); describe('UpdateOrderTemplateFail', () => { - const updateOrderTemplateFailAction = new UpdateOrderTemplateFail({ error: { message: 'invalid' } as HttpError }); + const updateOrderTemplateFailAction = updateOrderTemplateFail({ error: { message: 'invalid' } as HttpError }); beforeEach(() => { store$.dispatch(updateOrderTemplateFailAction); @@ -270,8 +270,8 @@ describe('Order Template Selectors', () => { }); describe('Get Selected Order Template', () => { - const loadOrderTemplatesSuccessActions = new LoadOrderTemplatesSuccess({ orderTemplates }); - const selectOrderTemplateAction = new SelectOrderTemplate({ id: orderTemplates[1].id }); + const loadOrderTemplatesSuccessActions = loadOrderTemplatesSuccess({ orderTemplates }); + const selectOrderTemplateAction = selectOrderTemplate({ id: orderTemplates[1].id }); beforeEach(() => { store$.dispatch(loadOrderTemplatesSuccessActions); @@ -288,7 +288,7 @@ describe('Order Template Selectors', () => { }); describe('Get Order Template Details', () => { - const loadOrderTemplateSuccessActions = new LoadOrderTemplatesSuccess({ orderTemplates }); + const loadOrderTemplateSuccessActions = loadOrderTemplatesSuccess({ orderTemplates }); beforeEach(() => { store$.dispatch(loadOrderTemplateSuccessActions); diff --git a/src/app/extensions/quoting/facades/quoting.facade.ts b/src/app/extensions/quoting/facades/quoting.facade.ts index 28a4af7339..44af3f1860 100644 --- a/src/app/extensions/quoting/facades/quoting.facade.ts +++ b/src/app/extensions/quoting/facades/quoting.facade.ts @@ -8,34 +8,34 @@ import { LineItemUpdate } from 'ish-core/models/line-item-update/line-item-updat import { QuoteRequest } from '../models/quote-request/quote-request.model'; import { Quote } from '../models/quote/quote.model'; import { - AddQuoteToBasket, - CreateQuoteRequestFromQuote, - DeleteQuote, - LoadQuotes, - RejectQuote, - ResetQuoteError, + addQuoteToBasket, + createQuoteRequestFromQuote, + deleteQuote, getCurrentQuotes, getQuoteError, getQuoteLoading, getSelectedQuoteWithProducts, + loadQuotes, + rejectQuote, + resetQuoteError, } from '../store/quote'; import { - AddBasketToQuoteRequest, - AddProductToQuoteRequest, - CreateQuoteRequestFromQuoteRequest, - DeleteItemFromQuoteRequest, - DeleteQuoteRequest, - LoadQuoteRequests, - SelectQuoteRequest, - SubmitQuoteRequest, - UpdateQuoteRequest, - UpdateQuoteRequestItems, - UpdateSubmitQuoteRequest, + addBasketToQuoteRequest, + addProductToQuoteRequest, + createQuoteRequestFromQuoteRequest, + deleteItemFromQuoteRequest, + deleteQuoteRequest, getActiveQuoteRequestWithProducts, getCurrentQuoteRequests, getQuoteRequestError, getQuoteRequestLoading, getSelectedQuoteRequestWithProducts, + loadQuoteRequests, + selectQuoteRequest, + submitQuoteRequest, + updateQuoteRequest, + updateQuoteRequestItems, + updateSubmitQuoteRequest, } from '../store/quote-request'; const getQuotesAndQuoteRequests = createSelector(getCurrentQuotes, getCurrentQuoteRequests, (quotes, quoteRequests): ( @@ -55,23 +55,23 @@ export class QuotingFacade { quoteError$ = this.store.pipe(select(getQuoteError)); private loadQuotes() { - this.store.dispatch(new LoadQuotes()); + this.store.dispatch(loadQuotes()); } rejectQuote() { - this.store.dispatch(new RejectQuote()); + this.store.dispatch(rejectQuote()); } private deleteQuote(id: string) { - this.store.dispatch(new DeleteQuote({ id })); + this.store.dispatch(deleteQuote({ id })); } addQuoteToBasket(quoteId: string) { - this.store.dispatch(new AddQuoteToBasket({ quoteId })); + this.store.dispatch(addQuoteToBasket({ quoteId })); } resetQuoteError() { - this.store.dispatch(new ResetQuoteError()); + this.store.dispatch(resetQuoteError()); } // QUOTE REQUEST @@ -82,47 +82,47 @@ export class QuotingFacade { activeQuoteRequest$ = this.store.pipe(select(getActiveQuoteRequestWithProducts)); private loadQuoteRequests() { - this.store.dispatch(new LoadQuoteRequests()); + this.store.dispatch(loadQuoteRequests()); } selectQuoteRequest(id: string) { - this.store.dispatch(new SelectQuoteRequest({ id })); + this.store.dispatch(selectQuoteRequest({ id })); } updateQuoteRequest(payload: { displayName?: string; description?: string }) { - this.store.dispatch(new UpdateQuoteRequest(payload)); + this.store.dispatch(updateQuoteRequest(payload)); } deleteQuoteRequest(id: string) { - this.store.dispatch(new DeleteQuoteRequest({ id })); + this.store.dispatch(deleteQuoteRequest({ id })); } submitQuoteRequest() { - this.store.dispatch(new SubmitQuoteRequest()); + this.store.dispatch(submitQuoteRequest()); } updateSubmitQuoteRequest(payload: { displayName?: string; description?: string }) { - this.store.dispatch(new UpdateSubmitQuoteRequest(payload)); + this.store.dispatch(updateSubmitQuoteRequest(payload)); } copyQuoteRequest(preventRedirect?: boolean) { - this.store.dispatch(new CreateQuoteRequestFromQuoteRequest({ redirect: !preventRedirect })); + this.store.dispatch(createQuoteRequestFromQuoteRequest({ redirect: !preventRedirect })); } updateQuoteRequestItem(update: LineItemUpdate) { - this.store.dispatch(new UpdateQuoteRequestItems({ lineItemUpdates: [update] })); + this.store.dispatch(updateQuoteRequestItems({ lineItemUpdates: [update] })); } deleteQuoteRequestItem(itemId: string) { - this.store.dispatch(new DeleteItemFromQuoteRequest({ itemId })); + this.store.dispatch(deleteItemFromQuoteRequest({ itemId })); } addBasketToQuoteRequest() { - this.store.dispatch(new AddBasketToQuoteRequest()); + this.store.dispatch(addBasketToQuoteRequest()); } addProductToQuoteRequest(sku: string, quantity: number) { - this.store.dispatch(new AddProductToQuoteRequest({ sku, quantity })); + this.store.dispatch(addProductToQuoteRequest({ sku, quantity })); } // QUOTE AND QUOTE REQUEST @@ -146,6 +146,6 @@ export class QuotingFacade { } createQuoteRequestFromQuote() { - this.store.dispatch(new CreateQuoteRequestFromQuote()); + this.store.dispatch(createQuoteRequestFromQuote()); } } diff --git a/src/app/extensions/quoting/services/quote-request/quote-request.service.spec.ts b/src/app/extensions/quoting/services/quote-request/quote-request.service.spec.ts index 1820df8e35..30c4500d7e 100644 --- a/src/app/extensions/quoting/services/quote-request/quote-request.service.spec.ts +++ b/src/app/extensions/quoting/services/quote-request/quote-request.service.spec.ts @@ -10,13 +10,13 @@ import { User } from 'ish-core/models/user/user.model'; import { ApiService } from 'ish-core/services/api/api.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; -import { LoadCompanyUserSuccess, LoginUserSuccess, LogoutUser } from 'ish-core/store/customer/user'; +import { loadCompanyUserSuccess, loginUserSuccess, logoutUser } from 'ish-core/store/customer/user'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { QuoteRequestItemData } from '../../models/quote-request-item/quote-request-item.interface'; import { QuoteRequestData } from '../../models/quote-request/quote-request.interface'; import { QuoteRequest } from '../../models/quote-request/quote-request.model'; -import { LoadQuoteRequestsSuccess } from '../../store/quote-request'; +import { loadQuoteRequestsSuccess } from '../../store/quote-request'; import { QuotingStoreModule } from '../../store/quoting-store.module'; import { QuoteRequestService } from './quote-request.service'; @@ -110,8 +110,8 @@ describe('Quote Request Service', () => { beforeEach(() => { when(apiService.get(anything())).thenReturn(of({ elements: [] })); - store$.dispatch(new LoginUserSuccess({ customer })); - store$.dispatch(new LoadCompanyUserSuccess({ user })); + store$.dispatch(loginUserSuccess({ customer })); + store$.dispatch(loadCompanyUserSuccess({ user })); }); it('should complete after first successful result', () => { @@ -119,7 +119,7 @@ describe('Quote Request Service', () => { verify(apiService.get(anything())).once(); - store$.dispatch(new LoadCompanyUserSuccess({ user: { ...user, firstName: 'test' } as User })); + store$.dispatch(loadCompanyUserSuccess({ user: { ...user, firstName: 'test' } as User })); verify(apiService.get(anything())).once(); @@ -142,7 +142,7 @@ describe('Quote Request Service', () => { verify(apiService.get(anything())).thrice(); expect(subscription3.closed).toBeTrue(); - store$.dispatch(new LogoutUser()); + store$.dispatch(logoutUser()); const subscription4 = quoteRequestService.getQuoteRequests().subscribe(fail); @@ -153,8 +153,8 @@ describe('Quote Request Service', () => { describe('when logged in', () => { beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer, user })); - store$.dispatch(new LoadCompanyUserSuccess({ user })); + store$.dispatch(loginUserSuccess({ customer, user })); + store$.dispatch(loadCompanyUserSuccess({ user })); }); it("should get quoteRequests data when 'getQuoteRequests' is called", done => { @@ -243,7 +243,7 @@ describe('Quote Request Service', () => { it("should post new item to quote request when 'addProductToQuoteRequest' is called", done => { store$.dispatch( - new LoadQuoteRequestsSuccess({ + loadQuoteRequestsSuccess({ quoteRequests: [ { id: 'QRID', diff --git a/src/app/extensions/quoting/services/quote/quote.service.spec.ts b/src/app/extensions/quoting/services/quote/quote.service.spec.ts index 20fdf9137c..9468681192 100644 --- a/src/app/extensions/quoting/services/quote/quote.service.spec.ts +++ b/src/app/extensions/quoting/services/quote/quote.service.spec.ts @@ -9,7 +9,7 @@ import { User } from 'ish-core/models/user/user.model'; import { ApiService } from 'ish-core/services/api/api.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; -import { LoadCompanyUserSuccess, LoginUserSuccess } from 'ish-core/store/customer/user'; +import { loadCompanyUserSuccess, loginUserSuccess } from 'ish-core/store/customer/user'; import { QuoteRequestItemData } from '../../models/quote-request-item/quote-request-item.interface'; import { QuoteRequestItem } from '../../models/quote-request-item/quote-request-item.model'; @@ -67,8 +67,8 @@ describe('Quote Service', () => { describe('when logged in', () => { beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer, user })); - store$.dispatch(new LoadCompanyUserSuccess({ user })); + store$.dispatch(loginUserSuccess({ customer, user })); + store$.dispatch(loadCompanyUserSuccess({ user })); }); it("should get quotes data when 'getQuotes' is called", done => { diff --git a/src/app/extensions/quoting/store/quote-request/quote-request.actions.ts b/src/app/extensions/quoting/store/quote-request/quote-request.actions.ts index 8d201771af..c6f66df155 100644 --- a/src/app/extensions/quoting/store/quote-request/quote-request.actions.ts +++ b/src/app/extensions/quoting/store/quote-request/quote-request.actions.ts @@ -1,254 +1,132 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; -import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { LineItemUpdate } from 'ish-core/models/line-item-update/line-item-update.model'; +import { httpError, payload } from 'ish-core/utils/ngrx-creators'; import { QuoteLineItemResult } from '../../models/quote-line-item-result/quote-line-item-result.model'; import { QuoteRequestItem } from '../../models/quote-request-item/quote-request-item.model'; import { QuoteRequestData } from '../../models/quote-request/quote-request.interface'; -export enum QuoteRequestActionTypes { - SelectQuoteRequest = '[Quote] Select QuoteRequest', - LoadQuoteRequests = '[Quote Internal] Load QuoteRequests', - LoadQuoteRequestsFail = '[Quote API] Load QuoteRequests Fail', - LoadQuoteRequestsSuccess = '[Quote API] Load QuoteRequests Success', - AddQuoteRequest = '[Quote] Add Quote Request', - AddQuoteRequestFail = '[Quote API] Add Quote Request Fail', - AddQuoteRequestSuccess = '[Quote API] Add Quote Request Success', - UpdateQuoteRequest = '[Quote] Update Quote Request', - UpdateQuoteRequestFail = '[Quote API] Update Quote Request Fail', - UpdateQuoteRequestSuccess = '[Quote API] Update Quote Request Success', - DeleteQuoteRequest = '[Quote] Delete Quote Request', - DeleteQuoteRequestFail = '[Quote API] Delete Quote Request Fail', - DeleteQuoteRequestSuccess = '[Quote API] Delete Quote Request Success', - SubmitQuoteRequest = '[Quote] Submit Quote Request', - SubmitQuoteRequestFail = '[Quote API] Submit Quote Request Fail', - SubmitQuoteRequestSuccess = '[Quote API] Submit Quote Request Success', - UpdateSubmitQuoteRequest = '[Quote] Update and Submit Quote Request', - CreateQuoteRequestFromQuoteRequest = '[Quote] Create Quote Request from Quote Request', - CreateQuoteRequestFromQuoteRequestFail = '[Quote API] Create Quote Request from Quote Request Fail', - CreateQuoteRequestFromQuoteRequestSuccess = '[Quote API] Create Quote Request from Quote Request Success', - LoadQuoteRequestItems = '[Quote] Load QuoteRequestItems', - LoadQuoteRequestItemsFail = '[Quote API] Load QuoteRequestItems Fail', - LoadQuoteRequestItemsSuccess = '[Quote API] Load QuoteRequestItems Success', - AddProductToQuoteRequest = '[Quote] Add Item to Quote Request', - AddProductToQuoteRequestFail = '[Quote API] Add Item to Quote Request Fail', - AddProductToQuoteRequestSuccess = '[Quote API] Add Item to Quote Request Success', - AddBasketToQuoteRequest = '[Quote] Add Basket to Quote Request', - AddBasketToQuoteRequestFail = '[Quote API] Add Basket to Quote Request Fail', - AddBasketToQuoteRequestSuccess = '[Quote API] Add Basket to Quote Request Success', - UpdateQuoteRequestItems = '[Quote] Update Quote Request Items', - UpdateQuoteRequestItemsFail = '[Quote API] Update Quote Request Items Fail', - UpdateQuoteRequestItemsSuccess = '[Quote API] Update Quote Request Items Success', - DeleteItemFromQuoteRequest = '[Quote] Delete Item from Quote Request', - DeleteItemFromQuoteRequestFail = '[Quote API] Delete Item from Quote Request Fail', - DeleteItemFromQuoteRequestSuccess = '[Quote API] Delete Item from Quote Request Success', -} - -export class SelectQuoteRequest implements Action { - readonly type = QuoteRequestActionTypes.SelectQuoteRequest; - constructor(public payload: { id: string }) {} -} - -export class LoadQuoteRequests implements Action { - readonly type = QuoteRequestActionTypes.LoadQuoteRequests; -} - -export class LoadQuoteRequestsFail implements Action { - readonly type = QuoteRequestActionTypes.LoadQuoteRequestsFail; - constructor(public payload: { error: HttpError }) {} -} - -export class LoadQuoteRequestsSuccess implements Action { - readonly type = QuoteRequestActionTypes.LoadQuoteRequestsSuccess; - constructor(public payload: { quoteRequests: QuoteRequestData[] }) {} -} - -export class AddQuoteRequest implements Action { - readonly type = QuoteRequestActionTypes.AddQuoteRequest; -} - -export class AddQuoteRequestFail implements Action { - readonly type = QuoteRequestActionTypes.AddQuoteRequestFail; - constructor(public payload: { error: HttpError }) {} -} - -export class AddQuoteRequestSuccess implements Action { - readonly type = QuoteRequestActionTypes.AddQuoteRequestSuccess; - constructor(public payload: { id: string }) {} -} - -export class UpdateQuoteRequest implements Action { - readonly type = QuoteRequestActionTypes.UpdateQuoteRequest; - constructor(public payload: { displayName?: string; description?: string }) {} -} - -export class UpdateQuoteRequestFail implements Action { - readonly type = QuoteRequestActionTypes.UpdateQuoteRequestFail; - constructor(public payload: { error: HttpError }) {} -} - -export class UpdateQuoteRequestSuccess implements Action { - readonly type = QuoteRequestActionTypes.UpdateQuoteRequestSuccess; - constructor(public payload: { quoteRequest: QuoteRequestData }) {} -} - -export class DeleteQuoteRequest implements Action { - readonly type = QuoteRequestActionTypes.DeleteQuoteRequest; - constructor(public payload: { id: string }) {} -} - -export class DeleteQuoteRequestFail implements Action { - readonly type = QuoteRequestActionTypes.DeleteQuoteRequestFail; - constructor(public payload: { error: HttpError }) {} -} - -export class DeleteQuoteRequestSuccess implements Action { - readonly type = QuoteRequestActionTypes.DeleteQuoteRequestSuccess; - constructor(public payload: { id: string }) {} -} - -export class SubmitQuoteRequest implements Action { - readonly type = QuoteRequestActionTypes.SubmitQuoteRequest; -} - -export class SubmitQuoteRequestFail implements Action { - readonly type = QuoteRequestActionTypes.SubmitQuoteRequestFail; - constructor(public payload: { error: HttpError }) {} -} - -export class SubmitQuoteRequestSuccess implements Action { - readonly type = QuoteRequestActionTypes.SubmitQuoteRequestSuccess; - constructor(public payload: { id: string }) {} -} - -export class UpdateSubmitQuoteRequest implements Action { - readonly type = QuoteRequestActionTypes.UpdateSubmitQuoteRequest; - constructor(public payload: { displayName?: string; description?: string }) {} -} - -export class CreateQuoteRequestFromQuoteRequest implements Action { - readonly type = QuoteRequestActionTypes.CreateQuoteRequestFromQuoteRequest; - constructor(public payload: { redirect?: boolean }) {} -} - -export class CreateQuoteRequestFromQuoteRequestFail implements Action { - readonly type = QuoteRequestActionTypes.CreateQuoteRequestFromQuoteRequestFail; - constructor(public payload: { error: HttpError }) {} -} - -export class CreateQuoteRequestFromQuoteRequestSuccess implements Action { - readonly type = QuoteRequestActionTypes.CreateQuoteRequestFromQuoteRequestSuccess; - constructor(public payload: { quoteLineItemResult: QuoteLineItemResult }) {} -} - -export class LoadQuoteRequestItems implements Action { - readonly type = QuoteRequestActionTypes.LoadQuoteRequestItems; - constructor(public payload: { id: string }) {} -} - -export class LoadQuoteRequestItemsFail implements Action { - readonly type = QuoteRequestActionTypes.LoadQuoteRequestItemsFail; - constructor(public payload: { error: HttpError }) {} -} - -export class LoadQuoteRequestItemsSuccess implements Action { - readonly type = QuoteRequestActionTypes.LoadQuoteRequestItemsSuccess; - constructor(public payload: { quoteRequestItems: QuoteRequestItem[] }) {} -} - -export class AddProductToQuoteRequest implements Action { - readonly type = QuoteRequestActionTypes.AddProductToQuoteRequest; - constructor(public payload: { sku: string; quantity: number }) {} -} - -export class AddProductToQuoteRequestFail implements Action { - readonly type = QuoteRequestActionTypes.AddProductToQuoteRequestFail; - constructor(public payload: { error: HttpError }) {} -} - -export class AddProductToQuoteRequestSuccess implements Action { - readonly type = QuoteRequestActionTypes.AddProductToQuoteRequestSuccess; - constructor(public payload: { id: string }) {} -} - -export class AddBasketToQuoteRequest implements Action { - readonly type = QuoteRequestActionTypes.AddBasketToQuoteRequest; -} - -export class AddBasketToQuoteRequestFail implements Action { - readonly type = QuoteRequestActionTypes.AddBasketToQuoteRequestFail; - constructor(public payload: { error: HttpError }) {} -} - -export class AddBasketToQuoteRequestSuccess implements Action { - readonly type = QuoteRequestActionTypes.AddBasketToQuoteRequestSuccess; - constructor(public payload: { id: string }) {} -} - -export class UpdateQuoteRequestItems implements Action { - readonly type = QuoteRequestActionTypes.UpdateQuoteRequestItems; - constructor(public payload: { lineItemUpdates: LineItemUpdate[] }) {} -} - -export class UpdateQuoteRequestItemsFail implements Action { - readonly type = QuoteRequestActionTypes.UpdateQuoteRequestItemsFail; - constructor(public payload: { error: HttpError }) {} -} - -export class UpdateQuoteRequestItemsSuccess implements Action { - readonly type = QuoteRequestActionTypes.UpdateQuoteRequestItemsSuccess; - constructor(public payload: { itemIds: string[] }) {} -} - -export class DeleteItemFromQuoteRequest implements Action { - readonly type = QuoteRequestActionTypes.DeleteItemFromQuoteRequest; - constructor(public payload: { itemId: string }) {} -} - -export class DeleteItemFromQuoteRequestFail implements Action { - readonly type = QuoteRequestActionTypes.DeleteItemFromQuoteRequestFail; - constructor(public payload: { error: HttpError }) {} -} - -export class DeleteItemFromQuoteRequestSuccess implements Action { - readonly type = QuoteRequestActionTypes.DeleteItemFromQuoteRequestSuccess; - constructor(public payload: { id: string }) {} -} - -export type QuoteAction = - | SelectQuoteRequest - | LoadQuoteRequests - | LoadQuoteRequestsFail - | LoadQuoteRequestsSuccess - | AddQuoteRequest - | AddQuoteRequestFail - | AddQuoteRequestSuccess - | UpdateQuoteRequest - | UpdateQuoteRequestFail - | UpdateQuoteRequestSuccess - | DeleteQuoteRequest - | DeleteQuoteRequestFail - | DeleteQuoteRequestSuccess - | SubmitQuoteRequest - | SubmitQuoteRequestFail - | SubmitQuoteRequestSuccess - | UpdateSubmitQuoteRequest - | CreateQuoteRequestFromQuoteRequest - | CreateQuoteRequestFromQuoteRequestFail - | CreateQuoteRequestFromQuoteRequestSuccess - | LoadQuoteRequestItems - | LoadQuoteRequestItemsFail - | LoadQuoteRequestItemsSuccess - | AddProductToQuoteRequest - | AddProductToQuoteRequestFail - | AddProductToQuoteRequestSuccess - | AddBasketToQuoteRequest - | AddBasketToQuoteRequestFail - | AddBasketToQuoteRequestSuccess - | UpdateQuoteRequestItems - | UpdateQuoteRequestItemsFail - | UpdateQuoteRequestItemsSuccess - | DeleteItemFromQuoteRequest - | DeleteItemFromQuoteRequestFail - | DeleteItemFromQuoteRequestSuccess; +export const selectQuoteRequest = createAction('[Quote] Select QuoteRequest', payload<{ id: string }>()); + +export const loadQuoteRequests = createAction('[Quote Internal] Load QuoteRequests'); + +export const loadQuoteRequestsFail = createAction('[Quote API] Load QuoteRequests Fail', httpError()); + +export const loadQuoteRequestsSuccess = createAction( + '[Quote API] Load QuoteRequests Success', + payload<{ quoteRequests: QuoteRequestData[] }>() +); + +export const addQuoteRequest = createAction('[Quote] Add Quote Request'); + +export const addQuoteRequestFail = createAction('[Quote API] Add Quote Request Fail', httpError()); + +export const addQuoteRequestSuccess = createAction('[Quote API] Add Quote Request Success', payload<{ id: string }>()); + +export const updateQuoteRequest = createAction( + '[Quote] Update Quote Request', + payload<{ displayName?: string; description?: string }>() +); + +export const updateQuoteRequestFail = createAction('[Quote API] Update Quote Request Fail', httpError()); + +export const updateQuoteRequestSuccess = createAction( + '[Quote API] Update Quote Request Success', + payload<{ quoteRequest: QuoteRequestData }>() +); + +export const deleteQuoteRequest = createAction('[Quote] Delete Quote Request', payload<{ id: string }>()); + +export const deleteQuoteRequestFail = createAction('[Quote API] Delete Quote Request Fail', httpError()); + +export const deleteQuoteRequestSuccess = createAction( + '[Quote API] Delete Quote Request Success', + payload<{ id: string }>() +); + +export const submitQuoteRequest = createAction('[Quote] Submit Quote Request'); + +export const submitQuoteRequestFail = createAction('[Quote API] Submit Quote Request Fail', httpError()); + +export const submitQuoteRequestSuccess = createAction( + '[Quote API] Submit Quote Request Success', + payload<{ id: string }>() +); + +export const updateSubmitQuoteRequest = createAction( + '[Quote] Update and Submit Quote Request', + payload<{ displayName?: string; description?: string }>() +); + +export const createQuoteRequestFromQuoteRequest = createAction( + '[Quote] Create Quote Request from Quote Request', + payload<{ redirect?: boolean }>() +); + +export const createQuoteRequestFromQuoteRequestFail = createAction( + '[Quote API] Create Quote Request from Quote Request Fail', + httpError() +); + +export const createQuoteRequestFromQuoteRequestSuccess = createAction( + '[Quote API] Create Quote Request from Quote Request Success', + payload<{ quoteLineItemResult: QuoteLineItemResult }>() +); + +export const loadQuoteRequestItems = createAction('[Quote] Load QuoteRequestItems', payload<{ id: string }>()); + +export const loadQuoteRequestItemsFail = createAction('[Quote API] Load QuoteRequestItems Fail', httpError()); + +export const loadQuoteRequestItemsSuccess = createAction( + '[Quote API] Load QuoteRequestItems Success', + payload<{ quoteRequestItems: QuoteRequestItem[] }>() +); + +export const addProductToQuoteRequest = createAction( + '[Quote] Add Item to Quote Request', + payload<{ sku: string; quantity: number }>() +); + +export const addProductToQuoteRequestFail = createAction('[Quote API] Add Item to Quote Request Fail', httpError()); + +export const addProductToQuoteRequestSuccess = createAction( + '[Quote API] Add Item to Quote Request Success', + payload<{ id: string }>() +); + +export const addBasketToQuoteRequest = createAction('[Quote] Add Basket to Quote Request'); + +export const addBasketToQuoteRequestFail = createAction('[Quote API] Add Basket to Quote Request Fail', httpError()); + +export const addBasketToQuoteRequestSuccess = createAction( + '[Quote API] Add Basket to Quote Request Success', + payload<{ id: string }>() +); + +export const updateQuoteRequestItems = createAction( + '[Quote] Update Quote Request Items', + payload<{ lineItemUpdates: LineItemUpdate[] }>() +); + +export const updateQuoteRequestItemsFail = createAction('[Quote API] Update Quote Request Items Fail', httpError()); + +export const updateQuoteRequestItemsSuccess = createAction( + '[Quote API] Update Quote Request Items Success', + payload<{ itemIds: string[] }>() +); + +export const deleteItemFromQuoteRequest = createAction( + '[Quote] Delete Item from Quote Request', + payload<{ itemId: string }>() +); + +export const deleteItemFromQuoteRequestFail = createAction( + '[Quote API] Delete Item from Quote Request Fail', + httpError() +); + +export const deleteItemFromQuoteRequestSuccess = createAction( + '[Quote API] Delete Item from Quote Request Success', + payload<{ id: string }>() +); diff --git a/src/app/extensions/quoting/store/quote-request/quote-request.effects.spec.ts b/src/app/extensions/quoting/store/quote-request/quote-request.effects.spec.ts index 42a77af22c..e3f3ffc4fd 100644 --- a/src/app/extensions/quoting/store/quote-request/quote-request.effects.spec.ts +++ b/src/app/extensions/quoting/store/quote-request/quote-request.effects.spec.ts @@ -19,11 +19,11 @@ import { Price } from 'ish-core/models/price/price.model'; import { ProductCompletenessLevel } from 'ish-core/models/product/product.model'; import { User } from 'ish-core/models/user/user.model'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; -import { DisplaySuccessMessage } from 'ish-core/store/core/messages'; -import { LoadBasketSuccess } from 'ish-core/store/customer/basket'; +import { displaySuccessMessage } from 'ish-core/store/core/messages'; +import { loadBasketSuccess } from 'ish-core/store/customer/basket'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; -import { LoadCompanyUserSuccess, LoginUserSuccess } from 'ish-core/store/customer/user'; -import { LoadProductIfNotLoaded } from 'ish-core/store/shopping/products'; +import { loadCompanyUserSuccess, loginUserSuccess } from 'ish-core/store/customer/user'; +import { loadProductIfNotLoaded } from 'ish-core/store/shopping/products'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { QuoteLineItemResult } from '../../models/quote-line-item-result/quote-line-item-result.model'; @@ -34,41 +34,41 @@ import { QuoteRequestService } from '../../services/quote-request/quote-request. import { QuotingStoreModule } from '../quoting-store.module'; import { - AddBasketToQuoteRequest, - AddBasketToQuoteRequestFail, - AddBasketToQuoteRequestSuccess, - AddProductToQuoteRequest, - AddProductToQuoteRequestFail, - AddProductToQuoteRequestSuccess, - AddQuoteRequest, - AddQuoteRequestFail, - AddQuoteRequestSuccess, - CreateQuoteRequestFromQuoteRequest, - CreateQuoteRequestFromQuoteRequestFail, - CreateQuoteRequestFromQuoteRequestSuccess, - DeleteItemFromQuoteRequest, - DeleteItemFromQuoteRequestFail, - DeleteItemFromQuoteRequestSuccess, - DeleteQuoteRequest, - DeleteQuoteRequestFail, - DeleteQuoteRequestSuccess, - LoadQuoteRequestItems, - LoadQuoteRequestItemsFail, - LoadQuoteRequestItemsSuccess, - LoadQuoteRequests, - LoadQuoteRequestsFail, - LoadQuoteRequestsSuccess, - SelectQuoteRequest, - SubmitQuoteRequest, - SubmitQuoteRequestFail, - SubmitQuoteRequestSuccess, - UpdateQuoteRequest, - UpdateQuoteRequestFail, - UpdateQuoteRequestItems, - UpdateQuoteRequestItemsFail, - UpdateQuoteRequestItemsSuccess, - UpdateQuoteRequestSuccess, - UpdateSubmitQuoteRequest, + addBasketToQuoteRequest, + addBasketToQuoteRequestFail, + addBasketToQuoteRequestSuccess, + addProductToQuoteRequest, + addProductToQuoteRequestFail, + addProductToQuoteRequestSuccess, + addQuoteRequest, + addQuoteRequestFail, + addQuoteRequestSuccess, + createQuoteRequestFromQuoteRequest, + createQuoteRequestFromQuoteRequestFail, + createQuoteRequestFromQuoteRequestSuccess, + deleteItemFromQuoteRequest, + deleteItemFromQuoteRequestFail, + deleteItemFromQuoteRequestSuccess, + deleteQuoteRequest, + deleteQuoteRequestFail, + deleteQuoteRequestSuccess, + loadQuoteRequestItems, + loadQuoteRequestItemsFail, + loadQuoteRequestItemsSuccess, + loadQuoteRequests, + loadQuoteRequestsFail, + loadQuoteRequestsSuccess, + selectQuoteRequest, + submitQuoteRequest, + submitQuoteRequestFail, + submitQuoteRequestSuccess, + updateQuoteRequest, + updateQuoteRequestFail, + updateQuoteRequestItems, + updateQuoteRequestItemsFail, + updateQuoteRequestItemsSuccess, + updateQuoteRequestSuccess, + updateSubmitQuoteRequest, } from './quote-request.actions'; import { QuoteRequestEffects } from './quote-request.effects'; @@ -122,14 +122,14 @@ describe('Quote Request Effects', () => { describe('loadQuoteRequests$', () => { beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); - store$.dispatch(new LoadCompanyUserSuccess({ user: { email: 'test' } as User })); + store$.dispatch(loginUserSuccess({ customer })); + store$.dispatch(loadCompanyUserSuccess({ user: { email: 'test' } as User })); when(quoteRequestServiceMock.getQuoteRequests()).thenReturn(of([{ id: 'QRID' } as QuoteRequestData])); }); it('should call the quoteService for getQuoteRequests', done => { - const action = new LoadQuoteRequests(); + const action = loadQuoteRequests(); actions$ = of(action); effects.loadQuoteRequests$.subscribe(() => { @@ -139,8 +139,8 @@ describe('Quote Request Effects', () => { }); it('should map to action of type LoadQuoteRequestsSuccess', () => { - const action = new LoadQuoteRequests(); - const completion = new LoadQuoteRequestsSuccess({ + const action = loadQuoteRequests(); + const completion = loadQuoteRequestsSuccess({ quoteRequests: [{ id: 'QRID' } as QuoteRequestData], }); actions$ = hot('-a-a-a', { a: action }); @@ -152,8 +152,8 @@ describe('Quote Request Effects', () => { it('should map invalid request to action of type LoadQuoteRequestsFail', () => { when(quoteRequestServiceMock.getQuoteRequests()).thenReturn(throwError({ message: 'invalid' })); - const action = new LoadQuoteRequests(); - const completion = new LoadQuoteRequestsFail({ error: { message: 'invalid' } as HttpError }); + const action = loadQuoteRequests(); + const completion = loadQuoteRequestsFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -163,14 +163,14 @@ describe('Quote Request Effects', () => { describe('addQuoteRequest$', () => { beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); - store$.dispatch(new LoadCompanyUserSuccess({ user: { email: 'test' } as User })); + store$.dispatch(loginUserSuccess({ customer })); + store$.dispatch(loadCompanyUserSuccess({ user: { email: 'test' } as User })); when(quoteRequestServiceMock.addQuoteRequest()).thenReturn(of('QRID')); }); it('should call the quoteService for addQuoteRequest', done => { - const action = new AddQuoteRequest(); + const action = addQuoteRequest(); actions$ = of(action); effects.addQuoteRequest$.subscribe(() => { @@ -180,8 +180,8 @@ describe('Quote Request Effects', () => { }); it('should map to action of type AddQuoteRequestSuccess', () => { - const action = new AddQuoteRequest(); - const completion = new AddQuoteRequestSuccess({ id: 'QRID' }); + const action = addQuoteRequest(); + const completion = addQuoteRequestSuccess({ id: 'QRID' }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -191,8 +191,8 @@ describe('Quote Request Effects', () => { it('should map invalid request to action of type AddQuoteRequestFail', () => { when(quoteRequestServiceMock.addQuoteRequest()).thenReturn(throwError({ message: 'invalid' })); - const action = new AddQuoteRequest(); - const completion = new AddQuoteRequestFail({ error: { message: 'invalid' } as HttpError }); + const action = addQuoteRequest(); + const completion = addQuoteRequestFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -202,10 +202,10 @@ describe('Quote Request Effects', () => { describe('updateQuoteRequest$', () => { beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); - store$.dispatch(new LoadCompanyUserSuccess({ user: { email: 'test' } as User })); - store$.dispatch(new LoadQuoteRequestsSuccess({ quoteRequests: [{ id: 'QRID' } as QuoteRequestData] })); - store$.dispatch(new SelectQuoteRequest({ id: 'QRID' })); + store$.dispatch(loginUserSuccess({ customer })); + store$.dispatch(loadCompanyUserSuccess({ user: { email: 'test' } as User })); + store$.dispatch(loadQuoteRequestsSuccess({ quoteRequests: [{ id: 'QRID' } as QuoteRequestData] })); + store$.dispatch(selectQuoteRequest({ id: 'QRID' })); when(quoteRequestServiceMock.updateQuoteRequest(anyString(), anything())).thenCall((_, quoteRequest) => of(quoteRequest) @@ -216,7 +216,7 @@ describe('Quote Request Effects', () => { const payload = { displayName: 'test', } as QuoteRequest; - const action = new UpdateQuoteRequest(payload); + const action = updateQuoteRequest(payload); actions$ = of(action); effects.updateQuoteRequest$.subscribe(() => { @@ -231,8 +231,8 @@ describe('Quote Request Effects', () => { displayName: 'test', } as QuoteRequestData, }; - const action = new UpdateQuoteRequest(payload.quoteRequest); - const completion = new UpdateQuoteRequestSuccess(payload); + const action = updateQuoteRequest(payload.quoteRequest); + const completion = updateQuoteRequestSuccess(payload); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -247,8 +247,8 @@ describe('Quote Request Effects', () => { const payload = { id: 'QRID', } as QuoteRequest; - const action = new UpdateQuoteRequest(payload); - const completion = new UpdateQuoteRequestFail({ error: { message: 'invalid' } as HttpError }); + const action = updateQuoteRequest(payload); + const completion = updateQuoteRequestFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -258,15 +258,15 @@ describe('Quote Request Effects', () => { describe('deleteQuoteRequest$', () => { beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); - store$.dispatch(new LoadCompanyUserSuccess({ user: { email: 'test' } as User })); + store$.dispatch(loginUserSuccess({ customer })); + store$.dispatch(loadCompanyUserSuccess({ user: { email: 'test' } as User })); when(quoteRequestServiceMock.deleteQuoteRequest(anyString())).thenReturn(of('QRID')); }); it('should call the quoteService for deleteQuoteRequest with specific quoteRequestId', done => { const id = 'QRID'; - const action = new DeleteQuoteRequest({ id }); + const action = deleteQuoteRequest({ id }); actions$ = of(action); effects.deleteQuoteRequest$.subscribe(() => { @@ -277,9 +277,9 @@ describe('Quote Request Effects', () => { it('should map to action of type deleteQuoteRequestSuccess', () => { const id = 'QRID'; - const action = new DeleteQuoteRequest({ id }); - const completion = new DeleteQuoteRequestSuccess({ id }); - const completion2 = new DisplaySuccessMessage({ + const action = deleteQuoteRequest({ id }); + const completion = deleteQuoteRequestSuccess({ id }); + const completion2 = displaySuccessMessage({ message: 'quote.delete.message', }); actions$ = hot('-a----a----a----|', { a: action }); @@ -292,8 +292,8 @@ describe('Quote Request Effects', () => { when(quoteRequestServiceMock.deleteQuoteRequest(anyString())).thenReturn(throwError({ message: 'invalid' })); const id = 'QRID'; - const action = new DeleteQuoteRequest({ id }); - const completion = new DeleteQuoteRequestFail({ error: { message: 'invalid' } as HttpError }); + const action = deleteQuoteRequest({ id }); + const completion = deleteQuoteRequestFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -303,15 +303,15 @@ describe('Quote Request Effects', () => { describe('submitQuoteRequest$', () => { beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); - store$.dispatch(new LoadCompanyUserSuccess({ user: { email: 'test' } as User })); - store$.dispatch(new SelectQuoteRequest({ id: 'QRID' })); + store$.dispatch(loginUserSuccess({ customer })); + store$.dispatch(loadCompanyUserSuccess({ user: { email: 'test' } as User })); + store$.dispatch(selectQuoteRequest({ id: 'QRID' })); when(quoteRequestServiceMock.submitQuoteRequest(anyString())).thenReturn(of('QRID')); }); it('should call the quoteService for submitQuoteRequest', done => { - const action = new SubmitQuoteRequest(); + const action = submitQuoteRequest(); actions$ = of(action); effects.submitQuoteRequest$.subscribe(() => { @@ -321,8 +321,8 @@ describe('Quote Request Effects', () => { }); it('should map to action of type SubmitQuoteRequestSuccess', () => { - const action = new SubmitQuoteRequest(); - const completion = new SubmitQuoteRequestSuccess({ id: 'QRID' }); + const action = submitQuoteRequest(); + const completion = submitQuoteRequestSuccess({ id: 'QRID' }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -332,8 +332,8 @@ describe('Quote Request Effects', () => { it('should map invalid request to action of type SubmitQuoteRequestFail', () => { when(quoteRequestServiceMock.submitQuoteRequest(anyString())).thenReturn(throwError({ message: 'invalid' })); - const action = new SubmitQuoteRequest(); - const completion = new SubmitQuoteRequestFail({ error: { message: 'invalid' } as HttpError }); + const action = submitQuoteRequest(); + const completion = submitQuoteRequestFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -343,9 +343,9 @@ describe('Quote Request Effects', () => { describe('updateSubmitQuoteRequest$', () => { beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); - store$.dispatch(new LoadCompanyUserSuccess({ user: { email: 'test' } as User })); - store$.dispatch(new SelectQuoteRequest({ id: 'QRID' })); + store$.dispatch(loginUserSuccess({ customer })); + store$.dispatch(loadCompanyUserSuccess({ user: { email: 'test' } as User })); + store$.dispatch(selectQuoteRequest({ id: 'QRID' })); when(quoteRequestServiceMock.updateQuoteRequest(anyString(), anything())).thenReturn( of({ id: 'QRID' } as QuoteRequestData) @@ -354,7 +354,7 @@ describe('Quote Request Effects', () => { }); it('should call the quoteService for updateQuoteRequest and for submitQuoteRequest', done => { - actions$ = of(new UpdateSubmitQuoteRequest({ displayName: 'edited' })); + actions$ = of(updateSubmitQuoteRequest({ displayName: 'edited' })); effects.updateSubmitQuoteRequest$.subscribe(action => { verify(quoteRequestServiceMock.updateQuoteRequest('QRID', deepEqual({ displayName: 'edited' }))).once(); verify(quoteRequestServiceMock.submitQuoteRequest('QRID')).once(); @@ -371,7 +371,7 @@ describe('Quote Request Effects', () => { throwError({ message: 'something went wrong' }) ); - actions$ = of(new UpdateSubmitQuoteRequest({ displayName: 'edited' })); + actions$ = of(updateSubmitQuoteRequest({ displayName: 'edited' })); effects.updateSubmitQuoteRequest$.subscribe(action => { verify(quoteRequestServiceMock.updateQuoteRequest('QRID', deepEqual({ displayName: 'edited' }))).once(); expect(action).toMatchInlineSnapshot(` @@ -387,7 +387,7 @@ describe('Quote Request Effects', () => { throwError({ message: 'something went wrong' }) ); - actions$ = of(new UpdateSubmitQuoteRequest({ displayName: 'edited' })); + actions$ = of(updateSubmitQuoteRequest({ displayName: 'edited' })); effects.updateSubmitQuoteRequest$.subscribe(action => { verify(quoteRequestServiceMock.updateQuoteRequest('QRID', deepEqual({ displayName: 'edited' }))).once(); verify(quoteRequestServiceMock.submitQuoteRequest('QRID')).once(); @@ -402,10 +402,10 @@ describe('Quote Request Effects', () => { describe('createQuoteRequestFromQuote$', () => { beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); - store$.dispatch(new LoadCompanyUserSuccess({ user: { email: 'test' } as User })); + store$.dispatch(loginUserSuccess({ customer })); + store$.dispatch(loadCompanyUserSuccess({ user: { email: 'test' } as User })); store$.dispatch( - new LoadQuoteRequestsSuccess({ + loadQuoteRequestsSuccess({ quoteRequests: [ { id: 'QRID', @@ -416,11 +416,11 @@ describe('Quote Request Effects', () => { }) ); store$.dispatch( - new LoadQuoteRequestItemsSuccess({ + loadQuoteRequestItemsSuccess({ quoteRequestItems: [{ productSKU: 'SKU', quantity: { value: 1 } } as QuoteRequestItem], }) ); - store$.dispatch(new SelectQuoteRequest({ id: 'QRID' })); + store$.dispatch(selectQuoteRequest({ id: 'QRID' })); when(quoteRequestServiceMock.createQuoteRequestFromQuoteRequest(anything())).thenReturn( of({ type: 'test' } as QuoteLineItemResult) @@ -428,7 +428,7 @@ describe('Quote Request Effects', () => { }); it('should call the quoteService for createQuoteRequestFromQuoteRequest', done => { - const action = new CreateQuoteRequestFromQuoteRequest({}); + const action = createQuoteRequestFromQuoteRequest({}); actions$ = of(action); effects.createQuoteRequestFromQuoteRequest$.subscribe(() => { @@ -438,8 +438,8 @@ describe('Quote Request Effects', () => { }); it('should map to action of type CreateQuoteRequestFromQuoteRequestSuccess', () => { - const action = new CreateQuoteRequestFromQuoteRequest({}); - const completion = new CreateQuoteRequestFromQuoteRequestSuccess({ + const action = createQuoteRequestFromQuoteRequest({}); + const completion = createQuoteRequestFromQuoteRequestSuccess({ quoteLineItemResult: { type: 'test', } as QuoteLineItemResult, @@ -455,8 +455,8 @@ describe('Quote Request Effects', () => { throwError({ message: 'invalid' }) ); - const action = new CreateQuoteRequestFromQuoteRequest({}); - const completion = new CreateQuoteRequestFromQuoteRequestFail({ + const action = createQuoteRequestFromQuoteRequest({}); + const completion = createQuoteRequestFromQuoteRequestFail({ error: { message: 'invalid', } as HttpError, @@ -470,10 +470,10 @@ describe('Quote Request Effects', () => { describe('loadQuoteRequestItems$', () => { beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); - store$.dispatch(new LoadCompanyUserSuccess({ user: { email: 'test' } as User })); + store$.dispatch(loginUserSuccess({ customer })); + store$.dispatch(loadCompanyUserSuccess({ user: { email: 'test' } as User })); store$.dispatch( - new LoadQuoteRequestsSuccess({ + loadQuoteRequestsSuccess({ quoteRequests: [ { id: 'QRID', @@ -491,7 +491,7 @@ describe('Quote Request Effects', () => { it('should call the quoteService for getQuoteRequestItem', done => { const id = 'QRID'; - const action = new LoadQuoteRequestItems({ id }); + const action = loadQuoteRequestItems({ id }); actions$ = of(action); effects.loadQuoteRequestItems$.subscribe(() => { @@ -502,14 +502,14 @@ describe('Quote Request Effects', () => { it('should map to action of type LoadQuoteRequestItemsSuccess', () => { const id = 'QRID'; - const action = new LoadQuoteRequestItems({ id }); + const action = loadQuoteRequestItems({ id }); - const completionLoadProductIfNotLoaded = new LoadProductIfNotLoaded({ + const completionLoadProductIfNotLoaded = loadProductIfNotLoaded({ sku: 'SKU', level: ProductCompletenessLevel.List, }); - const completionLoadQuoteRequestItemsSuccess = new LoadQuoteRequestItemsSuccess({ + const completionLoadQuoteRequestItemsSuccess = loadQuoteRequestItemsSuccess({ quoteRequestItems: [{ productSKU: 'SKU' } as QuoteRequestItem], }); @@ -528,8 +528,8 @@ describe('Quote Request Effects', () => { ); const id = 'QRID'; - const action = new LoadQuoteRequestItems({ id }); - const completion = new LoadQuoteRequestItemsFail({ + const action = loadQuoteRequestItems({ id }); + const completion = loadQuoteRequestItemsFail({ error: { message: 'invalid' } as HttpError, }); actions$ = hot('-a-a-a', { a: action }); @@ -541,10 +541,10 @@ describe('Quote Request Effects', () => { describe('loadProductsForQuoteRequest$', () => { it('should trigger LoadProduct actions for line items if LoadQuoteRequestItemsSuccess action triggered', () => { - const action = new LoadQuoteRequestItemsSuccess({ + const action = loadQuoteRequestItemsSuccess({ quoteRequestItems: [{ productSKU: 'SKU' } as QuoteRequestItem], }); - const completion = new LoadProductIfNotLoaded({ sku: 'SKU', level: ProductCompletenessLevel.List }); + const completion = loadProductIfNotLoaded({ sku: 'SKU', level: ProductCompletenessLevel.List }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -554,10 +554,10 @@ describe('Quote Request Effects', () => { describe('addProductToQuoteRequest$', () => { beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); - store$.dispatch(new LoadCompanyUserSuccess({ user: { email: 'test' } as User })); + store$.dispatch(loginUserSuccess({ customer })); + store$.dispatch(loadCompanyUserSuccess({ user: { email: 'test' } as User })); store$.dispatch( - new LoadQuoteRequestsSuccess({ + loadQuoteRequestsSuccess({ quoteRequests: [ { id: 'QRID', @@ -581,7 +581,7 @@ describe('Quote Request Effects', () => { sku: 'SKU', quantity: 1, }; - const action = new AddProductToQuoteRequest(payload); + const action = addProductToQuoteRequest(payload); actions$ = of(action); effects.addProductToQuoteRequest$.subscribe(() => { @@ -595,8 +595,8 @@ describe('Quote Request Effects', () => { sku: 'SKU', quantity: 1, }; - const action = new AddProductToQuoteRequest(payload); - const completion = new AddProductToQuoteRequestSuccess({ id: 'QRID' }); + const action = addProductToQuoteRequest(payload); + const completion = addProductToQuoteRequestSuccess({ id: 'QRID' }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -612,8 +612,8 @@ describe('Quote Request Effects', () => { sku: 'SKU', quantity: 1, }; - const action = new AddProductToQuoteRequest(payload); - const completion = new AddProductToQuoteRequestFail({ + const action = addProductToQuoteRequest(payload); + const completion = addProductToQuoteRequestFail({ error: { message: 'invalid', } as HttpError, @@ -627,10 +627,10 @@ describe('Quote Request Effects', () => { describe('addBasketToQuoteRequest$', () => { beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); - store$.dispatch(new LoadCompanyUserSuccess({ user: { email: 'test' } as User })); + store$.dispatch(loginUserSuccess({ customer })); + store$.dispatch(loadCompanyUserSuccess({ user: { email: 'test' } as User })); store$.dispatch( - new LoadQuoteRequestsSuccess({ + loadQuoteRequestsSuccess({ quoteRequests: [ { id: 'QRID', @@ -646,7 +646,7 @@ describe('Quote Request Effects', () => { }) ); store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'BID', lineItems: [ @@ -666,7 +666,7 @@ describe('Quote Request Effects', () => { }); it('should call the quoteService for addProductToQuoteRequest', done => { - const action = new AddBasketToQuoteRequest(); + const action = addBasketToQuoteRequest(); actions$ = of(action); effects.addBasketToQuoteRequest$.subscribe(() => { @@ -676,8 +676,8 @@ describe('Quote Request Effects', () => { }); it('should map to action of type AddBasketToQuoteRequestSuccess', () => { - const action = new AddBasketToQuoteRequest(); - const completion = new AddBasketToQuoteRequestSuccess({ id: 'QRID' }); + const action = addBasketToQuoteRequest(); + const completion = addBasketToQuoteRequestSuccess({ id: 'QRID' }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -689,8 +689,8 @@ describe('Quote Request Effects', () => { throwError({ message: 'invalid' }) ); - const action = new AddBasketToQuoteRequest(); - const completion = new AddBasketToQuoteRequestFail({ + const action = addBasketToQuoteRequest(); + const completion = addBasketToQuoteRequestFail({ error: { message: 'invalid', } as HttpError, @@ -704,10 +704,10 @@ describe('Quote Request Effects', () => { describe('updateQuoteRequestItems$', () => { beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); - store$.dispatch(new LoadCompanyUserSuccess({ user: { email: 'test' } as User })); + store$.dispatch(loginUserSuccess({ customer })); + store$.dispatch(loadCompanyUserSuccess({ user: { email: 'test' } as User })); store$.dispatch( - new LoadQuoteRequestsSuccess({ + loadQuoteRequestsSuccess({ quoteRequests: [ { id: 'QRID', @@ -718,7 +718,7 @@ describe('Quote Request Effects', () => { }) ); store$.dispatch( - new LoadQuoteRequestItemsSuccess({ + loadQuoteRequestItemsSuccess({ quoteRequestItems: [ { id: 'IID', @@ -729,7 +729,7 @@ describe('Quote Request Effects', () => { ], }) ); - store$.dispatch(new SelectQuoteRequest({ id: 'QRID' })); + store$.dispatch(selectQuoteRequest({ id: 'QRID' })); when(quoteRequestServiceMock.updateQuoteRequestItem(anyString(), anything())).thenReturn(of('QRID')); }); @@ -743,7 +743,7 @@ describe('Quote Request Effects', () => { }, ], }; - const action = new UpdateQuoteRequestItems(payload); + const action = updateQuoteRequestItems(payload); actions$ = of(action); effects.updateQuoteRequestItems$.subscribe(() => { @@ -761,7 +761,7 @@ describe('Quote Request Effects', () => { }, ], }; - const action = new UpdateQuoteRequestItems(payload); + const action = updateQuoteRequestItems(payload); actions$ = of(action); effects.updateQuoteRequestItems$.subscribe(() => { @@ -781,7 +781,7 @@ describe('Quote Request Effects', () => { }, ], }; - const action = new UpdateQuoteRequestItems(payload); + const action = updateQuoteRequestItems(payload); actions$ = of(action); effects.updateQuoteRequestItems$.subscribe(() => { @@ -800,8 +800,8 @@ describe('Quote Request Effects', () => { }, ], }; - const action = new UpdateQuoteRequestItems(payload); - const completion = new UpdateQuoteRequestItemsSuccess({ itemIds: ['QRID'] }); + const action = updateQuoteRequestItems(payload); + const completion = updateQuoteRequestItemsSuccess({ itemIds: ['QRID'] }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -821,8 +821,8 @@ describe('Quote Request Effects', () => { }, ], }; - const action = new UpdateQuoteRequestItems(payload); - const completion = new UpdateQuoteRequestItemsFail({ + const action = updateQuoteRequestItems(payload); + const completion = updateQuoteRequestItemsFail({ error: { message: 'invalid', } as HttpError, @@ -836,10 +836,10 @@ describe('Quote Request Effects', () => { describe('deleteItemFromQuoteRequest$', () => { beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); - store$.dispatch(new LoadCompanyUserSuccess({ user: { email: 'test' } as User })); - store$.dispatch(new LoadQuoteRequestsSuccess({ quoteRequests: [{ id: 'QRID' } as QuoteRequestData] })); - store$.dispatch(new SelectQuoteRequest({ id: 'QRID' })); + store$.dispatch(loginUserSuccess({ customer })); + store$.dispatch(loadCompanyUserSuccess({ user: { email: 'test' } as User })); + store$.dispatch(loadQuoteRequestsSuccess({ quoteRequests: [{ id: 'QRID' } as QuoteRequestData] })); + store$.dispatch(selectQuoteRequest({ id: 'QRID' })); when(quoteRequestServiceMock.removeItemFromQuoteRequest(anyString(), anyString())).thenReturn(of('QRID')); }); @@ -848,7 +848,7 @@ describe('Quote Request Effects', () => { const payload = { itemId: 'IID', }; - const action = new DeleteItemFromQuoteRequest(payload); + const action = deleteItemFromQuoteRequest(payload); actions$ = of(action); effects.deleteItemFromQuoteRequest$.subscribe(() => { @@ -862,8 +862,8 @@ describe('Quote Request Effects', () => { const payload = { itemId: 'IID', }; - const action = new DeleteItemFromQuoteRequest(payload); - const completion = new DeleteItemFromQuoteRequestSuccess({ id: 'QRID' }); + const action = deleteItemFromQuoteRequest(payload); + const completion = deleteItemFromQuoteRequestSuccess({ id: 'QRID' }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -878,8 +878,8 @@ describe('Quote Request Effects', () => { const payload = { itemId: 'IID', }; - const action = new DeleteItemFromQuoteRequest(payload); - const completion = new DeleteItemFromQuoteRequestFail({ + const action = deleteItemFromQuoteRequest(payload); + const completion = deleteItemFromQuoteRequestFail({ error: { message: 'invalid', } as HttpError, @@ -903,7 +903,7 @@ describe('Quote Request Effects', () => { sku: 'SKU', quantity: 1, }; - const action = new AddProductToQuoteRequest(payload); + const action = addProductToQuoteRequest(payload); actions$ = of(action); effects.goToLoginOnAddQuoteRequest$.subscribe(noop, fail, noop); @@ -914,7 +914,7 @@ describe('Quote Request Effects', () => { })); it('should navigate to /login with returnUrl set if AddBasketToQuoteRequest called without proper login.', fakeAsync(() => { - const action = new AddBasketToQuoteRequest(); + const action = addBasketToQuoteRequest(); actions$ = of(action); effects.goToLoginOnAddQuoteRequest$.subscribe(noop, fail, noop); @@ -928,7 +928,7 @@ describe('Quote Request Effects', () => { describe('goToQuoteRequestDetail$', () => { it('should navigate to /account/quotes/request/QRID if AddBasketToQuoteRequestSuccess called.', fakeAsync(() => { const id = 'QRID'; - const action = new AddBasketToQuoteRequestSuccess({ id }); + const action = addBasketToQuoteRequestSuccess({ id }); actions$ = of(action); effects.goToQuoteRequestDetail$.subscribe(noop, fail, noop); @@ -941,8 +941,8 @@ describe('Quote Request Effects', () => { describe('loadQuoteRequestsAfterChangeSuccess$', () => { it('should map to action of type LoadQuoteRequests if AddQuoteRequestSuccess action triggered', () => { - const action = new AddQuoteRequestSuccess({ id: 'QRID' }); - const completion = new LoadQuoteRequests(); + const action = addQuoteRequestSuccess({ id: 'QRID' }); + const completion = loadQuoteRequests(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -950,10 +950,10 @@ describe('Quote Request Effects', () => { }); it('should map to action of type LoadQuoteRequests if UpdateQuoteRequestSuccess action triggered', () => { - const action = new UpdateQuoteRequestSuccess({ + const action = updateQuoteRequestSuccess({ quoteRequest: { id: 'QRID' } as QuoteRequestData, }); - const completion = new LoadQuoteRequests(); + const completion = loadQuoteRequests(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -961,8 +961,8 @@ describe('Quote Request Effects', () => { }); it('should map to action of type LoadQuoteRequests if DeleteQuoteRequestSuccess action triggered', () => { - const action = new DeleteQuoteRequestSuccess({ id: 'QRID' }); - const completion = new LoadQuoteRequests(); + const action = deleteQuoteRequestSuccess({ id: 'QRID' }); + const completion = loadQuoteRequests(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -970,8 +970,8 @@ describe('Quote Request Effects', () => { }); it('should map to action of type LoadQuoteRequests if SubmitQuoteRequestSuccess action triggered', () => { - const action = new SubmitQuoteRequestSuccess({ id: 'QRID' }); - const completion = new LoadQuoteRequests(); + const action = submitQuoteRequestSuccess({ id: 'QRID' }); + const completion = loadQuoteRequests(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -979,10 +979,10 @@ describe('Quote Request Effects', () => { }); it('should map to action of type LoadQuoteRequests if CreateQuoteRequestFromQuoteSuccess action triggered', () => { - const action = new CreateQuoteRequestFromQuoteRequestSuccess({ + const action = createQuoteRequestFromQuoteRequestSuccess({ quoteLineItemResult: {} as QuoteLineItemResult, }); - const completion = new LoadQuoteRequests(); + const completion = loadQuoteRequests(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -990,8 +990,8 @@ describe('Quote Request Effects', () => { }); it('should map to action of type LoadQuoteRequests if AddProductToQuoteRequestSuccess action triggered', () => { - const action = new AddProductToQuoteRequestSuccess({ id: 'QRID' }); - const completion = new LoadQuoteRequests(); + const action = addProductToQuoteRequestSuccess({ id: 'QRID' }); + const completion = loadQuoteRequests(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -999,8 +999,8 @@ describe('Quote Request Effects', () => { }); it('should map to action of type LoadQuoteRequests if AddBasketToQuoteRequestSuccess action triggered', () => { - const action = new AddBasketToQuoteRequestSuccess({ id: 'QRID' }); - const completion = new LoadQuoteRequests(); + const action = addBasketToQuoteRequestSuccess({ id: 'QRID' }); + const completion = loadQuoteRequests(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -1008,8 +1008,8 @@ describe('Quote Request Effects', () => { }); it('should map to action of type LoadQuoteRequests if UpdateQuoteRequestItemsSuccess action triggered', () => { - const action = new UpdateQuoteRequestItemsSuccess({ itemIds: ['QRID'] }); - const completion = new LoadQuoteRequests(); + const action = updateQuoteRequestItemsSuccess({ itemIds: ['QRID'] }); + const completion = loadQuoteRequests(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -1017,8 +1017,8 @@ describe('Quote Request Effects', () => { }); it('should map to action of type LoadQuoteRequests if DeleteItemFromQuoteRequestSuccess action triggered', () => { - const action = new DeleteItemFromQuoteRequestSuccess({ id: 'QRID' }); - const completion = new LoadQuoteRequests(); + const action = deleteItemFromQuoteRequestSuccess({ id: 'QRID' }); + const completion = loadQuoteRequests(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -1026,8 +1026,8 @@ describe('Quote Request Effects', () => { }); it('should map to action of type LoadQuoteRequests if LoadCompanyUserSuccess action triggered', () => { - const action = new LoadCompanyUserSuccess({ user: {} as User }); - const completion = new LoadQuoteRequests(); + const action = loadCompanyUserSuccess({ user: {} as User }); + const completion = loadQuoteRequests(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -1059,9 +1059,9 @@ describe('Quote Request Effects', () => { describe('loadQuoteRequestItemsAfterSelectQuoteRequest', () => { it('should fire LoadQuoteRequestItems when SelectQuoteRequest and LoadQuoteRequestsSuccess action triggered.', () => { - const selectQuoteRequestAction = new SelectQuoteRequest({ id: 'QRID' }); - const loadQuoteRequestSuccessAction = new LoadQuoteRequestsSuccess({ quoteRequests: [] }); - const expected = new LoadQuoteRequestItems({ id: 'QRID' }); + const selectQuoteRequestAction = selectQuoteRequest({ id: 'QRID' }); + const loadQuoteRequestSuccessAction = loadQuoteRequestsSuccess({ quoteRequests: [] }); + const expected = loadQuoteRequestItems({ id: 'QRID' }); actions$ = hot('a--b--a', { a: selectQuoteRequestAction, b: loadQuoteRequestSuccessAction }); expect(effects.loadQuoteRequestItemsAfterSelectQuoteRequest$).toBeObservable(cold('---a--a', { a: expected })); @@ -1071,7 +1071,7 @@ describe('Quote Request Effects', () => { describe('loadQuoteRequestsOnLogin', () => { it('should fire LoadQuoteRequests if getLoggedInCustomer selector streams true.', done => { store$.dispatch( - new LoginUserSuccess({ + loginUserSuccess({ customer: {} as Customer, user: {} as User, }) diff --git a/src/app/extensions/quoting/store/quote-request/quote-request.effects.ts b/src/app/extensions/quoting/store/quote-request/quote-request.effects.ts index 1463727707..e166d1d53c 100644 --- a/src/app/extensions/quoting/store/quote-request/quote-request.effects.ts +++ b/src/app/extensions/quoting/store/quote-request/quote-request.effects.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; import { combineLatest, concat, forkJoin } from 'rxjs'; @@ -25,12 +25,12 @@ import { } from 'ish-core/models/line-item-update/line-item-update.helper'; import { LineItemUpdate } from 'ish-core/models/line-item-update/line-item-update.model'; import { ProductCompletenessLevel } from 'ish-core/models/product/product.model'; -import { DisplaySuccessMessage } from 'ish-core/store/core/messages'; +import { displaySuccessMessage } from 'ish-core/store/core/messages'; import { selectRouteParam } from 'ish-core/store/core/router'; -import { SetBreadcrumbData } from 'ish-core/store/core/viewconf'; +import { setBreadcrumbData } from 'ish-core/store/core/viewconf'; import { getCurrentBasket } from 'ish-core/store/customer/basket'; -import { UserActionTypes, getUserAuthorized } from 'ish-core/store/customer/user'; -import { LoadProductIfNotLoaded } from 'ish-core/store/shopping/products'; +import { getUserAuthorized, loadCompanyUserSuccess } from 'ish-core/store/customer/user'; +import { loadProductIfNotLoaded } from 'ish-core/store/shopping/products'; import { distinctCompareWith, mapErrorToAction, @@ -42,41 +42,44 @@ import { import { QuoteRequest } from '../../models/quote-request/quote-request.model'; import { QuoteRequestService } from '../../services/quote-request/quote-request.service'; -import { QuoteActionTypes } from '../quote/quote.actions'; +import { createQuoteRequestFromQuoteSuccess } from '../quote/quote.actions'; import { - AddBasketToQuoteRequestFail, - AddBasketToQuoteRequestSuccess, - AddProductToQuoteRequest, - AddProductToQuoteRequestFail, - AddProductToQuoteRequestSuccess, - AddQuoteRequestFail, - AddQuoteRequestSuccess, - CreateQuoteRequestFromQuoteRequest, - CreateQuoteRequestFromQuoteRequestFail, - CreateQuoteRequestFromQuoteRequestSuccess, - DeleteItemFromQuoteRequest, - DeleteItemFromQuoteRequestFail, - DeleteItemFromQuoteRequestSuccess, - DeleteQuoteRequest, - DeleteQuoteRequestFail, - DeleteQuoteRequestSuccess, - LoadQuoteRequestItems, - LoadQuoteRequestItemsFail, - LoadQuoteRequestItemsSuccess, - LoadQuoteRequests, - LoadQuoteRequestsFail, - LoadQuoteRequestsSuccess, - QuoteRequestActionTypes, - SelectQuoteRequest, - SubmitQuoteRequestFail, - SubmitQuoteRequestSuccess, - UpdateQuoteRequest, - UpdateQuoteRequestFail, - UpdateQuoteRequestItems, - UpdateQuoteRequestItemsFail, - UpdateQuoteRequestItemsSuccess, - UpdateQuoteRequestSuccess, + addBasketToQuoteRequest, + addBasketToQuoteRequestFail, + addBasketToQuoteRequestSuccess, + addProductToQuoteRequest, + addProductToQuoteRequestFail, + addProductToQuoteRequestSuccess, + addQuoteRequest, + addQuoteRequestFail, + addQuoteRequestSuccess, + createQuoteRequestFromQuoteRequest, + createQuoteRequestFromQuoteRequestFail, + createQuoteRequestFromQuoteRequestSuccess, + deleteItemFromQuoteRequest, + deleteItemFromQuoteRequestFail, + deleteItemFromQuoteRequestSuccess, + deleteQuoteRequest, + deleteQuoteRequestFail, + deleteQuoteRequestSuccess, + loadQuoteRequestItems, + loadQuoteRequestItemsFail, + loadQuoteRequestItemsSuccess, + loadQuoteRequests, + loadQuoteRequestsFail, + loadQuoteRequestsSuccess, + selectQuoteRequest, + submitQuoteRequest, + submitQuoteRequestFail, + submitQuoteRequestSuccess, + updateQuoteRequest, + updateQuoteRequestFail, + updateQuoteRequestItems, + updateQuoteRequestItemsFail, + updateQuoteRequestItemsSuccess, + updateQuoteRequestSuccess, + updateSubmitQuoteRequest, } from './quote-request.actions'; import { getCurrentQuoteRequests, @@ -99,13 +102,14 @@ export class QuoteRequestEffects { /** * The load quote requests effect. */ - @Effect() - loadQuoteRequests$ = this.actions$.pipe( - ofType(QuoteRequestActionTypes.LoadQuoteRequests), - concatMap(() => - this.quoteRequestService.getQuoteRequests().pipe( - map(quoteRequests => new LoadQuoteRequestsSuccess({ quoteRequests })), - mapErrorToAction(LoadQuoteRequestsFail) + loadQuoteRequests$ = createEffect(() => + this.actions$.pipe( + ofType(loadQuoteRequests), + concatMap(() => + this.quoteRequestService.getQuoteRequests().pipe( + map(quoteRequests => loadQuoteRequestsSuccess({ quoteRequests })), + mapErrorToAction(loadQuoteRequestsFail) + ) ) ) ); @@ -113,13 +117,14 @@ export class QuoteRequestEffects { /** * Add quote request to a specific user of a specific customer. */ - @Effect() - addQuoteRequest$ = this.actions$.pipe( - ofType(QuoteRequestActionTypes.AddQuoteRequest), - concatMap(() => - this.quoteRequestService.addQuoteRequest().pipe( - map(id => new AddQuoteRequestSuccess({ id })), - mapErrorToAction(AddQuoteRequestFail) + addQuoteRequest$ = createEffect(() => + this.actions$.pipe( + ofType(addQuoteRequest), + concatMap(() => + this.quoteRequestService.addQuoteRequest().pipe( + map(id => addQuoteRequestSuccess({ id })), + mapErrorToAction(addQuoteRequestFail) + ) ) ) ); @@ -127,15 +132,16 @@ export class QuoteRequestEffects { /** * Update specific quote request for a specific user of a specific customer. */ - @Effect() - updateQuoteRequest$ = this.actions$.pipe( - ofType(QuoteRequestActionTypes.UpdateQuoteRequest), - mapToPayload(), - withLatestFrom(this.store.pipe(select(getSelectedQuoteRequestId))), - concatMap(([payload, quoteRequestId]) => - this.quoteRequestService.updateQuoteRequest(quoteRequestId, payload).pipe( - map(quoteRequest => new UpdateQuoteRequestSuccess({ quoteRequest })), - mapErrorToAction(UpdateQuoteRequestFail) + updateQuoteRequest$ = createEffect(() => + this.actions$.pipe( + ofType(updateQuoteRequest), + mapToPayload(), + withLatestFrom(this.store.pipe(select(getSelectedQuoteRequestId))), + concatMap(([payload, quoteRequestId]) => + this.quoteRequestService.updateQuoteRequest(quoteRequestId, payload).pipe( + map(quoteRequest => updateQuoteRequestSuccess({ quoteRequest })), + mapErrorToAction(updateQuoteRequestFail) + ) ) ) ); @@ -143,18 +149,19 @@ export class QuoteRequestEffects { /** * Delete quote request from a specific user of a specific customer. */ - @Effect() - deleteQuoteRequest$ = this.actions$.pipe( - ofType(QuoteRequestActionTypes.DeleteQuoteRequest), - mapToPayloadProperty('id'), + deleteQuoteRequest$ = createEffect(() => + this.actions$.pipe( + ofType(deleteQuoteRequest), + mapToPayloadProperty('id'), - concatMap(quoteRequestId => - this.quoteRequestService.deleteQuoteRequest(quoteRequestId).pipe( - mergeMap(id => [ - new DeleteQuoteRequestSuccess({ id }), - new DisplaySuccessMessage({ message: 'quote.delete.message' }), - ]), - mapErrorToAction(DeleteQuoteRequestFail) + concatMap(quoteRequestId => + this.quoteRequestService.deleteQuoteRequest(quoteRequestId).pipe( + mergeMap(id => [ + deleteQuoteRequestSuccess({ id }), + displaySuccessMessage({ message: 'quote.delete.message' }), + ]), + mapErrorToAction(deleteQuoteRequestFail) + ) ) ) ); @@ -162,14 +169,15 @@ export class QuoteRequestEffects { /** * Submit quote request from a specific user of a specific customer. */ - @Effect() - submitQuoteRequest$ = this.actions$.pipe( - ofType(QuoteRequestActionTypes.SubmitQuoteRequest), - withLatestFrom(this.store.pipe(select(getSelectedQuoteRequestId))), - concatMap(([, quoteRequestId]) => - this.quoteRequestService.submitQuoteRequest(quoteRequestId).pipe( - map(id => new SubmitQuoteRequestSuccess({ id })), - mapErrorToAction(SubmitQuoteRequestFail) + submitQuoteRequest$ = createEffect(() => + this.actions$.pipe( + ofType(submitQuoteRequest), + withLatestFrom(this.store.pipe(select(getSelectedQuoteRequestId))), + concatMap(([, quoteRequestId]) => + this.quoteRequestService.submitQuoteRequest(quoteRequestId).pipe( + map(id => submitQuoteRequestSuccess({ id })), + mapErrorToAction(submitQuoteRequestFail) + ) ) ) ); @@ -177,20 +185,21 @@ export class QuoteRequestEffects { /** * Update quote request before submitting the quote request. */ - @Effect() - updateSubmitQuoteRequest$ = this.actions$.pipe( - ofType(QuoteRequestActionTypes.UpdateSubmitQuoteRequest), - mapToPayload(), - withLatestFrom(this.store.pipe(select(getSelectedQuoteRequestId))), - concatMap(([payload, quoteRequestId]) => - this.quoteRequestService.updateQuoteRequest(quoteRequestId, payload).pipe( - switchMap(quoteRequest => - this.quoteRequestService.submitQuoteRequest(quoteRequest.id).pipe( - map(id => new SubmitQuoteRequestSuccess({ id })), - mapErrorToAction(SubmitQuoteRequestFail) - ) - ), - mapErrorToAction(UpdateQuoteRequestFail) + updateSubmitQuoteRequest$ = createEffect(() => + this.actions$.pipe( + ofType(updateSubmitQuoteRequest), + mapToPayload(), + withLatestFrom(this.store.pipe(select(getSelectedQuoteRequestId))), + concatMap(([payload, quoteRequestId]) => + this.quoteRequestService.updateQuoteRequest(quoteRequestId, payload).pipe( + switchMap(quoteRequest => + this.quoteRequestService.submitQuoteRequest(quoteRequest.id).pipe( + map(id => submitQuoteRequestSuccess({ id })), + mapErrorToAction(submitQuoteRequestFail) + ) + ), + mapErrorToAction(updateQuoteRequestFail) + ) ) ) ); @@ -198,20 +207,23 @@ export class QuoteRequestEffects { /** * Create quote request based on selected, submited quote request from a specific user of a specific customer. */ - @Effect() - createQuoteRequestFromQuoteRequest$ = this.actions$.pipe( - ofType(QuoteRequestActionTypes.CreateQuoteRequestFromQuoteRequest), - mapToPayloadProperty('redirect'), - withLatestFrom(this.store.pipe(select(getSelectedQuoteRequestWithProducts))), - concatMap(([redirect, currentQuoteRequest]) => - this.quoteRequestService.createQuoteRequestFromQuoteRequest(currentQuoteRequest).pipe( - map(quoteLineItemResult => new CreateQuoteRequestFromQuoteRequestSuccess({ quoteLineItemResult })), - tap(quoteLineItemResult => { - if (redirect) { - this.router.navigate([`/account/quotes/request/${quoteLineItemResult.payload.quoteLineItemResult.title}`]); - } - }), - mapErrorToAction(CreateQuoteRequestFromQuoteRequestFail) + createQuoteRequestFromQuoteRequest$ = createEffect(() => + this.actions$.pipe( + ofType(createQuoteRequestFromQuoteRequest), + mapToPayloadProperty('redirect'), + withLatestFrom(this.store.pipe(select(getSelectedQuoteRequestWithProducts))), + concatMap(([redirect, currentQuoteRequest]) => + this.quoteRequestService.createQuoteRequestFromQuoteRequest(currentQuoteRequest).pipe( + map(quoteLineItemResult => createQuoteRequestFromQuoteRequestSuccess({ quoteLineItemResult })), + tap(quoteLineItemResult => { + if (redirect) { + this.router.navigate([ + `/account/quotes/request/${quoteLineItemResult.payload.quoteLineItemResult.title}`, + ]); + } + }), + mapErrorToAction(createQuoteRequestFromQuoteRequestFail) + ) ) ) ); @@ -219,26 +231,27 @@ export class QuoteRequestEffects { /** * The load quote requests items effect. */ - @Effect() - loadQuoteRequestItems$ = this.actions$.pipe( - ofType(QuoteRequestActionTypes.LoadQuoteRequestItems), - mapToPayloadProperty('id'), - withLatestFrom(this.store.pipe(select(getCurrentQuoteRequests))), - map(([quoteId, quoteRequests]) => quoteRequests.filter(item => item.id === quoteId).pop()), - whenTruthy(), - mergeMap(quoteRequest => - forkJoin( - // tslint:disable-next-line:no-string-literal - quoteRequest.items.map(item => this.quoteRequestService.getQuoteRequestItem(quoteRequest.id, item['title'])) - ).pipe( - defaultIfEmpty([]), - mergeMap(quoteRequestItems => [ - ...quoteRequestItems.map( - item => new LoadProductIfNotLoaded({ sku: item.productSKU, level: ProductCompletenessLevel.List }) - ), - new LoadQuoteRequestItemsSuccess({ quoteRequestItems }), - ]), - mapErrorToAction(LoadQuoteRequestItemsFail) + loadQuoteRequestItems$ = createEffect(() => + this.actions$.pipe( + ofType(loadQuoteRequestItems), + mapToPayloadProperty('id'), + withLatestFrom(this.store.pipe(select(getCurrentQuoteRequests))), + map(([quoteId, quoteRequests]) => quoteRequests.filter(item => item.id === quoteId).pop()), + whenTruthy(), + mergeMap(quoteRequest => + forkJoin( + // tslint:disable-next-line:no-string-literal + quoteRequest.items.map(item => this.quoteRequestService.getQuoteRequestItem(quoteRequest.id, item['title'])) + ).pipe( + defaultIfEmpty([]), + mergeMap(quoteRequestItems => [ + ...quoteRequestItems.map(item => + loadProductIfNotLoaded({ sku: item.productSKU, level: ProductCompletenessLevel.List }) + ), + loadQuoteRequestItemsSuccess({ quoteRequestItems }), + ]), + mapErrorToAction(loadQuoteRequestItemsFail) + ) ) ) ); @@ -247,30 +260,32 @@ export class QuoteRequestEffects { * After successfully loading quote request, trigger a LoadProduct action * for each product that is missing in the current product entities state. */ - @Effect() - loadProductsForQuoteRequest$ = this.actions$.pipe( - ofType(QuoteRequestActionTypes.LoadQuoteRequestItemsSuccess), - mapToPayloadProperty('quoteRequestItems'), - concatMap(lineItems => [ - ...lineItems.map( - ({ productSKU }) => new LoadProductIfNotLoaded({ sku: productSKU, level: ProductCompletenessLevel.List }) - ), - ]) + loadProductsForQuoteRequest$ = createEffect(() => + this.actions$.pipe( + ofType(loadQuoteRequestItemsSuccess), + mapToPayloadProperty('quoteRequestItems'), + concatMap(lineItems => [ + ...lineItems.map(({ productSKU }) => + loadProductIfNotLoaded({ sku: productSKU, level: ProductCompletenessLevel.List }) + ), + ]) + ) ); /** * Add an item to the current editable quote request from a specific user of a specific customer. */ - @Effect() - addProductToQuoteRequest$ = this.actions$.pipe( - ofType(QuoteRequestActionTypes.AddProductToQuoteRequest), - mapToPayload(), - withLatestFrom(this.store.pipe(select(getUserAuthorized))), - filter(([, authorized]) => authorized), - concatMap(([item]) => - this.quoteRequestService.addProductToQuoteRequest(item.sku, item.quantity).pipe( - map(id => new AddProductToQuoteRequestSuccess({ id })), - mapErrorToAction(AddProductToQuoteRequestFail) + addProductToQuoteRequest$ = createEffect(() => + this.actions$.pipe( + ofType(addProductToQuoteRequest), + mapToPayload(), + withLatestFrom(this.store.pipe(select(getUserAuthorized))), + filter(([, authorized]) => authorized), + concatMap(([item]) => + this.quoteRequestService.addProductToQuoteRequest(item.sku, item.quantity).pipe( + map(id => addProductToQuoteRequestSuccess({ id })), + mapErrorToAction(addProductToQuoteRequestFail) + ) ) ) ); @@ -278,19 +293,20 @@ export class QuoteRequestEffects { /** * Trigger an AddProductToQuoteRequest action for each line item thats in the current basket. */ - @Effect() - addBasketToQuoteRequest$ = this.actions$.pipe( - ofType(QuoteRequestActionTypes.AddBasketToQuoteRequest), - withLatestFrom(this.store.pipe(select(getCurrentBasket))), - concatMap(([, currentBasket]) => - concat( - ...currentBasket.lineItems.map(lineItem => - this.quoteRequestService.addProductToQuoteRequest(lineItem.productSKU, lineItem.quantity.value) + addBasketToQuoteRequest$ = createEffect(() => + this.actions$.pipe( + ofType(addBasketToQuoteRequest), + withLatestFrom(this.store.pipe(select(getCurrentBasket))), + concatMap(([, currentBasket]) => + concat( + ...currentBasket.lineItems.map(lineItem => + this.quoteRequestService.addProductToQuoteRequest(lineItem.productSKU, lineItem.quantity.value) + ) + ).pipe( + last(), + map(id => addBasketToQuoteRequestSuccess({ id })), + mapErrorToAction(addBasketToQuoteRequestFail) ) - ).pipe( - last(), - map(id => new AddBasketToQuoteRequestSuccess({ id })), - mapErrorToAction(AddBasketToQuoteRequestFail) ) ) ); @@ -301,26 +317,27 @@ export class QuoteRequestEffects { * Triggers delete item request if item quantity set to zero * TODO: currently updating more than one item at a time is not needed. We could simplify this effect. */ - @Effect() - updateQuoteRequestItems$ = this.actions$.pipe( - ofType(QuoteRequestActionTypes.UpdateQuoteRequestItems), - mapToPayloadProperty('lineItemUpdates'), - withLatestFrom(this.store.pipe(select(getSelectedQuoteRequestWithProducts))), - map(([lineItemUpdates, selectedQuoteRequest]) => ({ - quoteRequestId: selectedQuoteRequest.id, - updatedItems: this.filterQuoteRequestsForChanges(lineItemUpdates, selectedQuoteRequest), - })), - concatMap(payload => - forkJoin( - payload.updatedItems.map(item => - item.quantity === 0 - ? this.quoteRequestService.removeItemFromQuoteRequest(payload.quoteRequestId, item.itemId) - : this.quoteRequestService.updateQuoteRequestItem(payload.quoteRequestId, item) + updateQuoteRequestItems$ = createEffect(() => + this.actions$.pipe( + ofType(updateQuoteRequestItems), + mapToPayloadProperty('lineItemUpdates'), + withLatestFrom(this.store.pipe(select(getSelectedQuoteRequestWithProducts))), + map(([lineItemUpdates, selectedQuoteRequest]) => ({ + quoteRequestId: selectedQuoteRequest.id, + updatedItems: this.filterQuoteRequestsForChanges(lineItemUpdates, selectedQuoteRequest), + })), + concatMap(payload => + forkJoin( + payload.updatedItems.map(item => + item.quantity === 0 + ? this.quoteRequestService.removeItemFromQuoteRequest(payload.quoteRequestId, item.itemId) + : this.quoteRequestService.updateQuoteRequestItem(payload.quoteRequestId, item) + ) + ).pipe( + defaultIfEmpty(), + map(itemIds => updateQuoteRequestItemsSuccess({ itemIds })), + mapErrorToAction(updateQuoteRequestItemsFail) ) - ).pipe( - defaultIfEmpty(), - map(itemIds => new UpdateQuoteRequestItemsSuccess({ itemIds })), - mapErrorToAction(UpdateQuoteRequestItemsFail) ) ) ); @@ -328,15 +345,16 @@ export class QuoteRequestEffects { /** * Delete item from quote request for a specific user of a specific customer. */ - @Effect() - deleteItemFromQuoteRequest$ = this.actions$.pipe( - ofType(QuoteRequestActionTypes.DeleteItemFromQuoteRequest), - mapToPayloadProperty('itemId'), - withLatestFrom(this.store.pipe(select(getSelectedQuoteRequestId))), - concatMap(([payload, quoteRequestId]) => - this.quoteRequestService.removeItemFromQuoteRequest(quoteRequestId, payload).pipe( - map(id => new DeleteItemFromQuoteRequestSuccess({ id })), - mapErrorToAction(DeleteItemFromQuoteRequestFail) + deleteItemFromQuoteRequest$ = createEffect(() => + this.actions$.pipe( + ofType(deleteItemFromQuoteRequest), + mapToPayloadProperty('itemId'), + withLatestFrom(this.store.pipe(select(getSelectedQuoteRequestId))), + concatMap(([payload, quoteRequestId]) => + this.quoteRequestService.removeItemFromQuoteRequest(quoteRequestId, payload).pipe( + map(id => deleteItemFromQuoteRequestSuccess({ id })), + mapErrorToAction(deleteItemFromQuoteRequestFail) + ) ) ) ); @@ -344,101 +362,107 @@ export class QuoteRequestEffects { /** * Call router for navigate to login on AddProductToQuoteRequest if not logged in. */ - @Effect({ dispatch: false }) - goToLoginOnAddQuoteRequest$ = this.actions$.pipe( - ofType(QuoteRequestActionTypes.AddProductToQuoteRequest, QuoteRequestActionTypes.AddBasketToQuoteRequest), - mergeMap(() => this.store.pipe(select(getUserAuthorized), first())), - whenFalsy(), - tap(() => { - const queryParams = { returnUrl: this.router.routerState.snapshot.url, messageKey: 'quotes' }; - this.router.navigate(['/login'], { queryParams }); - }) + goToLoginOnAddQuoteRequest$ = createEffect( + () => + this.actions$.pipe( + ofType(addProductToQuoteRequest, addBasketToQuoteRequest), + mergeMap(() => this.store.pipe(select(getUserAuthorized), first())), + whenFalsy(), + tap(() => { + const queryParams = { returnUrl: this.router.routerState.snapshot.url, messageKey: 'quotes' }; + this.router.navigate(['/login'], { queryParams }); + }) + ), + { dispatch: false } ); - @Effect({ dispatch: false }) - goToQuoteRequestDetail$ = this.actions$.pipe( - ofType(QuoteRequestActionTypes.AddBasketToQuoteRequestSuccess), - mapToPayloadProperty('id'), - tap(quoteRequestId => { - this.router.navigate([`/account/quotes/request/${quoteRequestId}`]); - }) + goToQuoteRequestDetail$ = createEffect( + () => + this.actions$.pipe( + ofType(addBasketToQuoteRequestSuccess), + mapToPayloadProperty('id'), + tap(quoteRequestId => { + this.router.navigate([`/account/quotes/request/${quoteRequestId}`]); + }) + ), + { dispatch: false } ); /** * Triggers a SelectQuoteRequest action for the just created copy after successfully creating a quote request copy. */ - @Effect() - selectQuoteRequestAfterCopy$ = this.actions$.pipe( - ofType( - QuoteRequestActionTypes.CreateQuoteRequestFromQuoteRequestSuccess - ), - mapToPayload(), - map(payload => new SelectQuoteRequest({ id: payload.quoteLineItemResult.title })) + selectQuoteRequestAfterCopy$ = createEffect(() => + this.actions$.pipe( + ofType(createQuoteRequestFromQuoteRequestSuccess), + mapToPayload(), + map(payload => selectQuoteRequest({ id: payload.quoteLineItemResult.title })) + ) ); /** * Triggers a LoadQuoteRequests action after successful quote request related interaction with the Quote API. */ - @Effect() - loadQuoteRequestsAfterChangeSuccess$ = this.actions$.pipe( - ofType( - QuoteRequestActionTypes.AddQuoteRequestSuccess, - QuoteRequestActionTypes.UpdateQuoteRequestSuccess, - QuoteRequestActionTypes.DeleteQuoteRequestSuccess, - QuoteRequestActionTypes.SubmitQuoteRequestSuccess, - QuoteRequestActionTypes.CreateQuoteRequestFromQuoteRequestSuccess, - QuoteRequestActionTypes.AddProductToQuoteRequestSuccess, - QuoteRequestActionTypes.AddBasketToQuoteRequestSuccess, - QuoteRequestActionTypes.UpdateQuoteRequestItemsSuccess, - QuoteRequestActionTypes.DeleteItemFromQuoteRequestSuccess, - QuoteActionTypes.CreateQuoteRequestFromQuoteSuccess, - UserActionTypes.LoadCompanyUserSuccess - ), - filter(() => this.featureToggleService.enabled('quoting')), - mapTo(new LoadQuoteRequests()) + loadQuoteRequestsAfterChangeSuccess$ = createEffect(() => + this.actions$.pipe( + ofType( + addQuoteRequestSuccess, + updateQuoteRequestSuccess, + deleteQuoteRequestSuccess, + submitQuoteRequestSuccess, + createQuoteRequestFromQuoteRequestSuccess, + addProductToQuoteRequestSuccess, + addBasketToQuoteRequestSuccess, + updateQuoteRequestItemsSuccess, + deleteItemFromQuoteRequestSuccess, + createQuoteRequestFromQuoteSuccess, + loadCompanyUserSuccess + ), + filter(() => this.featureToggleService.enabled('quoting')), + mapTo(loadQuoteRequests()) + ) ); /** * Triggers a SelectQuoteRequest action if route contains quoteRequestId parameter */ - @Effect() - routeListenerForSelectingQuote$ = this.store.pipe( - select(selectRouteParam('quoteRequestId')), - distinctCompareWith(this.store.pipe(select(getSelectedQuoteRequestId))), - map(id => new SelectQuoteRequest({ id })) + routeListenerForSelectingQuote$ = createEffect(() => + this.store.pipe( + select(selectRouteParam('quoteRequestId')), + distinctCompareWith(this.store.pipe(select(getSelectedQuoteRequestId))), + map(id => selectQuoteRequest({ id })) + ) ); /** * Triggers a LoadQuoteRequestItems action if a quote request gets selected and LoadQuoteRequestsSuccess action triggered */ - @Effect() - loadQuoteRequestItemsAfterSelectQuoteRequest$ = combineLatest([ - this.actions$.pipe( - ofType(QuoteRequestActionTypes.SelectQuoteRequest), - mapToPayloadProperty('id') - ), - this.actions$.pipe(ofType(QuoteRequestActionTypes.LoadQuoteRequestsSuccess)), - ]).pipe( - filter(([quoteId]) => !!quoteId), - map(([quoteId]) => new LoadQuoteRequestItems({ id: quoteId })) + loadQuoteRequestItemsAfterSelectQuoteRequest$ = createEffect(() => + combineLatest([ + this.actions$.pipe(ofType(selectQuoteRequest), mapToPayloadProperty('id')), + this.actions$.pipe(ofType(loadQuoteRequestsSuccess)), + ]).pipe( + filter(([quoteId]) => !!quoteId), + map(([quoteId]) => loadQuoteRequestItems({ id: quoteId })) + ) ); - @Effect() - loadQuoteRequestsOnLogin$ = this.store.pipe(select(getUserAuthorized), whenTruthy(), mapTo(new LoadQuoteRequests())); + loadQuoteRequestsOnLogin$ = createEffect(() => + this.store.pipe(select(getUserAuthorized), whenTruthy(), mapTo(loadQuoteRequests())) + ); - @Effect() - setQuoteRequestBreadcrumb$ = this.store.pipe( - select(getSelectedQuoteRequest), - whenTruthy(), - withLatestFrom(this.translateService.get('quote.edit.unsubmitted.quote_request_details.text')), - map( - ([quoteRequest, x]) => - new SetBreadcrumbData({ + setQuoteRequestBreadcrumb$ = createEffect(() => + this.store.pipe( + select(getSelectedQuoteRequest), + whenTruthy(), + withLatestFrom(this.translateService.get('quote.edit.unsubmitted.quote_request_details.text')), + map(([quoteRequest, x]) => + setBreadcrumbData({ breadcrumbData: [ { key: 'quote.quotes.link', link: '/account/quotes' }, { text: `${x} - ${quoteRequest.displayName}` }, ], }) + ) ) ); diff --git a/src/app/extensions/quoting/store/quote-request/quote-request.reducer.spec.ts b/src/app/extensions/quoting/store/quote-request/quote-request.reducer.spec.ts index 72bd75a334..11a7d937c4 100644 --- a/src/app/extensions/quoting/store/quote-request/quote-request.reducer.spec.ts +++ b/src/app/extensions/quoting/store/quote-request/quote-request.reducer.spec.ts @@ -5,47 +5,47 @@ import { QuoteRequestItem } from '../../models/quote-request-item/quote-request- import { QuoteRequestData } from '../../models/quote-request/quote-request.interface'; import { - AddBasketToQuoteRequest, - AddBasketToQuoteRequestFail, - AddBasketToQuoteRequestSuccess, - AddProductToQuoteRequest, - AddProductToQuoteRequestFail, - AddProductToQuoteRequestSuccess, - AddQuoteRequest, - AddQuoteRequestFail, - AddQuoteRequestSuccess, - CreateQuoteRequestFromQuoteRequest, - CreateQuoteRequestFromQuoteRequestFail, - CreateQuoteRequestFromQuoteRequestSuccess, - DeleteItemFromQuoteRequest, - DeleteItemFromQuoteRequestFail, - DeleteItemFromQuoteRequestSuccess, - DeleteQuoteRequest, - DeleteQuoteRequestFail, - DeleteQuoteRequestSuccess, - LoadQuoteRequestItems, - LoadQuoteRequestItemsFail, - LoadQuoteRequestItemsSuccess, - LoadQuoteRequests, - LoadQuoteRequestsFail, - LoadQuoteRequestsSuccess, - SelectQuoteRequest, - SubmitQuoteRequest, - SubmitQuoteRequestFail, - SubmitQuoteRequestSuccess, - UpdateQuoteRequest, - UpdateQuoteRequestFail, - UpdateQuoteRequestItems, - UpdateQuoteRequestItemsFail, - UpdateQuoteRequestItemsSuccess, - UpdateQuoteRequestSuccess, + addBasketToQuoteRequest, + addBasketToQuoteRequestFail, + addBasketToQuoteRequestSuccess, + addProductToQuoteRequest, + addProductToQuoteRequestFail, + addProductToQuoteRequestSuccess, + addQuoteRequest, + addQuoteRequestFail, + addQuoteRequestSuccess, + createQuoteRequestFromQuoteRequest, + createQuoteRequestFromQuoteRequestFail, + createQuoteRequestFromQuoteRequestSuccess, + deleteItemFromQuoteRequest, + deleteItemFromQuoteRequestFail, + deleteItemFromQuoteRequestSuccess, + deleteQuoteRequest, + deleteQuoteRequestFail, + deleteQuoteRequestSuccess, + loadQuoteRequestItems, + loadQuoteRequestItemsFail, + loadQuoteRequestItemsSuccess, + loadQuoteRequests, + loadQuoteRequestsFail, + loadQuoteRequestsSuccess, + selectQuoteRequest, + submitQuoteRequest, + submitQuoteRequestFail, + submitQuoteRequestSuccess, + updateQuoteRequest, + updateQuoteRequestFail, + updateQuoteRequestItems, + updateQuoteRequestItemsFail, + updateQuoteRequestItemsSuccess, + updateQuoteRequestSuccess, } from './quote-request.actions'; import { initialState, quoteRequestReducer } from './quote-request.reducer'; describe('Quote Request Reducer', () => { describe('SelectQuoteRequest', () => { it('should select a quote request when reduced', () => { - const action = new SelectQuoteRequest({ id: 'test' }); + const action = selectQuoteRequest({ id: 'test' }); const state = quoteRequestReducer(initialState, action); expect(state.selected).toEqual('test'); @@ -55,7 +55,7 @@ describe('Quote Request Reducer', () => { describe('LoadQuoteRequests actions', () => { describe('LoadQuoteRequests action', () => { it('should set loading to true', () => { - const action = new LoadQuoteRequests(); + const action = loadQuoteRequests(); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -65,7 +65,7 @@ describe('Quote Request Reducer', () => { describe('LoadQuoteRequestsFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new LoadQuoteRequestsFail({ error }); + const action = loadQuoteRequestsFail({ error }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -81,7 +81,7 @@ describe('Quote Request Reducer', () => { } as QuoteRequestData, ]; - const action = new LoadQuoteRequestsSuccess({ quoteRequests }); + const action = loadQuoteRequestsSuccess({ quoteRequests }); const state = quoteRequestReducer(initialState, action); expect(state.ids).toEqual(['test']); @@ -94,7 +94,7 @@ describe('Quote Request Reducer', () => { describe('AddQuoteRequest actions', () => { describe('AddQuoteRequest action', () => { it('should set loading to true', () => { - const action = new AddQuoteRequest(); + const action = addQuoteRequest(); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -104,7 +104,7 @@ describe('Quote Request Reducer', () => { describe('AddQuoteRequestFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new AddQuoteRequestFail({ error }); + const action = addQuoteRequestFail({ error }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -114,7 +114,7 @@ describe('Quote Request Reducer', () => { describe('AddQuoteRequestSuccess action', () => { it('should set loading to false', () => { - const action = new AddQuoteRequestSuccess({ id: 'test' }); + const action = addQuoteRequestSuccess({ id: 'test' }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -126,7 +126,7 @@ describe('Quote Request Reducer', () => { describe('UpdateQuoteRequest action', () => { it('should set loading to true', () => { const displayName = 'test'; - const action = new UpdateQuoteRequest({ displayName }); + const action = updateQuoteRequest({ displayName }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -136,7 +136,7 @@ describe('Quote Request Reducer', () => { describe('UpdateQuoteRequestFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new UpdateQuoteRequestFail({ error }); + const action = updateQuoteRequestFail({ error }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -147,7 +147,7 @@ describe('Quote Request Reducer', () => { describe('UpdateQuoteRequestSuccess action', () => { it('should set loading to false', () => { const quoteRequest = { id: 'test' } as QuoteRequestData; - const action = new UpdateQuoteRequestSuccess({ quoteRequest }); + const action = updateQuoteRequestSuccess({ quoteRequest }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -159,7 +159,7 @@ describe('Quote Request Reducer', () => { describe('DeleteQuoteRequest action', () => { it('should set loading to true', () => { const id = 'test'; - const action = new DeleteQuoteRequest({ id }); + const action = deleteQuoteRequest({ id }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -169,7 +169,7 @@ describe('Quote Request Reducer', () => { describe('DeleteQuoteRequestFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new DeleteQuoteRequestFail({ error }); + const action = deleteQuoteRequestFail({ error }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -180,7 +180,7 @@ describe('Quote Request Reducer', () => { describe('DeleteQuoteRequestSuccess action', () => { it('should set loading to false', () => { const id = 'test'; - const action = new DeleteQuoteRequestSuccess({ id }); + const action = deleteQuoteRequestSuccess({ id }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -191,7 +191,7 @@ describe('Quote Request Reducer', () => { describe('SubmitQuoteRequest actions', () => { describe('SubmitQuoteRequest action', () => { it('should set loading to true', () => { - const action = new SubmitQuoteRequest(); + const action = submitQuoteRequest(); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -201,7 +201,7 @@ describe('Quote Request Reducer', () => { describe('SubmitQuoteRequestFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new SubmitQuoteRequestFail({ error }); + const action = submitQuoteRequestFail({ error }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -212,7 +212,7 @@ describe('Quote Request Reducer', () => { describe('SubmitQuoteRequestSuccess action', () => { it('should set loading to false', () => { const id = 'test'; - const action = new SubmitQuoteRequestSuccess({ id }); + const action = submitQuoteRequestSuccess({ id }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -223,7 +223,7 @@ describe('Quote Request Reducer', () => { describe('CreateQuoteRequestFromQuoteRequest actions', () => { describe('CreateQuoteRequestFromQuoteRequest action', () => { it('should set loading to true', () => { - const action = new CreateQuoteRequestFromQuoteRequest({}); + const action = createQuoteRequestFromQuoteRequest({}); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -233,7 +233,7 @@ describe('Quote Request Reducer', () => { describe('CreateQuoteRequestFromQuoteRequestFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new CreateQuoteRequestFromQuoteRequestFail({ error }); + const action = createQuoteRequestFromQuoteRequestFail({ error }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -244,7 +244,7 @@ describe('Quote Request Reducer', () => { describe('CreateQuoteRequestFromQuoteRequestSuccess action', () => { it('should set loading to false', () => { const quoteLineItemResult = {} as QuoteLineItemResult; - const action = new CreateQuoteRequestFromQuoteRequestSuccess({ quoteLineItemResult }); + const action = createQuoteRequestFromQuoteRequestSuccess({ quoteLineItemResult }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -256,7 +256,7 @@ describe('Quote Request Reducer', () => { describe('LoadQuoteRequestItems action', () => { it('should set loading to true', () => { const id = 'test'; - const action = new LoadQuoteRequestItems({ id }); + const action = loadQuoteRequestItems({ id }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -266,7 +266,7 @@ describe('Quote Request Reducer', () => { describe('LoadQuoteRequestItemsFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new LoadQuoteRequestItemsFail({ error }); + const action = loadQuoteRequestItemsFail({ error }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -284,7 +284,7 @@ describe('Quote Request Reducer', () => { ], }; - const action = new LoadQuoteRequestItemsSuccess(quoteRequestItems); + const action = loadQuoteRequestItemsSuccess(quoteRequestItems); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -301,7 +301,7 @@ describe('Quote Request Reducer', () => { sku: 'test', quantity: 1, }; - const action = new AddProductToQuoteRequest(payload); + const action = addProductToQuoteRequest(payload); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -311,7 +311,7 @@ describe('Quote Request Reducer', () => { describe('AddProductToQuoteRequestFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new AddProductToQuoteRequestFail({ error }); + const action = addProductToQuoteRequestFail({ error }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -322,7 +322,7 @@ describe('Quote Request Reducer', () => { describe('AddProductToQuoteRequestSuccess action', () => { it('should set loading to false', () => { const id = 'test'; - const action = new AddProductToQuoteRequestSuccess({ id }); + const action = addProductToQuoteRequestSuccess({ id }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -333,7 +333,7 @@ describe('Quote Request Reducer', () => { describe('AddBasketToQuoteRequest actions', () => { describe('AddBasketToQuoteRequest action', () => { it('should set loading to true', () => { - const action = new AddBasketToQuoteRequest(); + const action = addBasketToQuoteRequest(); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -343,7 +343,7 @@ describe('Quote Request Reducer', () => { describe('AddBasketToQuoteRequestFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new AddBasketToQuoteRequestFail({ error }); + const action = addBasketToQuoteRequestFail({ error }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -353,7 +353,7 @@ describe('Quote Request Reducer', () => { describe('AddBasketToQuoteRequestSuccess action', () => { it('should set loading to false', () => { - const action = new AddBasketToQuoteRequestSuccess({ id: 'QRID' }); + const action = addBasketToQuoteRequestSuccess({ id: 'QRID' }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -365,7 +365,7 @@ describe('Quote Request Reducer', () => { describe('UpdateQuoteRequestItems action', () => { it('should set loading to true', () => { const lineItemUpdates = [{ itemId: 'test', quantity: 1 }]; - const action = new UpdateQuoteRequestItems({ lineItemUpdates }); + const action = updateQuoteRequestItems({ lineItemUpdates }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -375,7 +375,7 @@ describe('Quote Request Reducer', () => { describe('UpdateQuoteRequestItemsFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new UpdateQuoteRequestItemsFail({ error }); + const action = updateQuoteRequestItemsFail({ error }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -386,7 +386,7 @@ describe('Quote Request Reducer', () => { describe('UpdateQuoteRequestItemsSuccess action', () => { it('should set loading to false', () => { const itemIds = ['test']; - const action = new UpdateQuoteRequestItemsSuccess({ itemIds }); + const action = updateQuoteRequestItemsSuccess({ itemIds }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -401,7 +401,7 @@ describe('Quote Request Reducer', () => { quoteRequestId: 'test', itemId: 'test', }; - const action = new DeleteItemFromQuoteRequest(payload); + const action = deleteItemFromQuoteRequest(payload); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -411,7 +411,7 @@ describe('Quote Request Reducer', () => { describe('DeleteItemFromQuoteRequestFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new DeleteItemFromQuoteRequestFail({ error }); + const action = deleteItemFromQuoteRequestFail({ error }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -422,7 +422,7 @@ describe('Quote Request Reducer', () => { describe('DeleteItemFromQuoteRequestSuccess action', () => { it('should set loading to false', () => { const id = 'test'; - const action = new DeleteItemFromQuoteRequestSuccess({ id }); + const action = deleteItemFromQuoteRequestSuccess({ id }); const state = quoteRequestReducer(initialState, action); expect(state.loading).toBeFalse(); diff --git a/src/app/extensions/quoting/store/quote-request/quote-request.reducer.ts b/src/app/extensions/quoting/store/quote-request/quote-request.reducer.ts index 9dce5ca4b7..8a650ad2fc 100644 --- a/src/app/extensions/quoting/store/quote-request/quote-request.reducer.ts +++ b/src/app/extensions/quoting/store/quote-request/quote-request.reducer.ts @@ -1,11 +1,49 @@ import { EntityState, createEntityAdapter } from '@ngrx/entity'; +import { createReducer, on } from '@ngrx/store'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; +import { setErrorOn, setLoadingOn } from 'ish-core/utils/ngrx-creators'; import { QuoteRequestItem } from '../../models/quote-request-item/quote-request-item.model'; import { QuoteRequestData } from '../../models/quote-request/quote-request.interface'; -import { QuoteAction, QuoteRequestActionTypes } from './quote-request.actions'; +import { + addBasketToQuoteRequest, + addBasketToQuoteRequestFail, + addBasketToQuoteRequestSuccess, + addProductToQuoteRequest, + addProductToQuoteRequestFail, + addProductToQuoteRequestSuccess, + addQuoteRequest, + addQuoteRequestFail, + addQuoteRequestSuccess, + createQuoteRequestFromQuoteRequest, + createQuoteRequestFromQuoteRequestFail, + createQuoteRequestFromQuoteRequestSuccess, + deleteItemFromQuoteRequest, + deleteItemFromQuoteRequestFail, + deleteItemFromQuoteRequestSuccess, + deleteQuoteRequest, + deleteQuoteRequestFail, + deleteQuoteRequestSuccess, + loadQuoteRequestItems, + loadQuoteRequestItemsFail, + loadQuoteRequestItemsSuccess, + loadQuoteRequests, + loadQuoteRequestsFail, + loadQuoteRequestsSuccess, + selectQuoteRequest, + submitQuoteRequest, + submitQuoteRequestFail, + submitQuoteRequestSuccess, + updateQuoteRequest, + updateQuoteRequestFail, + updateQuoteRequestItems, + updateQuoteRequestItemsFail, + updateQuoteRequestItemsSuccess, + updateQuoteRequestSuccess, + updateSubmitQuoteRequest, +} from './quote-request.actions'; export const quoteRequestAdapter = createEntityAdapter(); @@ -23,92 +61,74 @@ export const initialState: QuoteRequestState = quoteRequestAdapter.getInitialSta selected: undefined, }); -export function quoteRequestReducer(state = initialState, action: QuoteAction): QuoteRequestState { - switch (action.type) { - case QuoteRequestActionTypes.SelectQuoteRequest: { - return { - ...state, - selected: action.payload.id, - }; +export const quoteRequestReducer = createReducer( + initialState, + on(selectQuoteRequest, (state: QuoteRequestState, action) => ({ + ...state, + selected: action.payload.id, + })), + setLoadingOn( + loadQuoteRequests, + addQuoteRequest, + updateQuoteRequest, + deleteQuoteRequest, + submitQuoteRequest, + updateSubmitQuoteRequest, + createQuoteRequestFromQuoteRequest, + loadQuoteRequestItems, + addProductToQuoteRequest, + addBasketToQuoteRequest, + updateQuoteRequestItems, + deleteItemFromQuoteRequest + ), + setErrorOn( + loadQuoteRequestsFail, + addQuoteRequestFail, + updateQuoteRequestFail, + deleteQuoteRequestFail, + submitQuoteRequestFail, + createQuoteRequestFromQuoteRequestFail, + loadQuoteRequestItemsFail, + addProductToQuoteRequestFail, + addBasketToQuoteRequestFail, + updateQuoteRequestItemsFail, + deleteItemFromQuoteRequestFail + ), + on(loadQuoteRequestsSuccess, (state: QuoteRequestState, action) => { + const quoteRequests = action.payload.quoteRequests; + + if (!state) { + return; } - case QuoteRequestActionTypes.LoadQuoteRequests: - case QuoteRequestActionTypes.AddQuoteRequest: - case QuoteRequestActionTypes.UpdateQuoteRequest: - case QuoteRequestActionTypes.DeleteQuoteRequest: - case QuoteRequestActionTypes.SubmitQuoteRequest: - case QuoteRequestActionTypes.UpdateSubmitQuoteRequest: - case QuoteRequestActionTypes.CreateQuoteRequestFromQuoteRequest: - case QuoteRequestActionTypes.LoadQuoteRequestItems: - case QuoteRequestActionTypes.AddProductToQuoteRequest: - case QuoteRequestActionTypes.AddBasketToQuoteRequest: - case QuoteRequestActionTypes.UpdateQuoteRequestItems: - case QuoteRequestActionTypes.DeleteItemFromQuoteRequest: { - return { - ...state, - loading: true, - }; - } - - case QuoteRequestActionTypes.LoadQuoteRequestsFail: - case QuoteRequestActionTypes.AddQuoteRequestFail: - case QuoteRequestActionTypes.UpdateQuoteRequestFail: - case QuoteRequestActionTypes.DeleteQuoteRequestFail: - case QuoteRequestActionTypes.SubmitQuoteRequestFail: - case QuoteRequestActionTypes.CreateQuoteRequestFromQuoteRequestFail: - case QuoteRequestActionTypes.LoadQuoteRequestItemsFail: - case QuoteRequestActionTypes.AddProductToQuoteRequestFail: - case QuoteRequestActionTypes.AddBasketToQuoteRequestFail: - case QuoteRequestActionTypes.UpdateQuoteRequestItemsFail: - case QuoteRequestActionTypes.DeleteItemFromQuoteRequestFail: { - const error = action.payload.error; - - return { - ...state, - error, - loading: false, - }; - } - - case QuoteRequestActionTypes.LoadQuoteRequestsSuccess: { - const quoteRequests = action.payload.quoteRequests; - - if (!state) { - return; - } - - return { - ...quoteRequestAdapter.setAll(quoteRequests, state), - loading: false, - }; - } - - case QuoteRequestActionTypes.LoadQuoteRequestItemsSuccess: { - const quoteRequestItems = action.payload.quoteRequestItems; - - return { - ...state, - quoteRequestItems, - loading: false, - }; - } - - case QuoteRequestActionTypes.AddQuoteRequestSuccess: - case QuoteRequestActionTypes.UpdateQuoteRequestSuccess: - case QuoteRequestActionTypes.DeleteQuoteRequestSuccess: - case QuoteRequestActionTypes.SubmitQuoteRequestSuccess: - case QuoteRequestActionTypes.CreateQuoteRequestFromQuoteRequestSuccess: - case QuoteRequestActionTypes.AddProductToQuoteRequestSuccess: - case QuoteRequestActionTypes.AddBasketToQuoteRequestSuccess: - case QuoteRequestActionTypes.UpdateQuoteRequestItemsSuccess: - case QuoteRequestActionTypes.DeleteItemFromQuoteRequestSuccess: { - return { - ...state, - loading: false, - error: undefined, - }; - } - } - - return state; -} + return { + ...quoteRequestAdapter.setAll(quoteRequests, state), + loading: false, + }; + }), + on(loadQuoteRequestItemsSuccess, (state: QuoteRequestState, action) => { + const quoteRequestItems = action.payload.quoteRequestItems; + + return { + ...state, + quoteRequestItems, + loading: false, + }; + }), + on( + addQuoteRequestSuccess, + updateQuoteRequestSuccess, + deleteQuoteRequestSuccess, + submitQuoteRequestSuccess, + createQuoteRequestFromQuoteRequestSuccess, + addProductToQuoteRequestSuccess, + addBasketToQuoteRequestSuccess, + updateQuoteRequestItemsSuccess, + deleteItemFromQuoteRequestSuccess, + (state: QuoteRequestState) => ({ + ...state, + loading: false, + error: undefined, + }) + ) +); diff --git a/src/app/extensions/quoting/store/quote-request/quote-request.selectors.spec.ts b/src/app/extensions/quoting/store/quote-request/quote-request.selectors.spec.ts index 6fc3b1a231..9bb2766f94 100644 --- a/src/app/extensions/quoting/store/quote-request/quote-request.selectors.spec.ts +++ b/src/app/extensions/quoting/store/quote-request/quote-request.selectors.spec.ts @@ -3,7 +3,7 @@ import { TestBed } from '@angular/core/testing'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { Product } from 'ish-core/models/product/product.model'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; -import { LoadProductSuccess } from 'ish-core/store/shopping/products'; +import { loadProductSuccess } from 'ish-core/store/shopping/products'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; @@ -12,13 +12,13 @@ import { QuoteRequestData } from '../../models/quote-request/quote-request.inter import { QuotingStoreModule } from '../quoting-store.module'; import { - LoadQuoteRequestItems, - LoadQuoteRequestItemsFail, - LoadQuoteRequestItemsSuccess, - LoadQuoteRequests, - LoadQuoteRequestsFail, - LoadQuoteRequestsSuccess, - SelectQuoteRequest, + loadQuoteRequestItems, + loadQuoteRequestItemsFail, + loadQuoteRequestItemsSuccess, + loadQuoteRequests, + loadQuoteRequestsFail, + loadQuoteRequestsSuccess, + selectQuoteRequest, } from './quote-request.actions'; import { getActiveQuoteRequest, @@ -56,18 +56,18 @@ describe('Quote Request Selectors', () => { describe('selecting a quote request', () => { beforeEach(() => { store$.dispatch( - new LoadQuoteRequestsSuccess({ + loadQuoteRequestsSuccess({ quoteRequests: [ { id: 'test', items: [] }, { id: 'test2', editable: true, items: [] }, ] as QuoteRequestData[], }) ); - store$.dispatch(new LoadProductSuccess({ product: { sku: 'test' } as Product })); + store$.dispatch(loadProductSuccess({ product: { sku: 'test' } as Product })); store$.dispatch( - new LoadQuoteRequestItemsSuccess({ quoteRequestItems: [{ productSKU: 'test' }] as QuoteRequestItem[] }) + loadQuoteRequestItemsSuccess({ quoteRequestItems: [{ productSKU: 'test' }] as QuoteRequestItem[] }) ); - store$.dispatch(new SelectQuoteRequest({ id: 'test' })); + store$.dispatch(selectQuoteRequest({ id: 'test' })); }); it('should set "selected" to selected quote item id and set selected quote request', () => { @@ -93,7 +93,7 @@ describe('Quote Request Selectors', () => { describe('loading quote request list', () => { beforeEach(() => { - store$.dispatch(new LoadQuoteRequests()); + store$.dispatch(loadQuoteRequests()); }); it('should set the state to loading', () => { @@ -105,14 +105,14 @@ describe('Quote Request Selectors', () => { { id: 'test', items: [] }, { id: 'test2', editable: true, items: [], state: 'New' }, ] as QuoteRequestData[]; - store$.dispatch(new LoadQuoteRequestsSuccess({ quoteRequests })); + store$.dispatch(loadQuoteRequestsSuccess({ quoteRequests })); expect(getQuoteRequestLoading(store$.state)).toBeFalse(); expect(getActiveQuoteRequest(store$.state)).toEqual(quoteRequests[1]); }); it('should set loading to false and set error state', () => { - store$.dispatch(new LoadQuoteRequestsFail({ error: { message: 'invalid' } as HttpError })); + store$.dispatch(loadQuoteRequestsFail({ error: { message: 'invalid' } as HttpError })); expect(getQuoteRequestLoading(store$.state)).toBeFalse(); expect(getQuoteRequestError(store$.state)).toEqual({ message: 'invalid' }); }); @@ -120,7 +120,7 @@ describe('Quote Request Selectors', () => { describe('loading quote request item list', () => { beforeEach(() => { - store$.dispatch(new LoadQuoteRequestItems({ id: 'test' })); + store$.dispatch(loadQuoteRequestItems({ id: 'test' })); }); it('should set the state to loading', () => { @@ -129,7 +129,7 @@ describe('Quote Request Selectors', () => { it('should set loading to false and set quote state', () => { const quoteRequestItems = [{ productSKU: 'test' }] as QuoteRequestItem[]; - store$.dispatch(new LoadQuoteRequestItemsSuccess({ quoteRequestItems })); + store$.dispatch(loadQuoteRequestItemsSuccess({ quoteRequestItems })); expect(getQuoteRequestLoading(store$.state)).toBeFalse(); expect(getQuoteRequestItemsWithProducts(store$.state)).toEqual(quoteRequestItems); @@ -137,7 +137,7 @@ describe('Quote Request Selectors', () => { }); it('should set loading to false and set error state', () => { - store$.dispatch(new LoadQuoteRequestItemsFail({ error: { message: 'invalid' } as HttpError })); + store$.dispatch(loadQuoteRequestItemsFail({ error: { message: 'invalid' } as HttpError })); expect(getQuoteRequestLoading(store$.state)).toBeFalse(); expect(getQuoteRequestItemsWithProducts(store$.state)).toBeEmpty(); expect(getQuoteRequestError(store$.state)).toEqual({ message: 'invalid' }); @@ -150,10 +150,10 @@ describe('Quote Request Selectors', () => { { id: 'test', items: [] }, { id: 'test2', editable: true, items: [{ title: 'item1' }], state: 'New' }, ] as QuoteRequestData[]; - store$.dispatch(new LoadQuoteRequestsSuccess({ quoteRequests })); + store$.dispatch(loadQuoteRequestsSuccess({ quoteRequests })); const quoteRequestItems = [{ id: 'item1', productSKU: 'test' }] as QuoteRequestItem[]; - store$.dispatch(new LoadQuoteRequestItemsSuccess({ quoteRequestItems })); - store$.dispatch(new LoadProductSuccess({ product: { sku: 'test' } as Product })); + store$.dispatch(loadQuoteRequestItemsSuccess({ quoteRequestItems })); + store$.dispatch(loadProductSuccess({ product: { sku: 'test' } as Product })); }); it('should have a product on the active quote request', () => { diff --git a/src/app/extensions/quoting/store/quote/quote.actions.ts b/src/app/extensions/quoting/store/quote/quote.actions.ts index f270c9993e..6f9de96ee6 100644 --- a/src/app/extensions/quoting/store/quote/quote.actions.ts +++ b/src/app/extensions/quoting/store/quote/quote.actions.ts @@ -1,127 +1,53 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; -import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { Link } from 'ish-core/models/link/link.model'; +import { httpError, payload } from 'ish-core/utils/ngrx-creators'; import { QuoteLineItemResult } from '../../models/quote-line-item-result/quote-line-item-result.model'; import { QuoteData } from '../../models/quote/quote.interface'; -export enum QuoteActionTypes { - SelectQuote = '[Quote] Select Quote', - LoadQuotes = '[Quote Internal] Load Quotes', - LoadQuotesFail = '[Quote API] Load Quotes Fail', - LoadQuotesSuccess = '[Quote API] Load Quotes Success', - DeleteQuote = '[Quote] Delete Quote', - DeleteQuoteFail = '[Quote API] Delete Quote Fail', - DeleteQuoteSuccess = '[Quote API] Delete Quote Success', - RejectQuote = '[Quote] Reject Quote', - RejectQuoteFail = '[Quote API] Reject Quote Fail', - RejectQuoteSuccess = '[Quote API] Reject Quote Success', - CreateQuoteRequestFromQuote = '[Quote] Create Quote Request from Quote', - CreateQuoteRequestFromQuoteFail = '[Quote API] Create Quote Request from Quote Fail', - CreateQuoteRequestFromQuoteSuccess = '[Quote API] Create Quote Request from Quote Success', - AddQuoteToBasket = '[Basket] Add Quote To Basket', - AddQuoteToBasketFail = '[Basket API] Add Quote To Basket Fail', - AddQuoteToBasketSuccess = '[Basket API] Add Quote To Basket Success', - ResetQuoteError = '[Quote] Reset Quote Error', -} - -export class SelectQuote implements Action { - readonly type = QuoteActionTypes.SelectQuote; - constructor(public payload: { id: string }) {} -} - -export class LoadQuotes implements Action { - readonly type = QuoteActionTypes.LoadQuotes; -} - -export class LoadQuotesFail implements Action { - readonly type = QuoteActionTypes.LoadQuotesFail; - constructor(public payload: { error: HttpError }) {} -} - -export class LoadQuotesSuccess implements Action { - readonly type = QuoteActionTypes.LoadQuotesSuccess; - constructor(public payload: { quotes: QuoteData[] }) {} -} - -export class DeleteQuote implements Action { - readonly type = QuoteActionTypes.DeleteQuote; - constructor(public payload: { id: string }) {} -} - -export class DeleteQuoteFail implements Action { - readonly type = QuoteActionTypes.DeleteQuoteFail; - constructor(public payload: { error: HttpError }) {} -} - -export class DeleteQuoteSuccess implements Action { - readonly type = QuoteActionTypes.DeleteQuoteSuccess; - constructor(public payload: { id: string }) {} -} - -export class RejectQuote implements Action { - readonly type = QuoteActionTypes.RejectQuote; -} - -export class RejectQuoteFail implements Action { - readonly type = QuoteActionTypes.RejectQuoteFail; - constructor(public payload: { error: HttpError }) {} -} - -export class RejectQuoteSuccess implements Action { - readonly type = QuoteActionTypes.RejectQuoteSuccess; - constructor(public payload: { id: string }) {} -} - -export class CreateQuoteRequestFromQuote implements Action { - readonly type = QuoteActionTypes.CreateQuoteRequestFromQuote; -} - -export class CreateQuoteRequestFromQuoteFail implements Action { - readonly type = QuoteActionTypes.CreateQuoteRequestFromQuoteFail; - constructor(public payload: { error: HttpError }) {} -} - -export class CreateQuoteRequestFromQuoteSuccess implements Action { - readonly type = QuoteActionTypes.CreateQuoteRequestFromQuoteSuccess; - constructor(public payload: { quoteLineItemRequest: QuoteLineItemResult }) {} -} - -export class AddQuoteToBasket implements Action { - readonly type = QuoteActionTypes.AddQuoteToBasket; - constructor(public payload: { quoteId: string; basketId?: string }) {} -} - -export class AddQuoteToBasketFail implements Action { - readonly type = QuoteActionTypes.AddQuoteToBasketFail; - constructor(public payload: { error: HttpError }) {} -} - -export class AddQuoteToBasketSuccess implements Action { - readonly type = QuoteActionTypes.AddQuoteToBasketSuccess; - constructor(public payload: { link: Link }) {} -} - -export class ResetQuoteError implements Action { - readonly type = QuoteActionTypes.ResetQuoteError; -} - -export type QuoteAction = - | SelectQuote - | LoadQuotes - | LoadQuotesFail - | LoadQuotesSuccess - | DeleteQuote - | DeleteQuoteFail - | DeleteQuoteSuccess - | RejectQuote - | RejectQuoteFail - | RejectQuoteSuccess - | CreateQuoteRequestFromQuote - | CreateQuoteRequestFromQuoteFail - | CreateQuoteRequestFromQuoteSuccess - | AddQuoteToBasket - | AddQuoteToBasketFail - | AddQuoteToBasketSuccess - | ResetQuoteError; +export const selectQuote = createAction('[Quote] Select Quote', payload<{ id: string }>()); + +export const loadQuotes = createAction('[Quote Internal] Load Quotes'); + +export const loadQuotesFail = createAction('[Quote API] Load Quotes Fail', httpError()); + +export const loadQuotesSuccess = createAction('[Quote API] Load Quotes Success', payload<{ quotes: QuoteData[] }>()); + +export const deleteQuote = createAction('[Quote] Delete Quote', payload<{ id: string }>()); + +export const deleteQuoteFail = createAction('[Quote API] Delete Quote Fail', httpError()); + +export const deleteQuoteSuccess = createAction('[Quote API] Delete Quote Success', payload<{ id: string }>()); + +export const rejectQuote = createAction('[Quote] Reject Quote'); + +export const rejectQuoteFail = createAction('[Quote API] Reject Quote Fail', httpError()); + +export const rejectQuoteSuccess = createAction('[Quote API] Reject Quote Success', payload<{ id: string }>()); + +export const createQuoteRequestFromQuote = createAction('[Quote] Create Quote Request from Quote'); + +export const createQuoteRequestFromQuoteFail = createAction( + '[Quote API] Create Quote Request from Quote Fail', + httpError() +); + +export const createQuoteRequestFromQuoteSuccess = createAction( + '[Quote API] Create Quote Request from Quote Success', + payload<{ quoteLineItemRequest: QuoteLineItemResult }>() +); + +export const addQuoteToBasket = createAction( + '[Basket] Add Quote To Basket', + payload<{ quoteId: string; basketId?: string }>() +); + +export const addQuoteToBasketFail = createAction('[Basket API] Add Quote To Basket Fail', httpError()); + +export const addQuoteToBasketSuccess = createAction( + '[Basket API] Add Quote To Basket Success', + payload<{ link: Link }>() +); + +export const resetQuoteError = createAction('[Quote] Reset Quote Error'); diff --git a/src/app/extensions/quoting/store/quote/quote.effects.spec.ts b/src/app/extensions/quoting/store/quote/quote.effects.spec.ts index 802e59e061..eef9126384 100644 --- a/src/app/extensions/quoting/store/quote/quote.effects.spec.ts +++ b/src/app/extensions/quoting/store/quote/quote.effects.spec.ts @@ -17,35 +17,35 @@ import { Link } from 'ish-core/models/link/link.model'; import { User } from 'ish-core/models/user/user.model'; import { BasketService } from 'ish-core/services/basket/basket.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; -import { LoadBasketSuccess } from 'ish-core/store/customer/basket'; +import { loadBasketSuccess } from 'ish-core/store/customer/basket'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; -import { LoadCompanyUserSuccess, LoginUserSuccess } from 'ish-core/store/customer/user'; +import { loadCompanyUserSuccess, loginUserSuccess } from 'ish-core/store/customer/user'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { QuoteLineItemResult } from '../../models/quote-line-item-result/quote-line-item-result.model'; import { QuoteRequestItem } from '../../models/quote-request-item/quote-request-item.model'; import { QuoteData } from '../../models/quote/quote.interface'; import { QuoteService } from '../../services/quote/quote.service'; -import { SubmitQuoteRequestSuccess } from '../quote-request'; +import { submitQuoteRequestSuccess } from '../quote-request'; import { QuotingStoreModule } from '../quoting-store.module'; import { - AddQuoteToBasket, - AddQuoteToBasketFail, - AddQuoteToBasketSuccess, - CreateQuoteRequestFromQuote, - CreateQuoteRequestFromQuoteFail, - CreateQuoteRequestFromQuoteSuccess, - DeleteQuote, - DeleteQuoteFail, - DeleteQuoteSuccess, - LoadQuotes, - LoadQuotesFail, - LoadQuotesSuccess, - RejectQuote, - RejectQuoteFail, - RejectQuoteSuccess, - SelectQuote, + addQuoteToBasket, + addQuoteToBasketFail, + addQuoteToBasketSuccess, + createQuoteRequestFromQuote, + createQuoteRequestFromQuoteFail, + createQuoteRequestFromQuoteSuccess, + deleteQuote, + deleteQuoteFail, + deleteQuoteSuccess, + loadQuotes, + loadQuotesFail, + loadQuotesSuccess, + rejectQuote, + rejectQuoteFail, + rejectQuoteSuccess, + selectQuote, } from './quote.actions'; import { QuoteEffects } from './quote.effects'; @@ -92,8 +92,8 @@ describe('Quote Effects', () => { store$ = TestBed.inject(Store); location = TestBed.inject(Location); - store$.dispatch(new LoginUserSuccess({ customer })); - store$.dispatch(new LoadCompanyUserSuccess({ user: { email: 'test' } as User })); + store$.dispatch(loginUserSuccess({ customer })); + store$.dispatch(loadCompanyUserSuccess({ user: { email: 'test' } as User })); })); describe('loadQuotes$', () => { @@ -102,7 +102,7 @@ describe('Quote Effects', () => { }); it('should call the quoteService for getQuotes', done => { - const action = new LoadQuotes(); + const action = loadQuotes(); actions$ = of(action); effects.loadQuotes$.subscribe(() => { @@ -112,8 +112,8 @@ describe('Quote Effects', () => { }); it('should map to action of type LoadQuotesSuccess', () => { - const action = new LoadQuotes(); - const completion = new LoadQuotesSuccess({ quotes: [{ id: 'QID' } as QuoteData] }); + const action = loadQuotes(); + const completion = loadQuotesSuccess({ quotes: [{ id: 'QID' } as QuoteData] }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -123,8 +123,8 @@ describe('Quote Effects', () => { it('should map invalid request to action of type LoadQuotesFail', () => { when(quoteServiceMock.getQuotes()).thenReturn(throwError({ message: 'invalid' })); - const action = new LoadQuotes(); - const completion = new LoadQuotesFail({ error: { message: 'invalid' } as HttpError }); + const action = loadQuotes(); + const completion = loadQuotesFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -139,7 +139,7 @@ describe('Quote Effects', () => { it('should call the quoteService for deleteQuote with specific quoteId', done => { const id = 'QID'; - const action = new DeleteQuote({ id }); + const action = deleteQuote({ id }); actions$ = of(action); effects.deleteQuote$.subscribe(() => { @@ -150,8 +150,8 @@ describe('Quote Effects', () => { it('should map to action of type DeleteQuoteSuccess', () => { const id = 'QID'; - const action = new DeleteQuote({ id }); - const completion = new DeleteQuoteSuccess({ id }); + const action = deleteQuote({ id }); + const completion = deleteQuoteSuccess({ id }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -161,8 +161,8 @@ describe('Quote Effects', () => { it('should map invalid request to action of type DeleteQuoteFail', () => { when(quoteServiceMock.deleteQuote(anyString())).thenReturn(throwError({ message: 'invalid' })); - const action = new DeleteQuote({ id: 'QID' }); - const completion = new DeleteQuoteFail({ error: { message: 'invalid' } as HttpError }); + const action = deleteQuote({ id: 'QID' }); + const completion = deleteQuoteFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -173,7 +173,7 @@ describe('Quote Effects', () => { describe('rejectQuote$', () => { beforeEach(() => { store$.dispatch( - new LoadQuotesSuccess({ + loadQuotesSuccess({ quotes: [ { id: 'QID', @@ -182,13 +182,13 @@ describe('Quote Effects', () => { ], }) ); - store$.dispatch(new SelectQuote({ id: 'QID' })); + store$.dispatch(selectQuote({ id: 'QID' })); when(quoteServiceMock.rejectQuote(anyString())).thenCall(() => of('QID')); }); it('should call the quoteService for rejectQuote', done => { - const action = new RejectQuote(); + const action = rejectQuote(); actions$ = of(action); effects.rejectQuote$.subscribe(() => { @@ -198,8 +198,8 @@ describe('Quote Effects', () => { }); it('should map to action of type RejectQuoteSuccess', () => { - const action = new RejectQuote(); - const completion = new RejectQuoteSuccess({ id: 'QID' }); + const action = rejectQuote(); + const completion = rejectQuoteSuccess({ id: 'QID' }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -209,8 +209,8 @@ describe('Quote Effects', () => { it('should map invalid request to action of type RejectQuoteFail', () => { when(quoteServiceMock.rejectQuote(anyString())).thenCall(() => throwError({ message: 'invalid' })); - const action = new RejectQuote(); - const completion = new RejectQuoteFail({ error: { message: 'invalid' } as HttpError }); + const action = rejectQuote(); + const completion = rejectQuoteFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -220,10 +220,10 @@ describe('Quote Effects', () => { describe('createQuoteRequestFromQuote$', () => { beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); - store$.dispatch(new LoadCompanyUserSuccess({ user: { email: 'test' } as User })); + store$.dispatch(loginUserSuccess({ customer })); + store$.dispatch(loadCompanyUserSuccess({ user: { email: 'test' } as User })); store$.dispatch( - new LoadQuotesSuccess({ + loadQuotesSuccess({ quotes: [ { id: 'QID', @@ -232,7 +232,7 @@ describe('Quote Effects', () => { ], }) ); - store$.dispatch(new SelectQuote({ id: 'QID' })); + store$.dispatch(selectQuote({ id: 'QID' })); when(quoteServiceMock.createQuoteRequestFromQuote(anything())).thenReturn( of({ type: 'test' } as QuoteLineItemResult) @@ -240,7 +240,7 @@ describe('Quote Effects', () => { }); it('should call the quoteService for createQuoteRequestFromQuote', done => { - const action = new CreateQuoteRequestFromQuote(); + const action = createQuoteRequestFromQuote(); actions$ = of(action); effects.createQuoteRequestFromQuote$.subscribe(() => { @@ -250,8 +250,8 @@ describe('Quote Effects', () => { }); it('should map to action of type CreateQuoteRequestFromQuoteSuccess', () => { - const action = new CreateQuoteRequestFromQuote(); - const completion = new CreateQuoteRequestFromQuoteSuccess({ + const action = createQuoteRequestFromQuote(); + const completion = createQuoteRequestFromQuoteSuccess({ quoteLineItemRequest: { type: 'test', } as QuoteLineItemResult, @@ -265,8 +265,8 @@ describe('Quote Effects', () => { it('should map invalid request to action of type CreateQuoteRequestFromQuoteFail', () => { when(quoteServiceMock.createQuoteRequestFromQuote(anything())).thenReturn(throwError({ message: 'invalid' })); - const action = new CreateQuoteRequestFromQuote(); - const completion = new CreateQuoteRequestFromQuoteFail({ + const action = createQuoteRequestFromQuote(); + const completion = createQuoteRequestFromQuoteFail({ error: { message: 'invalid', } as HttpError, @@ -280,8 +280,8 @@ describe('Quote Effects', () => { describe('loadQuotesAfterChangeSuccess$', () => { it('should map to action of type LoadQuotes if DeleteQuoteSuccess action triggered', () => { - const action = new DeleteQuoteSuccess(anyString()); - const completion = new LoadQuotes(); + const action = deleteQuoteSuccess(anyString()); + const completion = loadQuotes(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -289,8 +289,8 @@ describe('Quote Effects', () => { }); it('should map to action of type LoadQuotes if RejectQuoteSuccess action triggered', () => { - const action = new RejectQuoteSuccess(anyString()); - const completion = new LoadQuotes(); + const action = rejectQuoteSuccess(anyString()); + const completion = loadQuotes(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -298,8 +298,8 @@ describe('Quote Effects', () => { }); it('should map to action of type LoadQuotes if SubmitQuoteRequestSuccess action triggered', () => { - const action = new SubmitQuoteRequestSuccess(anyString()); - const completion = new LoadQuotes(); + const action = submitQuoteRequestSuccess(anyString()); + const completion = loadQuotes(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -307,8 +307,8 @@ describe('Quote Effects', () => { }); it('should map to action of type LoadQuotes if LoadCompanyUserSuccess action triggered', () => { - const action = new LoadCompanyUserSuccess({ user: {} as User }); - const completion = new LoadQuotes(); + const action = loadCompanyUserSuccess({ user: {} as User }); + const completion = loadQuotes(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -320,7 +320,7 @@ describe('Quote Effects', () => { it('should call the basketService for addQuoteToBasket', done => { when(quoteServiceMock.addQuoteToBasket(anyString(), anyString())).thenReturn(of({} as Link)); store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'BID', lineItems: [], @@ -329,7 +329,7 @@ describe('Quote Effects', () => { ); const quoteId = 'QID'; - const action = new AddQuoteToBasket({ quoteId }); + const action = addQuoteToBasket({ quoteId }); actions$ = of(action); effects.addQuoteToBasket$.subscribe(() => { @@ -342,7 +342,7 @@ describe('Quote Effects', () => { when(basketServiceMock.createBasket()).thenReturn(of({} as Basket)); const quoteId = 'quoteId'; - const action = new AddQuoteToBasket({ quoteId }); + const action = addQuoteToBasket({ quoteId }); actions$ = of(action); effects.getBasketBeforeAddQuoteToBasket$.subscribe(() => { @@ -355,7 +355,7 @@ describe('Quote Effects', () => { when(quoteServiceMock.addQuoteToBasket(anyString(), anyString())).thenReturn(of({} as Link)); store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'BID', lineItems: [], @@ -364,8 +364,8 @@ describe('Quote Effects', () => { ); const quoteId = 'QID'; - const action = new AddQuoteToBasket({ quoteId }); - const completion = new AddQuoteToBasketSuccess({ link: {} as Link }); + const action = addQuoteToBasket({ quoteId }); + const completion = addQuoteToBasketSuccess({ link: {} as Link }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -376,7 +376,7 @@ describe('Quote Effects', () => { when(quoteServiceMock.addQuoteToBasket(anyString(), anyString())).thenReturn(throwError({ message: 'invalid' })); store$.dispatch( - new LoadBasketSuccess({ + loadBasketSuccess({ basket: { id: 'BID', lineItems: [], @@ -385,8 +385,8 @@ describe('Quote Effects', () => { ); const quoteId = 'QID'; - const action = new AddQuoteToBasket({ quoteId }); - const completion = new AddQuoteToBasketFail({ error: { message: 'invalid' } as HttpError }); + const action = addQuoteToBasket({ quoteId }); + const completion = addQuoteToBasketFail({ error: { message: 'invalid' } as HttpError }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -396,7 +396,7 @@ describe('Quote Effects', () => { describe('gotoBasketAfterAddQuoteToBasketSuccess$', () => { it('should navigate to basket when success', fakeAsync(() => { - const action = new AddQuoteToBasketSuccess({ link: {} as Link }); + const action = addQuoteToBasketSuccess({ link: {} as Link }); actions$ = of(action); effects.gotoBasketAfterAddQuoteToBasketSuccess$.subscribe(noop, fail, noop); tick(1000); diff --git a/src/app/extensions/quoting/store/quote/quote.effects.ts b/src/app/extensions/quoting/store/quote/quote.effects.ts index 4da68a89c3..00cbf854ba 100644 --- a/src/app/extensions/quoting/store/quote/quote.effects.ts +++ b/src/app/extensions/quoting/store/quote/quote.effects.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; import { combineLatest } from 'rxjs'; @@ -10,31 +10,32 @@ import { FeatureToggleService } from 'ish-core/feature-toggle.module'; import { ProductCompletenessLevel } from 'ish-core/models/product/product.model'; import { BasketService } from 'ish-core/services/basket/basket.service'; import { selectRouteParam } from 'ish-core/store/core/router'; -import { SetBreadcrumbData } from 'ish-core/store/core/viewconf'; -import { UpdateBasket, getCurrentBasketId } from 'ish-core/store/customer/basket'; -import { UserActionTypes } from 'ish-core/store/customer/user'; -import { LoadProductIfNotLoaded } from 'ish-core/store/shopping/products'; +import { setBreadcrumbData } from 'ish-core/store/core/viewconf'; +import { getCurrentBasketId, updateBasket } from 'ish-core/store/customer/basket'; +import { loadCompanyUserSuccess } from 'ish-core/store/customer/user'; +import { loadProductIfNotLoaded } from 'ish-core/store/shopping/products'; import { mapErrorToAction, mapToPayload, mapToPayloadProperty, whenTruthy } from 'ish-core/utils/operators'; import { QuoteService } from '../../services/quote/quote.service'; -import { QuoteRequestActionTypes } from '../quote-request'; +import { submitQuoteRequestSuccess } from '../quote-request'; import { - AddQuoteToBasket, - AddQuoteToBasketFail, - AddQuoteToBasketSuccess, - CreateQuoteRequestFromQuoteFail, - CreateQuoteRequestFromQuoteSuccess, - DeleteQuote, - DeleteQuoteFail, - DeleteQuoteSuccess, - LoadQuotes, - LoadQuotesFail, - LoadQuotesSuccess, - QuoteActionTypes, - RejectQuoteFail, - RejectQuoteSuccess, - SelectQuote, + addQuoteToBasket, + addQuoteToBasketFail, + addQuoteToBasketSuccess, + createQuoteRequestFromQuote, + createQuoteRequestFromQuoteFail, + createQuoteRequestFromQuoteSuccess, + deleteQuote, + deleteQuoteFail, + deleteQuoteSuccess, + loadQuotes, + loadQuotesFail, + loadQuotesSuccess, + rejectQuote, + rejectQuoteFail, + rejectQuoteSuccess, + selectQuote, } from './quote.actions'; import { getSelectedQuote, getSelectedQuoteId, getSelectedQuoteWithProducts } from './quote.selectors'; @@ -53,13 +54,14 @@ export class QuoteEffects { /** * The load quotes effect. */ - @Effect() - loadQuotes$ = this.actions$.pipe( - ofType(QuoteActionTypes.LoadQuotes), - concatMap(() => - this.quoteService.getQuotes().pipe( - map(quotes => new LoadQuotesSuccess({ quotes })), - mapErrorToAction(LoadQuotesFail) + loadQuotes$ = createEffect(() => + this.actions$.pipe( + ofType(loadQuotes), + concatMap(() => + this.quoteService.getQuotes().pipe( + map(quotes => loadQuotesSuccess({ quotes })), + mapErrorToAction(loadQuotesFail) + ) ) ) ); @@ -67,14 +69,15 @@ export class QuoteEffects { /** * Delete quote from a specific user of a specific customer. */ - @Effect() - deleteQuote$ = this.actions$.pipe( - ofType(QuoteActionTypes.DeleteQuote), - mapToPayloadProperty('id'), - concatMap(quoteId => - this.quoteService.deleteQuote(quoteId).pipe( - map(id => new DeleteQuoteSuccess({ id })), - mapErrorToAction(DeleteQuoteFail) + deleteQuote$ = createEffect(() => + this.actions$.pipe( + ofType(deleteQuote), + mapToPayloadProperty('id'), + concatMap(quoteId => + this.quoteService.deleteQuote(quoteId).pipe( + map(id => deleteQuoteSuccess({ id })), + mapErrorToAction(deleteQuoteFail) + ) ) ) ); @@ -82,14 +85,15 @@ export class QuoteEffects { /** * Reject quote from a specific user of a specific customer. */ - @Effect() - rejectQuote$ = this.actions$.pipe( - ofType(QuoteActionTypes.RejectQuote), - withLatestFrom(this.store.pipe(select(getSelectedQuoteId))), - concatMap(([, quoteId]) => - this.quoteService.rejectQuote(quoteId).pipe( - map(id => new RejectQuoteSuccess({ id })), - mapErrorToAction(RejectQuoteFail) + rejectQuote$ = createEffect(() => + this.actions$.pipe( + ofType(rejectQuote), + withLatestFrom(this.store.pipe(select(getSelectedQuoteId))), + concatMap(([, quoteId]) => + this.quoteService.rejectQuote(quoteId).pipe( + map(id => rejectQuoteSuccess({ id })), + mapErrorToAction(rejectQuoteFail) + ) ) ) ); @@ -97,17 +101,18 @@ export class QuoteEffects { /** * Create quote request based on selected quote from a specific user of a specific customer. */ - @Effect() - createQuoteRequestFromQuote$ = this.actions$.pipe( - ofType(QuoteActionTypes.CreateQuoteRequestFromQuote), - withLatestFrom(this.store.pipe(select(getSelectedQuoteWithProducts))), - concatMap(([, currentQuoteRequest]) => - this.quoteService.createQuoteRequestFromQuote(currentQuoteRequest).pipe( - map(quoteLineItemRequest => new CreateQuoteRequestFromQuoteSuccess({ quoteLineItemRequest })), - tap(quoteLineItemResult => - this.router.navigate([`/account/quotes/request/${quoteLineItemResult.payload.quoteLineItemRequest.title}`]) - ), - mapErrorToAction(CreateQuoteRequestFromQuoteFail) + createQuoteRequestFromQuote$ = createEffect(() => + this.actions$.pipe( + ofType(createQuoteRequestFromQuote), + withLatestFrom(this.store.pipe(select(getSelectedQuoteWithProducts))), + concatMap(([, currentQuoteRequest]) => + this.quoteService.createQuoteRequestFromQuote(currentQuoteRequest).pipe( + map(quoteLineItemRequest => createQuoteRequestFromQuoteSuccess({ quoteLineItemRequest })), + tap(quoteLineItemResult => + this.router.navigate([`/account/quotes/request/${quoteLineItemResult.payload.quoteLineItemRequest.title}`]) + ), + mapErrorToAction(createQuoteRequestFromQuoteFail) + ) ) ) ); @@ -115,59 +120,58 @@ export class QuoteEffects { /** * Triggers a LoadQuotes action after successful quote related interaction with the Quote API. */ - @Effect() - loadQuotesAfterChangeSuccess$ = this.actions$.pipe( - ofType( - QuoteActionTypes.DeleteQuoteSuccess, - QuoteActionTypes.RejectQuoteSuccess, - QuoteRequestActionTypes.SubmitQuoteRequestSuccess, - UserActionTypes.LoadCompanyUserSuccess - ), - filter(() => this.featureToggleService.enabled('quoting')), - mapTo(new LoadQuotes()) + loadQuotesAfterChangeSuccess$ = createEffect(() => + this.actions$.pipe( + ofType(deleteQuoteSuccess, rejectQuoteSuccess, submitQuoteRequestSuccess, loadCompanyUserSuccess), + filter(() => this.featureToggleService.enabled('quoting')), + mapTo(loadQuotes()) + ) ); /** * Triggers a SelectQuote action if route contains quoteId parameter */ - @Effect() - routeListenerForSelectingQuote$ = this.store.pipe( - select(selectRouteParam('quoteId')), - map(id => new SelectQuote({ id })) + routeListenerForSelectingQuote$ = createEffect(() => + this.store.pipe( + select(selectRouteParam('quoteId')), + map(id => selectQuote({ id })) + ) ); /** * After selecting and successfully loading quote, trigger a LoadProduct action * for each product that is missing in the current product entities state. */ - @Effect() - loadProductsForSelectedQuote$ = combineLatest([ - this.actions$.pipe(ofType(QuoteActionTypes.SelectQuote), mapToPayloadProperty('id')), - this.actions$.pipe(ofType(QuoteActionTypes.LoadQuotesSuccess), mapToPayloadProperty('quotes')), - ]).pipe( - map(([quoteId, quotes]) => quotes.filter(quote => quote.id === quoteId).pop()), - whenTruthy(), - concatMap(quote => [ - ...quote.items.map( - ({ productSKU }) => new LoadProductIfNotLoaded({ sku: productSKU, level: ProductCompletenessLevel.List }) - ), - ]) + loadProductsForSelectedQuote$ = createEffect(() => + combineLatest([ + this.actions$.pipe(ofType(selectQuote), mapToPayloadProperty('id')), + this.actions$.pipe(ofType(loadQuotesSuccess), mapToPayloadProperty('quotes')), + ]).pipe( + map(([quoteId, quotes]) => quotes.filter(quote => quote.id === quoteId).pop()), + whenTruthy(), + concatMap(quote => [ + ...quote.items.map(({ productSKU }) => + loadProductIfNotLoaded({ sku: productSKU, level: ProductCompletenessLevel.List }) + ), + ]) + ) ); /** * Add quote to the current basket. * Only triggers if the user has a basket. */ - @Effect() - addQuoteToBasket$ = this.actions$.pipe( - ofType(QuoteActionTypes.AddQuoteToBasket), - mapToPayload(), - withLatestFrom(this.store.pipe(select(getCurrentBasketId))), - filter(([payload, currentBasketId]) => !!currentBasketId || !!payload.basketId), - concatMap(([payload, currentBasketId]) => - this.quoteService.addQuoteToBasket(payload.quoteId, currentBasketId || payload.basketId).pipe( - map(link => new AddQuoteToBasketSuccess({ link })), - mapErrorToAction(AddQuoteToBasketFail) + addQuoteToBasket$ = createEffect(() => + this.actions$.pipe( + ofType(addQuoteToBasket), + mapToPayload(), + withLatestFrom(this.store.pipe(select(getCurrentBasketId))), + filter(([payload, currentBasketId]) => !!currentBasketId || !!payload.basketId), + concatMap(([payload, currentBasketId]) => + this.quoteService.addQuoteToBasket(payload.quoteId, currentBasketId || payload.basketId).pipe( + map(link => addQuoteToBasketSuccess({ link })), + mapErrorToAction(addQuoteToBasketFail) + ) ) ) ); @@ -176,14 +180,15 @@ export class QuoteEffects { * Get current basket if missing and call AddQuoteToBasketAction * Only triggers if the user has not yet a basket */ - @Effect() - getBasketBeforeAddQuoteToBasket$ = this.actions$.pipe( - ofType(QuoteActionTypes.AddQuoteToBasket), - mapToPayload(), - withLatestFrom(this.store.pipe(select(getCurrentBasketId))), - filter(([payload, basketId]) => !basketId && !payload.basketId), - mergeMap(([{ quoteId }]) => - this.basketService.createBasket().pipe(map(basket => new AddQuoteToBasket({ quoteId, basketId: basket.id }))) + getBasketBeforeAddQuoteToBasket$ = createEffect(() => + this.actions$.pipe( + ofType(addQuoteToBasket), + mapToPayload(), + withLatestFrom(this.store.pipe(select(getCurrentBasketId))), + filter(([payload, basketId]) => !basketId && !payload.basketId), + mergeMap(([{ quoteId }]) => + this.basketService.createBasket().pipe(map(basket => addQuoteToBasket({ quoteId, basketId: basket.id }))) + ) ) ); @@ -191,38 +196,42 @@ export class QuoteEffects { * Triggers a Caluculate Basket action after adding a quote to basket. * ToDo: This is only necessary as long as api v0 is used for addQuote and addPayment */ - @Effect() - calculateBasketAfterAddToQuote = this.actions$.pipe( - ofType(QuoteActionTypes.AddQuoteToBasketSuccess, QuoteActionTypes.AddQuoteToBasketFail), - mapTo(new UpdateBasket({ update: { calculated: true } })) + calculateBasketAfterAddToQuote = createEffect(() => + this.actions$.pipe( + ofType(addQuoteToBasketSuccess, addQuoteToBasketFail), + mapTo(updateBasket({ update: { calculated: true } })) + ) ); /** * Triggers a navigation to the basket if quote successfully added to the basket. */ - @Effect({ dispatch: false }) - gotoBasketAfterAddQuoteToBasketSuccess$ = this.actions$.pipe( - ofType(QuoteActionTypes.AddQuoteToBasketSuccess), - tap(() => { - this.router.navigate(['/basket']); - }) + gotoBasketAfterAddQuoteToBasketSuccess$ = createEffect( + () => + this.actions$.pipe( + ofType(addQuoteToBasketSuccess), + tap(() => { + this.router.navigate(['/basket']); + }) + ), + { dispatch: false } ); - @Effect() - setQuoteRequestBreadcrumb$ = this.store.pipe( - select(getSelectedQuote), - whenTruthy(), - withLatestFrom(this.translateService.get('quote.edit.responded.quote_details.text')), - withLatestFrom(this.translateService.get('quote.edit.unsubmitted.quote_request_details.text')), - map(([[quote, x], y]) => [quote, quote.state === 'Responded' ? x : y]), - map( - ([quote, x]) => - new SetBreadcrumbData({ + setQuoteRequestBreadcrumb$ = createEffect(() => + this.store.pipe( + select(getSelectedQuote), + whenTruthy(), + withLatestFrom(this.translateService.get('quote.edit.responded.quote_details.text')), + withLatestFrom(this.translateService.get('quote.edit.unsubmitted.quote_request_details.text')), + map(([[quote, x], y]) => [quote, quote.state === 'Responded' ? x : y]), + map(([quote, x]) => + setBreadcrumbData({ breadcrumbData: [ { key: 'quote.quotes.link', link: '/account/quotes' }, { text: `${x} - ${quote.displayName}` }, ], }) + ) ) ); } diff --git a/src/app/extensions/quoting/store/quote/quote.reducer.spec.ts b/src/app/extensions/quoting/store/quote/quote.reducer.spec.ts index 1584bd32d6..21c8059cd3 100644 --- a/src/app/extensions/quoting/store/quote/quote.reducer.spec.ts +++ b/src/app/extensions/quoting/store/quote/quote.reducer.spec.ts @@ -5,22 +5,22 @@ import { QuoteLineItemResult } from '../../models/quote-line-item-result/quote-l import { QuoteData } from '../../models/quote/quote.interface'; import { - AddQuoteToBasket, - AddQuoteToBasketFail, - AddQuoteToBasketSuccess, - CreateQuoteRequestFromQuote, - CreateQuoteRequestFromQuoteFail, - CreateQuoteRequestFromQuoteSuccess, - DeleteQuote, - DeleteQuoteFail, - DeleteQuoteSuccess, - LoadQuotes, - LoadQuotesFail, - LoadQuotesSuccess, - RejectQuote, - RejectQuoteFail, - RejectQuoteSuccess, - SelectQuote, + addQuoteToBasket, + addQuoteToBasketFail, + addQuoteToBasketSuccess, + createQuoteRequestFromQuote, + createQuoteRequestFromQuoteFail, + createQuoteRequestFromQuoteSuccess, + deleteQuote, + deleteQuoteFail, + deleteQuoteSuccess, + loadQuotes, + loadQuotesFail, + loadQuotesSuccess, + rejectQuote, + rejectQuoteFail, + rejectQuoteSuccess, + selectQuote, } from './quote.actions'; import { initialState, quoteReducer } from './quote.reducer'; @@ -28,7 +28,7 @@ describe('Quote Reducer', () => { describe('LoadQuotes actions', () => { describe('SelectQuote', () => { it('should select a quote when reduced', () => { - const action = new SelectQuote({ id: 'test' }); + const action = selectQuote({ id: 'test' }); const state = quoteReducer(initialState, action); expect(state.selected).toEqual('test'); @@ -37,7 +37,7 @@ describe('Quote Reducer', () => { describe('LoadQuotes action', () => { it('should set loading to true', () => { - const action = new LoadQuotes(); + const action = loadQuotes(); const state = quoteReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -47,7 +47,7 @@ describe('Quote Reducer', () => { describe('LoadQuotesFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new LoadQuotesFail({ error }); + const action = loadQuotesFail({ error }); const state = quoteReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -65,7 +65,7 @@ describe('Quote Reducer', () => { ], }; - const action = new LoadQuotesSuccess(quotes); + const action = loadQuotesSuccess(quotes); const state = quoteReducer(initialState, action); expect(state.ids).toEqual(['test']); @@ -79,7 +79,7 @@ describe('Quote Reducer', () => { describe('DeleteQuote action', () => { it('should set loading to true', () => { const id = 'test'; - const action = new DeleteQuote({ id }); + const action = deleteQuote({ id }); const state = quoteReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -89,7 +89,7 @@ describe('Quote Reducer', () => { describe('DeleteQuoteFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new DeleteQuoteFail({ error }); + const action = deleteQuoteFail({ error }); const state = quoteReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -100,7 +100,7 @@ describe('Quote Reducer', () => { describe('DeleteQuoteSuccess action', () => { it('should set loading to false', () => { const id = 'test'; - const action = new DeleteQuoteSuccess({ id }); + const action = deleteQuoteSuccess({ id }); const state = quoteReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -111,7 +111,7 @@ describe('Quote Reducer', () => { describe('RejectQuote actions', () => { describe('RejectQuote action', () => { it('should set loading to true', () => { - const action = new RejectQuote(); + const action = rejectQuote(); const state = quoteReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -121,7 +121,7 @@ describe('Quote Reducer', () => { describe('RejectQuoteFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new RejectQuoteFail({ error }); + const action = rejectQuoteFail({ error }); const state = quoteReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -132,7 +132,7 @@ describe('Quote Reducer', () => { describe('RejectQuoteSuccess action', () => { it('should set loading to false', () => { const id = 'test'; - const action = new RejectQuoteSuccess({ id }); + const action = rejectQuoteSuccess({ id }); const state = quoteReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -143,7 +143,7 @@ describe('Quote Reducer', () => { describe('CreateQuoteRequestFromQuote actions', () => { describe('CreateQuoteRequestFromQuote action', () => { it('should set loading to true', () => { - const action = new CreateQuoteRequestFromQuote(); + const action = createQuoteRequestFromQuote(); const state = quoteReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -153,7 +153,7 @@ describe('Quote Reducer', () => { describe('CreateQuoteRequestFromQuoteFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new CreateQuoteRequestFromQuoteFail({ error }); + const action = createQuoteRequestFromQuoteFail({ error }); const state = quoteReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -164,7 +164,7 @@ describe('Quote Reducer', () => { describe('CreateQuoteRequestFromQuoteSuccess action', () => { it('should set loading to false', () => { const quoteLineItemRequest = {} as QuoteLineItemResult; - const action = new CreateQuoteRequestFromQuoteSuccess({ quoteLineItemRequest }); + const action = createQuoteRequestFromQuoteSuccess({ quoteLineItemRequest }); const state = quoteReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -175,7 +175,7 @@ describe('Quote Reducer', () => { describe('AddQuoteToBasket actions', () => { describe('AddQuoteToBasket action', () => { it('should set loading to true', () => { - const action = new AddQuoteToBasket({ quoteId: 'QID' }); + const action = addQuoteToBasket({ quoteId: 'QID' }); const state = quoteReducer(initialState, action); expect(state.loading).toBeTrue(); @@ -185,7 +185,7 @@ describe('Quote Reducer', () => { describe('AddQuoteToBasketFail action', () => { it('should set loading to false', () => { const error = { message: 'invalid' } as HttpError; - const action = new AddQuoteToBasketFail({ error }); + const action = addQuoteToBasketFail({ error }); const state = quoteReducer(initialState, action); expect(state.loading).toBeFalse(); @@ -196,7 +196,7 @@ describe('Quote Reducer', () => { describe('AddQuoteToBasketSuccess action', () => { it('should set loading to false', () => { const link = {} as Link; - const action = new AddQuoteToBasketSuccess({ link }); + const action = addQuoteToBasketSuccess({ link }); const state = quoteReducer(initialState, action); expect(state.loading).toBeFalse(); diff --git a/src/app/extensions/quoting/store/quote/quote.reducer.ts b/src/app/extensions/quoting/store/quote/quote.reducer.ts index 512787150c..ca949a28a6 100644 --- a/src/app/extensions/quoting/store/quote/quote.reducer.ts +++ b/src/app/extensions/quoting/store/quote/quote.reducer.ts @@ -1,10 +1,30 @@ import { EntityState, createEntityAdapter } from '@ngrx/entity'; +import { createReducer, on } from '@ngrx/store'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; +import { setErrorOn, setLoadingOn } from 'ish-core/utils/ngrx-creators'; import { QuoteData } from '../../models/quote/quote.interface'; -import { QuoteAction, QuoteActionTypes } from './quote.actions'; +import { + addQuoteToBasket, + addQuoteToBasketFail, + addQuoteToBasketSuccess, + createQuoteRequestFromQuote, + createQuoteRequestFromQuoteFail, + createQuoteRequestFromQuoteSuccess, + deleteQuote, + deleteQuoteFail, + deleteQuoteSuccess, + loadQuotes, + loadQuotesFail, + loadQuotesSuccess, + rejectQuote, + rejectQuoteFail, + rejectQuoteSuccess, + resetQuoteError, + selectQuote, +} from './quote.actions'; export const quoteAdapter = createEntityAdapter(); @@ -20,69 +40,37 @@ export const initialState: QuoteState = quoteAdapter.getInitialState({ selected: undefined, }); -export function quoteReducer(state = initialState, action: QuoteAction): QuoteState { - switch (action.type) { - case QuoteActionTypes.SelectQuote: { - return { - ...state, - selected: action.payload.id, - }; +export const quoteReducer = createReducer( + initialState, + on(selectQuote, (state: QuoteState, action) => ({ + ...state, + selected: action.payload.id, + })), + setLoadingOn(loadQuotes, deleteQuote, rejectQuote, createQuoteRequestFromQuote, addQuoteToBasket), + setErrorOn(loadQuotesFail, deleteQuoteFail, rejectQuoteFail, createQuoteRequestFromQuoteFail, addQuoteToBasketFail), + on(loadQuotesSuccess, (state: QuoteState, action) => { + const quotes = action.payload.quotes; + if (!state) { + return; } - case QuoteActionTypes.LoadQuotes: - case QuoteActionTypes.DeleteQuote: - case QuoteActionTypes.RejectQuote: - case QuoteActionTypes.CreateQuoteRequestFromQuote: - case QuoteActionTypes.AddQuoteToBasket: { - return { - ...state, - loading: true, - }; - } - - case QuoteActionTypes.LoadQuotesFail: - case QuoteActionTypes.DeleteQuoteFail: - case QuoteActionTypes.RejectQuoteFail: - case QuoteActionTypes.CreateQuoteRequestFromQuoteFail: - case QuoteActionTypes.AddQuoteToBasketFail: { - const error = action.payload.error; - - return { - ...state, - error, - loading: false, - }; - } - - case QuoteActionTypes.LoadQuotesSuccess: { - const quotes = action.payload.quotes; - if (!state) { - return; - } - - return { - ...quoteAdapter.setAll(quotes, state), - loading: false, - }; - } - - case QuoteActionTypes.DeleteQuoteSuccess: - case QuoteActionTypes.RejectQuoteSuccess: - case QuoteActionTypes.CreateQuoteRequestFromQuoteSuccess: - case QuoteActionTypes.AddQuoteToBasketSuccess: { - return { - ...state, - loading: false, - }; - } - - case QuoteActionTypes.ResetQuoteError: { - return { - ...state, - error: undefined, - }; - } - } - - return state; -} + return { + ...quoteAdapter.setAll(quotes, state), + loading: false, + }; + }), + on( + deleteQuoteSuccess, + rejectQuoteSuccess, + createQuoteRequestFromQuoteSuccess, + addQuoteToBasketSuccess, + (state: QuoteState) => ({ + ...state, + loading: false, + }) + ), + on(resetQuoteError, (state: QuoteState) => ({ + ...state, + error: undefined, + })) +); diff --git a/src/app/extensions/quoting/store/quote/quote.selectors.spec.ts b/src/app/extensions/quoting/store/quote/quote.selectors.spec.ts index 8cb20b798d..db6a385d50 100644 --- a/src/app/extensions/quoting/store/quote/quote.selectors.spec.ts +++ b/src/app/extensions/quoting/store/quote/quote.selectors.spec.ts @@ -3,7 +3,7 @@ import { TestBed } from '@angular/core/testing'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { Product } from 'ish-core/models/product/product.model'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; -import { LoadProductSuccess } from 'ish-core/store/shopping/products'; +import { loadProductSuccess } from 'ish-core/store/shopping/products'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ngrx-testing'; @@ -11,7 +11,7 @@ import { QuoteData } from '../../models/quote/quote.interface'; import { Quote } from '../../models/quote/quote.model'; import { QuotingStoreModule } from '../quoting-store.module'; -import { LoadQuotes, LoadQuotesFail, LoadQuotesSuccess, SelectQuote } from './quote.actions'; +import { loadQuotes, loadQuotesFail, loadQuotesSuccess, selectQuote } from './quote.actions'; import { getCurrentQuotes, getQuoteError, @@ -45,15 +45,15 @@ describe('Quote Selectors', () => { describe('selecting a quote', () => { beforeEach(() => { store$.dispatch( - new LoadQuotesSuccess({ + loadQuotesSuccess({ quotes: [ { id: 'test', items: [{ productSKU: 'test' }] }, { id: 'test2', items: [] }, ] as QuoteData[], }) ); - store$.dispatch(new LoadProductSuccess({ product: { sku: 'test' } as Product })); - store$.dispatch(new SelectQuote({ id: 'test' })); + store$.dispatch(loadProductSuccess({ product: { sku: 'test' } as Product })); + store$.dispatch(selectQuote({ id: 'test' })); }); it('should set "selected" to selected quote item id and set selected quote', () => { @@ -75,7 +75,7 @@ describe('Quote Selectors', () => { describe('loading quote list', () => { beforeEach(() => { - store$.dispatch(new LoadQuotes()); + store$.dispatch(loadQuotes()); }); it('should set the state to loading', () => { @@ -84,14 +84,14 @@ describe('Quote Selectors', () => { it('should set loading to false and set quote state', () => { const quotes = { quotes: [{ id: 'test' }] as QuoteData[] }; - store$.dispatch(new LoadQuotesSuccess(quotes)); + store$.dispatch(loadQuotesSuccess(quotes)); expect(getQuoteLoading(store$.state)).toBeFalse(); expect(getCurrentQuotes(store$.state)).toEqual([{ id: 'test', state: 'Responded' } as Quote]); }); it('should set loading to false and set error state', () => { - store$.dispatch(new LoadQuotesFail({ error: { message: 'invalid' } as HttpError })); + store$.dispatch(loadQuotesFail({ error: { message: 'invalid' } as HttpError })); expect(getQuoteLoading(store$.state)).toBeFalse(); expect(getCurrentQuotes(store$.state)).toBeEmpty(); expect(getQuoteError(store$.state)).toEqual({ message: 'invalid' }); diff --git a/src/app/extensions/quoting/store/quoting-store.spec.ts b/src/app/extensions/quoting/store/quoting-store.spec.ts index 918e2f1e6c..e0e681455d 100644 --- a/src/app/extensions/quoting/store/quoting-store.spec.ts +++ b/src/app/extensions/quoting/store/quoting-store.spec.ts @@ -16,11 +16,11 @@ import { CountryService } from 'ish-core/services/country/country.service'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; import { - LoadCompanyUserSuccess, - LoginUserSuccess, - LogoutUser, getLoggedInCustomer, getLoggedInUser, + loadCompanyUserSuccess, + loginUserSuccess, + logoutUser, } from 'ish-core/store/customer/user'; import { ShoppingStoreModule } from 'ish-core/store/shopping/shopping-store.module'; import { @@ -34,12 +34,13 @@ import { QuoteRequestData } from '../models/quote-request/quote-request.interfac import { QuoteRequestService } from '../services/quote-request/quote-request.service'; import { QuoteService } from '../services/quote/quote.service'; -import { QuoteActionTypes, getCurrentQuotes } from './quote'; +import { getCurrentQuotes, loadQuotes, loadQuotesSuccess } from './quote'; import { - AddProductToQuoteRequest, - QuoteRequestActionTypes, + addProductToQuoteRequest, getActiveQuoteRequest, getCurrentQuoteRequests, + loadQuoteRequests, + loadQuoteRequestsSuccess, } from './quote-request'; import { QuotingStoreModule } from './quoting-store.module'; @@ -128,8 +129,8 @@ describe('Quoting Store', () => { return of({ type: 'Link', uri: 'customers/CID/users/UID/quoterequests/' + id, title: id }).pipe(delay(1000)); }); - store$.dispatch(new LoginUserSuccess({ customer, user })); - store$.dispatch(new LoadCompanyUserSuccess({ user })); + store$.dispatch(loginUserSuccess({ customer, user })); + store$.dispatch(loadCompanyUserSuccess({ user })); }); it('should be created', () => { @@ -140,17 +141,12 @@ describe('Quoting Store', () => { it('should load the quotes and quote requests after user login', () => { const firedActions = store$.actionsArray(/Quote/); - expect(firedActions).toSatisfy(containsActionWithType(QuoteActionTypes.LoadQuotes)); - expect(firedActions).toSatisfy(containsActionWithType(QuoteRequestActionTypes.LoadQuoteRequests)); + expect(firedActions).toSatisfy(containsActionWithType(loadQuotes.type)); + expect(firedActions).toSatisfy(containsActionWithType(loadQuoteRequests.type)); + expect(firedActions).toSatisfy(containsActionWithTypeAndPayload(loadQuotesSuccess.type, p => !p.length)); expect(firedActions).toSatisfy( - containsActionWithTypeAndPayload(QuoteActionTypes.LoadQuotesSuccess, p => !p.length) - ); - expect(firedActions).toSatisfy( - containsActionWithTypeAndPayload( - QuoteRequestActionTypes.LoadQuoteRequestsSuccess, - p => !!p.quoteRequests.length - ) + containsActionWithTypeAndPayload(loadQuoteRequestsSuccess.type, p => !!p.quoteRequests.length) ); }); @@ -194,9 +190,9 @@ describe('Quoting Store', () => { store$.reset(); resetCalls(apiServiceMock); - setTimeout(() => store$.dispatch(new AddProductToQuoteRequest({ sku: 'SKU', quantity: 1 })), 400); - setTimeout(() => store$.dispatch(new AddProductToQuoteRequest({ sku: 'SKU', quantity: 1 })), 450); - setTimeout(() => store$.dispatch(new AddProductToQuoteRequest({ sku: 'SKU', quantity: 1 })), 480); + setTimeout(() => store$.dispatch(addProductToQuoteRequest({ sku: 'SKU', quantity: 1 })), 400); + setTimeout(() => store$.dispatch(addProductToQuoteRequest({ sku: 'SKU', quantity: 1 })), 450); + setTimeout(() => store$.dispatch(addProductToQuoteRequest({ sku: 'SKU', quantity: 1 })), 480); }); it('should add all add requests to the same newly aquired quote request', done => @@ -215,7 +211,7 @@ describe('Quoting Store', () => { describe('user logs out', () => { beforeEach(() => { store$.reset(); - store$.dispatch(new LogoutUser()); + store$.dispatch(logoutUser()); }); it('should no longer have any quoting related data after user logout', () => { @@ -227,8 +223,8 @@ describe('Quoting Store', () => { describe('user logs in again', () => { beforeEach(() => { store$.reset(); - store$.dispatch(new LoginUserSuccess({ customer, user })); - store$.dispatch(new LoadCompanyUserSuccess({ user })); + store$.dispatch(loginUserSuccess({ customer, user })); + store$.dispatch(loadCompanyUserSuccess({ user })); }); it('should load all the quotes when logging in again', done => diff --git a/src/app/extensions/sentry/store/sentry-config/sentry-config.actions.ts b/src/app/extensions/sentry/store/sentry-config/sentry-config.actions.ts index 7a7b7c99f1..63722b71bc 100644 --- a/src/app/extensions/sentry/store/sentry-config/sentry-config.actions.ts +++ b/src/app/extensions/sentry/store/sentry-config/sentry-config.actions.ts @@ -1,12 +1,5 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; -export enum SentryConfigActionTypes { - SetSentryConfig = '[Sentry] Set Config', -} +import { payload } from 'ish-core/utils/ngrx-creators'; -export class SetSentryConfig implements Action { - readonly type = SentryConfigActionTypes.SetSentryConfig; - constructor(public payload: { dsn: string }) {} -} - -export type SentryConfigAction = SetSentryConfig; +export const setSentryConfig = createAction('[Sentry] Set Config', payload<{ dsn: string }>()); diff --git a/src/app/extensions/sentry/store/sentry-config/sentry-config.effects.ts b/src/app/extensions/sentry/store/sentry-config/sentry-config.effects.ts index 3484df347f..4c31be3cff 100644 --- a/src/app/extensions/sentry/store/sentry-config/sentry-config.effects.ts +++ b/src/app/extensions/sentry/store/sentry-config/sentry-config.effects.ts @@ -1,7 +1,7 @@ import { isPlatformServer } from '@angular/common'; import { Inject, Injectable, PLATFORM_ID } from '@angular/core'; import { TransferState } from '@angular/platform-browser'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Action, Store, UPDATE, select } from '@ngrx/store'; import * as Sentry from '@sentry/browser'; import { distinctUntilChanged, filter, map, switchMapTo, take, takeWhile, tap, withLatestFrom } from 'rxjs/operators'; @@ -15,7 +15,7 @@ import { getLoggedInUser } from 'ish-core/store/customer/user'; import { mapToProperty, whenTruthy } from 'ish-core/utils/operators'; import { StatePropertiesService } from 'ish-core/utils/state-transfer/state-properties.service'; -import { SentryConfigActionTypes, SetSentryConfig } from './sentry-config.actions'; +import { setSentryConfig } from './sentry-config.actions'; import { getSentryDSN } from './sentry-config.selectors'; @Injectable() @@ -30,83 +30,99 @@ export class SentryConfigEffects { private cookiesService: CookiesService ) {} - @Effect() - setSentryConfig$ = this.actions$.pipe( - takeWhile(() => isPlatformServer(this.platformId) && this.featureToggleService.enabled('sentry')), - ofType<{ features: string[] } & Action>(UPDATE), - filter(action => action.features.includes('sentry')), - take(1), - withLatestFrom(this.stateProperties.getStateOrEnvOrDefault('SENTRY_DSN', 'sentryDSN')), - map(([, sentryDSN]) => sentryDSN), - whenTruthy(), - map(dsn => new SetSentryConfig({ dsn })) + setSentryConfig$ = createEffect(() => + this.actions$.pipe( + takeWhile(() => isPlatformServer(this.platformId) && this.featureToggleService.enabled('sentry')), + ofType<{ features: string[] } & Action>(UPDATE), + filter(action => action.features.includes('sentry')), + take(1), + withLatestFrom(this.stateProperties.getStateOrEnvOrDefault('SENTRY_DSN', 'sentryDSN')), + map(([, sentryDSN]) => sentryDSN), + whenTruthy(), + map(dsn => setSentryConfig({ dsn })) + ) ); - @Effect({ dispatch: false }) - configureSentry$ = this.cookiesService.cookieLawSeen$.pipe( - whenTruthy(), - switchMapTo( - this.actions$.pipe( - ofType(SentryConfigActionTypes.SetSentryConfig), - filter(() => this.featureToggleService.enabled('sentry')), - withLatestFrom(this.store.pipe(select(getSentryDSN))), - map(([, sentryDSN]) => sentryDSN), + configureSentry$ = createEffect( + () => + this.cookiesService.cookieLawSeen$.pipe( whenTruthy(), - tap(dsn => { - const release = this.transferState.get(DISPLAY_VERSION, 'development'); - Sentry.init({ dsn, release }); - }) - ) - ) + switchMapTo( + this.actions$.pipe( + ofType(setSentryConfig), + filter(() => this.featureToggleService.enabled('sentry')), + withLatestFrom(this.store.pipe(select(getSentryDSN))), + map(([, sentryDSN]) => sentryDSN), + whenTruthy(), + tap(dsn => { + const release = this.transferState.get(DISPLAY_VERSION, 'development'); + Sentry.init({ dsn, release }); + }) + ) + ) + ), + { dispatch: false } ); - @Effect({ dispatch: false }) - trackUserLogin$ = this.store.pipe( - select(getLoggedInUser), - mapToProperty('email'), - distinctUntilChanged(), - tap(email => Sentry.configureScope(scope => scope.setUser(email ? { email } : undefined))) + trackUserLogin$ = createEffect( + () => + this.store.pipe( + select(getLoggedInUser), + mapToProperty('email'), + distinctUntilChanged(), + tap(email => Sentry.configureScope(scope => scope.setUser(email ? { email } : undefined))) + ), + { dispatch: false } ); - @Effect({ dispatch: false }) - trackRouting$ = this.store.pipe( - select(selectRouter), - map(payload => JSON.stringify(payload)), - tap(message => Sentry.addBreadcrumb({ category: 'routing', message })) + trackRouting$ = createEffect( + () => + this.store.pipe( + select(selectRouter), + map(payload => JSON.stringify(payload)), + tap(message => Sentry.addBreadcrumb({ category: 'routing', message })) + ), + { dispatch: false } ); - @Effect({ dispatch: false }) - trackErrorPageErrors$ = this.store.pipe( - ofUrl(/^\/error.*/), - select(getGeneralError), - whenTruthy(), - distinctUntilChanged(), - tap(error => { - Sentry.captureEvent({ - level: Sentry.Severity.Error, - message: error.message, - extra: { error }, - tags: { origin: 'Error Page' }, - }); - }) + trackErrorPageErrors$ = createEffect( + () => + this.store.pipe( + ofUrl(/^\/error.*/), + select(getGeneralError), + whenTruthy(), + distinctUntilChanged(), + tap(error => { + Sentry.captureEvent({ + level: Sentry.Severity.Error, + message: error.message, + extra: { error }, + tags: { origin: 'Error Page' }, + }); + }) + ), + { dispatch: false } ); - @Effect({ dispatch: false }) - trackFailActions$ = this.actions$.pipe( - filter<{ payload: { error: Error } } & Action>(action => action.type.endsWith('Fail')), - tap(action => { - const err = action.payload.error; - Sentry.captureEvent({ - level: Sentry.Severity.Error, - message: err.message, - extra: { - error: err, - action, - }, - tags: { - origin: action.type, - }, - }); - }) + trackFailActions$ = createEffect( + () => + this.actions$.pipe( + filter<{ payload: { error: Error } } & Action>(action => action.type.endsWith('Fail')), + tap(action => { + const err = action.payload.error; + Sentry.captureEvent({ + level: Sentry.Severity.Error, + message: err.message, + extra: { + error: err, + action, + }, + tags: { + origin: action.type, + }, + }); + }) + ), + { dispatch: false } ); } diff --git a/src/app/extensions/sentry/store/sentry-config/sentry-config.reducer.ts b/src/app/extensions/sentry/store/sentry-config/sentry-config.reducer.ts index 985cc4b40f..f4beb9fe83 100644 --- a/src/app/extensions/sentry/store/sentry-config/sentry-config.reducer.ts +++ b/src/app/extensions/sentry/store/sentry-config/sentry-config.reducer.ts @@ -1,4 +1,6 @@ -import { SentryConfigAction, SentryConfigActionTypes } from './sentry-config.actions'; +import { createReducer, on } from '@ngrx/store'; + +import { setSentryConfig } from './sentry-config.actions'; export interface SentryConfigState { dsn: string; @@ -8,15 +10,10 @@ export const initialState: SentryConfigState = { dsn: undefined, }; -export function sentryConfigReducer(state = initialState, action: SentryConfigAction): SentryConfigState { - switch (action.type) { - case SentryConfigActionTypes.SetSentryConfig: { - return { - ...state, - ...action.payload, - }; - } - } - - return state; -} +export const sentryConfigReducer = createReducer( + initialState, + on(setSentryConfig, (state: SentryConfigState, action) => ({ + ...state, + ...action.payload, + })) +); diff --git a/src/app/extensions/seo/guards/meta.guard.ts b/src/app/extensions/seo/guards/meta.guard.ts index be5857d230..23194145ec 100644 --- a/src/app/extensions/seo/guards/meta.guard.ts +++ b/src/app/extensions/seo/guards/meta.guard.ts @@ -4,7 +4,7 @@ import { Store } from '@ngrx/store'; import { SeoAttributes } from 'ish-core/models/seo-attributes/seo-attributes.model'; -import { SetSeoAttributes } from '../store/seo'; +import { setSeoAttributes } from '../store/seo'; const defaults: SeoAttributes = { title: 'seo.defaults.title', @@ -19,7 +19,7 @@ export class MetaGuard implements CanActivate, CanActivateChild { canActivate(route: ActivatedRouteSnapshot, _: RouterStateSnapshot): boolean { const metaSettings = route.hasOwnProperty('data') ? route.data.meta : undefined; - this.store.dispatch(new SetSeoAttributes({ ...defaults, ...metaSettings })); + this.store.dispatch(setSeoAttributes({ ...defaults, ...metaSettings })); return true; } diff --git a/src/app/extensions/seo/store/seo/seo.actions.ts b/src/app/extensions/seo/store/seo/seo.actions.ts index d00c6ac5f0..e72a8df2d9 100644 --- a/src/app/extensions/seo/store/seo/seo.actions.ts +++ b/src/app/extensions/seo/store/seo/seo.actions.ts @@ -1,13 +1,6 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; import { SeoAttributes } from 'ish-core/models/seo-attributes/seo-attributes.model'; +import { payload } from 'ish-core/utils/ngrx-creators'; -export enum SeoActionTypes { - SetSeoAttributes = '[SEO] Set Attributes', -} - -export class SetSeoAttributes implements Action { - readonly type = SeoActionTypes.SetSeoAttributes; - // tslint:disable-next-line:ngrx-use-complex-type-with-action-payload - constructor(public payload: Partial) {} -} +export const setSeoAttributes = createAction('[SEO] Set Attributes', payload>()); diff --git a/src/app/extensions/seo/store/seo/seo.effects.spec.ts b/src/app/extensions/seo/store/seo/seo.effects.spec.ts index 5913f15b90..9c1bf86d8d 100644 --- a/src/app/extensions/seo/store/seo/seo.effects.spec.ts +++ b/src/app/extensions/seo/store/seo/seo.effects.spec.ts @@ -7,7 +7,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { Observable, of } from 'rxjs'; import { anyString, instance, mock, verify, when } from 'ts-mockito'; -import { SetSeoAttributes } from './seo.actions'; +import { setSeoAttributes } from './seo.actions'; import { SeoEffects } from './seo.effects'; describe('Seo Effects', () => { @@ -34,7 +34,7 @@ describe('Seo Effects', () => { describe('setMetaData$', () => { it('should call the metaService to setup meta data', done => { - const action = new SetSeoAttributes({ + const action = setSeoAttributes({ title: 'dummy', description: 'dummy desc', robots: 'index, follow', diff --git a/src/app/extensions/seo/store/seo/seo.effects.ts b/src/app/extensions/seo/store/seo/seo.effects.ts index e574ebac04..55b6fcb721 100644 --- a/src/app/extensions/seo/store/seo/seo.effects.ts +++ b/src/app/extensions/seo/store/seo/seo.effects.ts @@ -1,6 +1,6 @@ import { DOCUMENT, isPlatformServer } from '@angular/common'; import { ApplicationRef, Inject, Injectable, Optional, PLATFORM_ID } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { routerNavigatedAction } from '@ngrx/router-store'; import { Store, select } from '@ngrx/store'; import { REQUEST } from '@nguniversal/express-engine/tokens'; @@ -30,7 +30,7 @@ import { getSelectedCategory } from 'ish-core/store/shopping/categories/categori import { getSelectedProduct } from 'ish-core/store/shopping/products'; import { mapToPayload, mapToProperty, whenTruthy } from 'ish-core/utils/operators'; -import { SeoActionTypes, SetSeoAttributes } from './seo.actions'; +import { setSeoAttributes } from './seo.actions'; @Injectable() export class SeoEffects { @@ -61,123 +61,139 @@ export class SeoEffects { this.ogImageDefault = `${this.baseURL}assets/img/og-image-default.jpg`; } - @Effect({ dispatch: false }) - seoCanonicalLink$ = this.actions$.pipe( - ofType(routerNavigatedAction), - tap(() => { - let canonicalLink = this.doc.querySelector('link[rel="canonical"]'); - if (!canonicalLink) { - canonicalLink = this.doc.createElement('link'); - canonicalLink.setAttribute('rel', 'canonical'); - this.doc.head.appendChild(canonicalLink); - } - canonicalLink.setAttribute('href', this.doc.URL); - this.meta.setTag('og:url', this.doc.URL); - }) - ); - - @Effect({ dispatch: false }) - setMetaData$ = this.actions$.pipe( - ofType(SeoActionTypes.SetSeoAttributes), - mapToPayload(), - whenTruthy(), - tap((seoAttributes: SeoAttributes) => { - if (seoAttributes) { - this.meta.setTitle(seoAttributes.title); - this.meta.setTag('og:image', seoAttributes['og:image'] || this.ogImageDefault); - Object.keys(seoAttributes) - .filter(key => key !== 'title' && key !== 'og:image') - .forEach(key => { - this.meta.setTag(key, seoAttributes[key]); - }); - } - }) + seoCanonicalLink$ = createEffect( + () => + this.actions$.pipe( + ofType(routerNavigatedAction), + tap(() => { + let canonicalLink = this.doc.querySelector('link[rel="canonical"]'); + if (!canonicalLink) { + canonicalLink = this.doc.createElement('link'); + canonicalLink.setAttribute('rel', 'canonical'); + this.doc.head.appendChild(canonicalLink); + } + canonicalLink.setAttribute('href', this.doc.URL); + this.meta.setTag('og:url', this.doc.URL); + }) + ), + { dispatch: false } ); - @Effect() - seoCategory$ = this.waitAppStable( - this.store.pipe( - ofCategoryUrl(), - select(getSelectedCategory), - filter(CategoryHelper.isCategoryCompletelyLoaded), - map( - c => - c && - c.seoAttributes && { - canonical: `category/${c.uniqueId}`, - ...c.seoAttributes, + setMetaData$ = createEffect( + () => + this.actions$.pipe( + ofType(setSeoAttributes), + mapToPayload(), + whenTruthy(), + tap((seoAttributes: SeoAttributes) => { + if (seoAttributes) { + this.meta.setTitle(seoAttributes.title); + this.meta.setTag('og:image', seoAttributes['og:image'] || this.ogImageDefault); + Object.keys(seoAttributes) + .filter(key => key !== 'title' && key !== 'og:image') + .forEach(key => { + this.meta.setTag(key, seoAttributes[key]); + }); } + }) ), - whenTruthy(), - map(x => new SetSeoAttributes(x)) - ) + { dispatch: false } ); - @Effect() - seoProduct$ = this.waitAppStable( - this.store.pipe( - ofProductUrl(), - select(getSelectedProduct), - filter(p => ProductHelper.isSufficientlyLoaded(p, ProductCompletenessLevel.Detail)), - filter(p => !ProductHelper.isFailedLoading(p)), - map(p => (ProductHelper.isVariationProduct(p) && p.productMaster()) || p), - distinctUntilKeyChanged('sku'), - map(p => { - const productImage = ProductHelper.getPrimaryImage(p, 'L'); - const seoAttributes = { - canonical: generateProductUrl(p), - 'og:image': productImage && productImage.effectiveUrl, - }; - return p.seoAttributes ? { ...seoAttributes, ...p.seoAttributes } : seoAttributes; - }), - whenTruthy(), - map(x => new SetSeoAttributes(x)) + seoCategory$ = createEffect(() => + this.waitAppStable( + this.store.pipe( + ofCategoryUrl(), + select(getSelectedCategory), + filter(CategoryHelper.isCategoryCompletelyLoaded), + map( + c => + c && + c.seoAttributes && { + canonical: `category/${c.uniqueId}`, + ...c.seoAttributes, + } + ), + whenTruthy(), + map(setSeoAttributes) + ) ) ); - @Effect() - seoSearch$ = this.waitAppStable( - this.store.pipe( - ofUrl(/^\/search.*/), - select(selectRouteParam('searchTerm')), - switchMap(searchTerm => this.translate.get('seo.title.search', { 0: searchTerm })), - whenTruthy(), - map(title => new SetSeoAttributes({ title })) + seoProduct$ = createEffect(() => + this.waitAppStable( + this.store.pipe( + ofProductUrl(), + select(getSelectedProduct), + filter(p => ProductHelper.isSufficientlyLoaded(p, ProductCompletenessLevel.Detail)), + filter(p => !ProductHelper.isFailedLoading(p)), + map(p => (ProductHelper.isVariationProduct(p) && p.productMaster()) || p), + distinctUntilKeyChanged('sku'), + map(p => { + const productImage = ProductHelper.getPrimaryImage(p, 'L'); + const seoAttributes = { + canonical: generateProductUrl(p), + 'og:image': productImage && productImage.effectiveUrl, + }; + return p.seoAttributes ? { ...seoAttributes, ...p.seoAttributes } : seoAttributes; + }), + whenTruthy(), + map(setSeoAttributes) + ) ) ); - @Effect() - seoContentPage$ = this.waitAppStable( - this.store.pipe( - ofUrl(/^\/page.*/), - select(getSelectedContentPage), - mapToProperty('displayName'), - whenTruthy(), - distinctUntilChanged(), - map(title => new SetSeoAttributes({ title })) + seoSearch$ = createEffect(() => + this.waitAppStable( + this.store.pipe( + ofUrl(/^\/search.*/), + select(selectRouteParam('searchTerm')), + switchMap(searchTerm => this.translate.get('seo.title.search', { 0: searchTerm })), + whenTruthy(), + map(title => setSeoAttributes({ title })) + ) ) ); - @Effect({ dispatch: false }) - seoLanguage$ = this.waitAppStable( - this.store.pipe( - select(getCurrentLocale), - whenTruthy(), - tap(current => { - this.meta.setTag('og:locale', current.lang); - }) + seoContentPage$ = createEffect(() => + this.waitAppStable( + this.store.pipe( + ofUrl(/^\/page.*/), + select(getSelectedContentPage), + mapToProperty('displayName'), + whenTruthy(), + distinctUntilChanged(), + map(title => setSeoAttributes({ title })) + ) ) ); - @Effect({ dispatch: false }) - seoAlternateLanguages$ = this.waitAppStable( - this.store.pipe( - select(getAvailableLocales), - whenTruthy(), - tap(locales => { - this.meta.setTag('og:locale:alternate', locales.map(x => x.lang).join(',')); - }) - ) + seoLanguage$ = createEffect( + () => + this.waitAppStable( + this.store.pipe( + select(getCurrentLocale), + whenTruthy(), + tap(current => { + this.meta.setTag('og:locale', current.lang); + }) + ) + ), + { dispatch: false } + ); + + seoAlternateLanguages$ = createEffect( + () => + this.waitAppStable( + this.store.pipe( + select(getAvailableLocales), + whenTruthy(), + tap(locales => { + this.meta.setTag('og:locale:alternate', locales.map(x => x.lang).join(',')); + }) + ) + ), + { dispatch: false } ); private waitAppStable(obs: Observable) { diff --git a/src/app/extensions/wishlists/facades/wishlists.facade.ts b/src/app/extensions/wishlists/facades/wishlists.facade.ts index 08ed02a780..40b9f3bd34 100644 --- a/src/app/extensions/wishlists/facades/wishlists.facade.ts +++ b/src/app/extensions/wishlists/facades/wishlists.facade.ts @@ -6,19 +6,19 @@ import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { Wishlist, WishlistHeader } from '../models/wishlist/wishlist.model'; import { - AddProductToNewWishlist, - AddProductToWishlist, - CreateWishlist, - DeleteWishlist, - MoveItemToWishlist, - RemoveItemFromWishlist, - UpdateWishlist, + addProductToNewWishlist, + addProductToWishlist, + createWishlist, + deleteWishlist, getAllWishlists, getAllWishlistsItemsSkus, getPreferredWishlist, getSelectedWishlistDetails, getWishlistsError, getWishlistsLoading, + moveItemToWishlist, + removeItemFromWishlist, + updateWishlist, } from '../store/wishlist'; // tslint:disable:member-ordering @@ -34,36 +34,36 @@ export class WishlistsFacade { wishlistError$: Observable = this.store.pipe(select(getWishlistsError)); addWishlist(wishlist: WishlistHeader): void | HttpError { - this.store.dispatch(new CreateWishlist({ wishlist })); + this.store.dispatch(createWishlist({ wishlist })); } deleteWishlist(id: string): void { - this.store.dispatch(new DeleteWishlist({ wishlistId: id })); + this.store.dispatch(deleteWishlist({ wishlistId: id })); } updateWishlist(wishlist: Wishlist): void { - this.store.dispatch(new UpdateWishlist({ wishlist })); + this.store.dispatch(updateWishlist({ wishlist })); } addProductToNewWishlist(title: string, sku: string): void { - this.store.dispatch(new AddProductToNewWishlist({ title, sku })); + this.store.dispatch(addProductToNewWishlist({ title, sku })); } addProductToWishlist(wishlistId: string, sku: string, quantity?: number): void { - this.store.dispatch(new AddProductToWishlist({ wishlistId, sku, quantity })); + this.store.dispatch(addProductToWishlist({ wishlistId, sku, quantity })); } moveItemToWishlist(sourceWishlistId: string, targetWishlistId: string, sku: string): void { this.store.dispatch( - new MoveItemToWishlist({ source: { id: sourceWishlistId }, target: { id: targetWishlistId, sku } }) + moveItemToWishlist({ source: { id: sourceWishlistId }, target: { id: targetWishlistId, sku } }) ); } moveItemToNewWishlist(sourceWishlistId: string, title: string, sku: string): void { - this.store.dispatch(new MoveItemToWishlist({ source: { id: sourceWishlistId }, target: { title, sku } })); + this.store.dispatch(moveItemToWishlist({ source: { id: sourceWishlistId }, target: { title, sku } })); } removeProductFromWishlist(wishlistId: string, sku: string): void { - this.store.dispatch(new RemoveItemFromWishlist({ wishlistId, sku })); + this.store.dispatch(removeItemFromWishlist({ wishlistId, sku })); } } diff --git a/src/app/extensions/wishlists/store/wishlist/wishlist.actions.ts b/src/app/extensions/wishlists/store/wishlist/wishlist.actions.ts index 97a36443c1..adfbb1a92a 100644 --- a/src/app/extensions/wishlists/store/wishlist/wishlist.actions.ts +++ b/src/app/extensions/wishlists/store/wishlist/wishlist.actions.ts @@ -1,165 +1,77 @@ -import { Action } from '@ngrx/store'; +import { createAction } from '@ngrx/store'; -import { HttpError } from 'ish-core/models/http-error/http-error.model'; +import { httpError, payload } from 'ish-core/utils/ngrx-creators'; import { Wishlist, WishlistHeader } from '../../models/wishlist/wishlist.model'; -export enum WishlistsActionTypes { - LoadWishlists = '[Wishlists Internal] Load Wishlists', - LoadWishlistsSuccess = '[Wishlists API] Load Wishlists Success', - LoadWishlistsFail = '[Wishlists API] Load Wishlists Fail', - - CreateWishlist = '[Wishlists] Create Wishlist', - CreateWishlistSuccess = '[Wishlists API] Create Wishlist Success', - CreateWishlistFail = '[Wishlists API] Create Wishlist Fail', - - UpdateWishlist = '[Wishlists] Update Wishlist', - UpdateWishlistSuccess = '[Wishlists API] Update Wishlist Success', - UpdateWishlistFail = '[Wishlists API] Update Wishlist Fail', - - DeleteWishlist = '[Wishlists] Delete Wishlist', - DeleteWishlistSuccess = '[Wishlists API] Delete Wishlist Success', - DeleteWishlistFail = '[Wishlists API] Delete Wishlist Fail', - - AddProductToWishlist = '[Wishlists] Add Item to Wishlist', - AddProductToWishlistSuccess = '[Wishlists API] Add Item to Wishlist Success', - AddProductToWishlistFail = '[Wishlists API] Add Item to Wishlist Fail', - - AddProductToNewWishlist = '[Wishlists Internal] Add Product To New Wishlist', - - MoveItemToWishlist = '[Wishlists] Move Item to another Wishlist', - - RemoveItemFromWishlist = '[Wishlists] Remove Item from Wishlist', - RemoveItemFromWishlistSuccess = '[Wishlists API] Remove Item from Wishlist Success', - RemoveItemFromWishlistFail = '[Wishlists API] Remove Item from Wishlist Fail', - - SelectWishlist = '[Wishlists Internal] Select Wishlist', -} - -export class LoadWishlists implements Action { - readonly type = WishlistsActionTypes.LoadWishlists; -} - -export class LoadWishlistsSuccess implements Action { - readonly type = WishlistsActionTypes.LoadWishlistsSuccess; - constructor(public payload: { wishlists: Wishlist[] }) {} -} - -export class LoadWishlistsFail implements Action { - readonly type = WishlistsActionTypes.LoadWishlistsFail; - constructor(public payload: { error: HttpError }) {} -} - -export class CreateWishlist implements Action { - readonly type = WishlistsActionTypes.CreateWishlist; - constructor(public payload: { wishlist: WishlistHeader }) {} -} - -export class CreateWishlistSuccess implements Action { - readonly type = WishlistsActionTypes.CreateWishlistSuccess; - constructor(public payload: { wishlist: Wishlist }) {} -} - -export class CreateWishlistFail implements Action { - readonly type = WishlistsActionTypes.CreateWishlistFail; - constructor(public payload: { error: HttpError }) {} -} - -export class UpdateWishlist implements Action { - readonly type = WishlistsActionTypes.UpdateWishlist; - constructor(public payload: { wishlist: Wishlist }) {} -} - -export class UpdateWishlistSuccess implements Action { - readonly type = WishlistsActionTypes.UpdateWishlistSuccess; - constructor(public payload: { wishlist: Wishlist }) {} -} - -export class UpdateWishlistFail implements Action { - readonly type = WishlistsActionTypes.UpdateWishlistFail; - constructor(public payload: { error: HttpError }) {} -} - -export class DeleteWishlist implements Action { - readonly type = WishlistsActionTypes.DeleteWishlist; - constructor(public payload: { wishlistId: string }) {} -} - -export class DeleteWishlistSuccess implements Action { - readonly type = WishlistsActionTypes.DeleteWishlistSuccess; - - constructor(public payload: { wishlistId: string }) {} -} - -export class DeleteWishlistFail implements Action { - readonly type = WishlistsActionTypes.DeleteWishlistFail; - constructor(public payload: { error: HttpError }) {} -} - -export class AddProductToWishlist implements Action { - readonly type = WishlistsActionTypes.AddProductToWishlist; - constructor(public payload: { wishlistId: string; sku: string; quantity?: number }) {} -} - -export class AddProductToWishlistSuccess implements Action { - readonly type = WishlistsActionTypes.AddProductToWishlistSuccess; - constructor(public payload: { wishlist: Wishlist }) {} -} - -export class AddProductToWishlistFail implements Action { - readonly type = WishlistsActionTypes.AddProductToWishlistFail; - constructor(public payload: { error: HttpError }) {} -} - -export class AddProductToNewWishlist implements Action { - readonly type = WishlistsActionTypes.AddProductToNewWishlist; - constructor(public payload: { title: string; sku: string }) {} -} - -export class MoveItemToWishlist implements Action { - readonly type = WishlistsActionTypes.MoveItemToWishlist; - constructor(public payload: { source: { id: string }; target: { id?: string; title?: string; sku: string } }) {} -} - -export class RemoveItemFromWishlist implements Action { - readonly type = WishlistsActionTypes.RemoveItemFromWishlist; - constructor(public payload: { wishlistId: string; sku: string }) {} -} - -export class RemoveItemFromWishlistSuccess implements Action { - readonly type = WishlistsActionTypes.RemoveItemFromWishlistSuccess; - constructor(public payload: { wishlist: Wishlist }) {} -} - -export class RemoveItemFromWishlistFail implements Action { - readonly type = WishlistsActionTypes.RemoveItemFromWishlistFail; - constructor(public payload: { error: HttpError }) {} -} - -export class SelectWishlist implements Action { - readonly type = WishlistsActionTypes.SelectWishlist; - constructor(public payload: { id: string }) {} -} - -export type WishlistsAction = - | LoadWishlists - | LoadWishlistsSuccess - | LoadWishlistsFail - | CreateWishlist - | CreateWishlistSuccess - | CreateWishlistFail - | UpdateWishlist - | UpdateWishlistSuccess - | UpdateWishlistFail - | DeleteWishlist - | DeleteWishlistSuccess - | DeleteWishlistFail - | AddProductToWishlist - | AddProductToWishlistSuccess - | AddProductToWishlistFail - | AddProductToNewWishlist - | MoveItemToWishlist - | RemoveItemFromWishlist - | RemoveItemFromWishlistSuccess - | RemoveItemFromWishlistFail - | SelectWishlist; +export const loadWishlists = createAction('[Wishlists Internal] Load Wishlists'); + +export const loadWishlistsSuccess = createAction( + '[Wishlists API] Load Wishlists Success', + payload<{ wishlists: Wishlist[] }>() +); + +export const loadWishlistsFail = createAction('[Wishlists API] Load Wishlists Fail', httpError()); + +export const createWishlist = createAction('[Wishlists] Create Wishlist', payload<{ wishlist: WishlistHeader }>()); + +export const createWishlistSuccess = createAction( + '[Wishlists API] Create Wishlist Success', + payload<{ wishlist: Wishlist }>() +); + +export const createWishlistFail = createAction('[Wishlists API] Create Wishlist Fail', httpError()); + +export const updateWishlist = createAction('[Wishlists] Update Wishlist', payload<{ wishlist: Wishlist }>()); + +export const updateWishlistSuccess = createAction( + '[Wishlists API] Update Wishlist Success', + payload<{ wishlist: Wishlist }>() +); + +export const updateWishlistFail = createAction('[Wishlists API] Update Wishlist Fail', httpError()); + +export const deleteWishlist = createAction('[Wishlists] Delete Wishlist', payload<{ wishlistId: string }>()); + +export const deleteWishlistSuccess = createAction( + '[Wishlists API] Delete Wishlist Success', + payload<{ wishlistId: string }>() +); + +export const deleteWishlistFail = createAction('[Wishlists API] Delete Wishlist Fail', httpError()); + +export const addProductToWishlist = createAction( + '[Wishlists] Add Item to Wishlist', + payload<{ wishlistId: string; sku: string; quantity?: number }>() +); + +export const addProductToWishlistSuccess = createAction( + '[Wishlists API] Add Item to Wishlist Success', + payload<{ wishlist: Wishlist }>() +); + +export const addProductToWishlistFail = createAction('[Wishlists API] Add Item to Wishlist Fail', httpError()); + +export const addProductToNewWishlist = createAction( + '[Wishlists Internal] Add Product To New Wishlist', + payload<{ title: string; sku: string }>() +); + +export const moveItemToWishlist = createAction( + '[Wishlists] Move Item to another Wishlist', + payload<{ source: { id: string }; target: { id?: string; title?: string; sku: string } }>() +); + +export const removeItemFromWishlist = createAction( + '[Wishlists] Remove Item from Wishlist', + payload<{ wishlistId: string; sku: string }>() +); + +export const removeItemFromWishlistSuccess = createAction( + '[Wishlists API] Remove Item from Wishlist Success', + payload<{ wishlist: Wishlist }>() +); + +export const removeItemFromWishlistFail = createAction('[Wishlists API] Remove Item from Wishlist Fail', httpError()); + +export const selectWishlist = createAction('[Wishlists Internal] Select Wishlist', payload<{ id: string }>()); diff --git a/src/app/extensions/wishlists/store/wishlist/wishlist.effects.spec.ts b/src/app/extensions/wishlists/store/wishlist/wishlist.effects.spec.ts index cfef0230db..529f7432db 100644 --- a/src/app/extensions/wishlists/store/wishlist/wishlist.effects.spec.ts +++ b/src/app/extensions/wishlists/store/wishlist/wishlist.effects.spec.ts @@ -12,37 +12,36 @@ import { FeatureToggleModule } from 'ish-core/feature-toggle.module'; import { Customer } from 'ish-core/models/customer/customer.model'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; import { CoreStoreModule } from 'ish-core/store/core/core-store.module'; -import { DisplaySuccessMessage } from 'ish-core/store/core/messages'; +import { displaySuccessMessage } from 'ish-core/store/core/messages'; import { CustomerStoreModule } from 'ish-core/store/customer/customer-store.module'; -import { LoginUserSuccess } from 'ish-core/store/customer/user'; +import { loginUserSuccess } from 'ish-core/store/customer/user'; import { Wishlist } from '../../models/wishlist/wishlist.model'; import { WishlistService } from '../../services/wishlist/wishlist.service'; import { WishlistsStoreModule } from '../wishlists-store.module'; import { - AddProductToNewWishlist, - AddProductToWishlist, - AddProductToWishlistFail, - AddProductToWishlistSuccess, - CreateWishlist, - CreateWishlistFail, - CreateWishlistSuccess, - DeleteWishlist, - DeleteWishlistFail, - DeleteWishlistSuccess, - LoadWishlists, - LoadWishlistsFail, - LoadWishlistsSuccess, - MoveItemToWishlist, - RemoveItemFromWishlist, - RemoveItemFromWishlistFail, - RemoveItemFromWishlistSuccess, - SelectWishlist, - UpdateWishlist, - UpdateWishlistFail, - UpdateWishlistSuccess, - WishlistsActionTypes, + addProductToNewWishlist, + addProductToWishlist, + addProductToWishlistFail, + addProductToWishlistSuccess, + createWishlist, + createWishlistFail, + createWishlistSuccess, + deleteWishlist, + deleteWishlistFail, + deleteWishlistSuccess, + loadWishlists, + loadWishlistsFail, + loadWishlistsSuccess, + moveItemToWishlist, + removeItemFromWishlist, + removeItemFromWishlistFail, + removeItemFromWishlistSuccess, + selectWishlist, + updateWishlist, + updateWishlistFail, + updateWishlistSuccess, } from './wishlist.actions'; import { WishlistEffects } from './wishlist.effects'; @@ -102,12 +101,12 @@ describe('Wishlist Effects', () => { describe('loadWishlists$', () => { beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); + store$.dispatch(loginUserSuccess({ customer })); when(wishlistServiceMock.getWishlists()).thenReturn(of(wishlists)); }); it('should call the wishlistService for loadWishlists', done => { - const action = new LoadWishlists(); + const action = loadWishlists(); actions$ = of(action); effects.loadWishlists$.subscribe(() => { @@ -117,8 +116,8 @@ describe('Wishlist Effects', () => { }); it('should map to actions of type LoadWishlistsSuccess', () => { - const action = new LoadWishlists(); - const completion = new LoadWishlistsSuccess({ + const action = loadWishlists(); + const completion = loadWishlistsSuccess({ wishlists, }); actions$ = hot('-a-a-a', { a: action }); @@ -130,8 +129,8 @@ describe('Wishlist Effects', () => { it('should map failed calls to actions of type LoadWishlistsFail', () => { const error = { message: 'invalid' } as HttpError; when(wishlistServiceMock.getWishlists()).thenReturn(throwError(error)); - const action = new LoadWishlists(); - const completion = new LoadWishlistsFail({ + const action = loadWishlists(); + const completion = loadWishlistsFail({ error, }); actions$ = hot('-a-a-a', { a: action }); @@ -154,12 +153,12 @@ describe('Wishlist Effects', () => { public: false, }; beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); + store$.dispatch(loginUserSuccess({ customer })); when(wishlistServiceMock.createWishlist(anything())).thenReturn(of(wishlistData[0])); }); it('should call the wishlistService for createWishlist', done => { - const action = new CreateWishlist({ wishlist: createWishlistData }); + const action = createWishlist({ wishlist: createWishlistData }); actions$ = of(action); effects.createWishlist$.subscribe(() => { @@ -169,11 +168,11 @@ describe('Wishlist Effects', () => { }); it('should map to actions of type CreateWishlistSuccess and SuccessMessage', () => { - const action = new CreateWishlist({ wishlist: createWishlistData }); - const completion1 = new CreateWishlistSuccess({ + const action = createWishlist({ wishlist: createWishlistData }); + const completion1 = createWishlistSuccess({ wishlist: wishlistData[0], }); - const completion2 = new DisplaySuccessMessage({ + const completion2 = displaySuccessMessage({ message: 'account.wishlists.new_wishlist.confirmation', messageParams: { 0: createWishlistData.title }, }); @@ -185,8 +184,8 @@ describe('Wishlist Effects', () => { it('should map failed calls to actions of type CreateWishlistFail', () => { const error = { message: 'invalid' } as HttpError; when(wishlistServiceMock.createWishlist(anything())).thenReturn(throwError(error)); - const action = new CreateWishlist({ wishlist: createWishlistData }); - const completion = new CreateWishlistFail({ + const action = createWishlist({ wishlist: createWishlistData }); + const completion = createWishlistFail({ error, }); actions$ = hot('-a-a-a', { a: action }); @@ -202,8 +201,8 @@ describe('Wishlist Effects', () => { preferred: true, public: false, }; - const action = new CreateWishlistSuccess({ wishlist: createdWishlist }); - const completion = new LoadWishlists(); + const action = createWishlistSuccess({ wishlist: createdWishlist }); + const completion = loadWishlists(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -214,13 +213,13 @@ describe('Wishlist Effects', () => { describe('deleteWishlist$', () => { const id = wishlists[0].id; beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); - store$.dispatch(new CreateWishlistSuccess({ wishlist: wishlists[0] })); + store$.dispatch(loginUserSuccess({ customer })); + store$.dispatch(createWishlistSuccess({ wishlist: wishlists[0] })); when(wishlistServiceMock.deleteWishlist(anyString())).thenReturn(of(undefined)); }); it('should call the wishlistService for deleteWishlist', done => { - const action = new DeleteWishlist({ wishlistId: id }); + const action = deleteWishlist({ wishlistId: id }); actions$ = of(action); effects.deleteWishlist$.subscribe(() => { @@ -230,9 +229,9 @@ describe('Wishlist Effects', () => { }); it('should map to actions of type DeleteWishlistSuccess', () => { - const action = new DeleteWishlist({ wishlistId: id }); - const completion1 = new DeleteWishlistSuccess({ wishlistId: id }); - const completion2 = new DisplaySuccessMessage({ + const action = deleteWishlist({ wishlistId: id }); + const completion1 = deleteWishlistSuccess({ wishlistId: id }); + const completion2 = displaySuccessMessage({ message: 'account.wishlists.delete_wishlist.confirmation', messageParams: { 0: wishlists[0].title }, }); @@ -244,8 +243,8 @@ describe('Wishlist Effects', () => { it('should map failed calls to actions of type DeleteWishlistFail', () => { const error = { message: 'invalid' } as HttpError; when(wishlistServiceMock.deleteWishlist(anyString())).thenReturn(throwError(error)); - const action = new DeleteWishlist({ wishlistId: id }); - const completion = new DeleteWishlistFail({ + const action = deleteWishlist({ wishlistId: id }); + const completion = deleteWishlistFail({ error, }); actions$ = hot('-a-a-a', { a: action }); @@ -266,12 +265,12 @@ describe('Wishlist Effects', () => { }, ]; beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); + store$.dispatch(loginUserSuccess({ customer })); when(wishlistServiceMock.updateWishlist(anything())).thenReturn(of(wishlistDetailData[0])); }); it('should call the wishlistService for updateWishlist', done => { - const action = new UpdateWishlist({ wishlist: wishlistDetailData[0] }); + const action = updateWishlist({ wishlist: wishlistDetailData[0] }); actions$ = of(action); effects.updateWishlist$.subscribe(() => { @@ -281,9 +280,9 @@ describe('Wishlist Effects', () => { }); it('should map to actions of type UpdateWishlistSuccess', () => { - const action = new UpdateWishlist({ wishlist: wishlistDetailData[0] }); - const completion1 = new UpdateWishlistSuccess({ wishlist: wishlistDetailData[0] }); - const completion2 = new DisplaySuccessMessage({ + const action = updateWishlist({ wishlist: wishlistDetailData[0] }); + const completion1 = updateWishlistSuccess({ wishlist: wishlistDetailData[0] }); + const completion2 = displaySuccessMessage({ message: 'account.wishlists.edit_wishlist.confirmation', messageParams: { 0: wishlistDetailData[0].title }, }); @@ -295,8 +294,8 @@ describe('Wishlist Effects', () => { it('should map failed calls to actions of type UpdateWishlistFail', () => { const error = { message: 'invalid' } as HttpError; when(wishlistServiceMock.updateWishlist(anything())).thenReturn(throwError(error)); - const action = new UpdateWishlist({ wishlist: wishlistDetailData[0] }); - const completion = new UpdateWishlistFail({ + const action = updateWishlist({ wishlist: wishlistDetailData[0] }); + const completion = updateWishlistFail({ error, }); actions$ = hot('-a-a-a', { a: action }); @@ -312,8 +311,8 @@ describe('Wishlist Effects', () => { preferred: true, public: false, }; - const action = new UpdateWishlistSuccess({ wishlist: updatedWishlist }); - const completion = new LoadWishlists(); + const action = updateWishlistSuccess({ wishlist: updatedWishlist }); + const completion = loadWishlists(); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); @@ -329,14 +328,14 @@ describe('Wishlist Effects', () => { }; beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); + store$.dispatch(loginUserSuccess({ customer })); when(wishlistServiceMock.addProductToWishlist(anyString(), anyString(), anyNumber())).thenReturn( of(wishlists[0]) ); }); it('should call the wishlistService for addProductToWishlist', done => { - const action = new AddProductToWishlist(payload); + const action = addProductToWishlist(payload); actions$ = of(action); effects.addProductToWishlist$.subscribe(() => { @@ -346,8 +345,8 @@ describe('Wishlist Effects', () => { }); it('should map to actions of type AddProductToWishlistSuccess', () => { - const action = new AddProductToWishlist(payload); - const completion = new AddProductToWishlistSuccess({ wishlist: wishlists[0] }); + const action = addProductToWishlist(payload); + const completion = addProductToWishlistSuccess({ wishlist: wishlists[0] }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); expect(effects.addProductToWishlist$).toBeObservable(expected$); @@ -358,8 +357,8 @@ describe('Wishlist Effects', () => { when(wishlistServiceMock.addProductToWishlist(anyString(), anyString(), anything())).thenReturn( throwError(error) ); - const action = new AddProductToWishlist(payload); - const completion = new AddProductToWishlistFail({ + const action = addProductToWishlist(payload); + const completion = addProductToWishlistFail({ error, }); actions$ = hot('-a-a-a', { a: action }); @@ -382,14 +381,14 @@ describe('Wishlist Effects', () => { public: false, }; beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); + store$.dispatch(loginUserSuccess({ customer })); when(wishlistServiceMock.createWishlist(anything())).thenReturn(of(wishlist)); }); it('should map to actions of types CreateWishlistSuccess and AddProductToWishlist', () => { - const action = new AddProductToNewWishlist(payload); - const completion1 = new CreateWishlistSuccess({ wishlist }); - const completion2 = new AddProductToWishlist({ wishlistId: wishlist.id, sku: payload.sku }); - const completion3 = new SelectWishlist({ id: wishlist.id }); + const action = addProductToNewWishlist(payload); + const completion1 = createWishlistSuccess({ wishlist }); + const completion2 = addProductToWishlist({ wishlistId: wishlist.id, sku: payload.sku }); + const completion3 = selectWishlist({ id: wishlist.id }); actions$ = hot('-a-----a-----a', { a: action }); const expected$ = cold('-(bcd)-(bcd)-(bcd)', { b: completion1, c: completion2, d: completion3 }); expect(effects.addProductToNewWishlist$).toBeObservable(expected$); @@ -397,8 +396,8 @@ describe('Wishlist Effects', () => { it('should map failed calls to actions of type CreateWishlistFail', () => { const error = { message: 'invalid' } as HttpError; when(wishlistServiceMock.createWishlist(anything())).thenReturn(throwError(error)); - const action = new AddProductToNewWishlist(payload); - const completion = new CreateWishlistFail({ + const action = addProductToNewWishlist(payload); + const completion = createWishlistFail({ error, }); actions$ = hot('-a-a-a', { a: action }); @@ -425,21 +424,21 @@ describe('Wishlist Effects', () => { public: false, }; beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); + store$.dispatch(loginUserSuccess({ customer })); when(wishlistServiceMock.createWishlist(anything())).thenReturn(of(wishlist)); }); it('should map to actions of types AddProductToNewWishlist and RemoveItemFromWishlist if there is no target id given', () => { - const action = new MoveItemToWishlist(payload1); - const completion1 = new AddProductToNewWishlist({ title: payload1.target.title, sku: payload1.target.sku }); - const completion2 = new RemoveItemFromWishlist({ wishlistId: payload1.source.id, sku: payload1.target.sku }); + const action = moveItemToWishlist(payload1); + const completion1 = addProductToNewWishlist({ title: payload1.target.title, sku: payload1.target.sku }); + const completion2 = removeItemFromWishlist({ wishlistId: payload1.source.id, sku: payload1.target.sku }); actions$ = hot('-a----a----a', { a: action }); const expected$ = cold('-(bc)-(bc)-(bc)', { b: completion1, c: completion2 }); expect(effects.moveItemToWishlist$).toBeObservable(expected$); }); it('should map to actions of types AddProductToWishlist and RemoveItemFromWishlist if there is a target id given', () => { - const action = new MoveItemToWishlist(payload2); - const completion1 = new AddProductToWishlist({ wishlistId: wishlist.id, sku: payload1.target.sku }); - const completion2 = new RemoveItemFromWishlist({ wishlistId: payload1.source.id, sku: payload1.target.sku }); + const action = moveItemToWishlist(payload2); + const completion1 = addProductToWishlist({ wishlistId: wishlist.id, sku: payload1.target.sku }); + const completion2 = removeItemFromWishlist({ wishlistId: payload1.source.id, sku: payload1.target.sku }); actions$ = hot('-a----a----a', { a: action }); const expected$ = cold('-(bc)-(bc)-(bc)', { b: completion1, c: completion2 }); expect(effects.moveItemToWishlist$).toBeObservable(expected$); @@ -459,12 +458,12 @@ describe('Wishlist Effects', () => { public: false, }; beforeEach(() => { - store$.dispatch(new LoginUserSuccess({ customer })); + store$.dispatch(loginUserSuccess({ customer })); when(wishlistServiceMock.removeProductFromWishlist(anyString(), anyString())).thenReturn(of(wishlist)); }); it('should call the wishlistService for removeProductFromWishlist', done => { - const action = new RemoveItemFromWishlist(payload); + const action = removeItemFromWishlist(payload); actions$ = of(action); effects.removeProductFromWishlist$.subscribe(() => { @@ -473,8 +472,8 @@ describe('Wishlist Effects', () => { }); }); it('should map to actions of type RemoveItemFromWishlistSuccess', () => { - const action = new RemoveItemFromWishlist(payload); - const completion = new RemoveItemFromWishlistSuccess({ wishlist }); + const action = removeItemFromWishlist(payload); + const completion = removeItemFromWishlistSuccess({ wishlist }); actions$ = hot('-a-a-a', { a: action }); const expected$ = cold('-c-c-c', { c: completion }); expect(effects.removeProductFromWishlist$).toBeObservable(expected$); @@ -482,8 +481,8 @@ describe('Wishlist Effects', () => { it('should map failed calls to actions of type RemoveItemFromWishlistFail', () => { const error = { message: 'invalid' } as HttpError; when(wishlistServiceMock.removeProductFromWishlist(anyString(), anyString())).thenReturn(throwError(error)); - const action = new RemoveItemFromWishlist(payload); - const completion = new RemoveItemFromWishlistFail({ + const action = removeItemFromWishlist(payload); + const completion = removeItemFromWishlistFail({ error, }); actions$ = hot('-a-a-a', { a: action }); @@ -513,18 +512,18 @@ describe('Wishlist Effects', () => { }); it('should call WishlistsService after login action was dispatched', done => { effects.loadWishlistsAfterLogin$.subscribe(action => { - expect(action.type).toEqual(WishlistsActionTypes.LoadWishlists); + expect(action.type).toEqual(loadWishlists.type); done(); }); - store$.dispatch(new LoginUserSuccess({ customer })); + store$.dispatch(loginUserSuccess({ customer })); }); }); describe('setWishlistBreadcrumb$', () => { beforeEach(() => { - store$.dispatch(new LoadWishlistsSuccess({ wishlists })); - store$.dispatch(new SelectWishlist({ id: wishlists[0].id })); + store$.dispatch(loadWishlistsSuccess({ wishlists })); + store$.dispatch(selectWishlist({ id: wishlists[0].id })); }); it('should set the breadcrumb of the selected wishlist', done => { diff --git a/src/app/extensions/wishlists/store/wishlist/wishlist.effects.ts b/src/app/extensions/wishlists/store/wishlist/wishlist.effects.ts index 7dd7768db0..0ffb5495ac 100644 --- a/src/app/extensions/wishlists/store/wishlist/wishlist.effects.ts +++ b/src/app/extensions/wishlists/store/wishlist/wishlist.effects.ts @@ -1,11 +1,11 @@ import { Injectable } from '@angular/core'; -import { Actions, Effect, ofType } from '@ngrx/effects'; +import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store, select } from '@ngrx/store'; import { debounceTime, filter, map, mapTo, mergeMap, switchMap, withLatestFrom } from 'rxjs/operators'; -import { DisplaySuccessMessage } from 'ish-core/store/core/messages'; +import { displaySuccessMessage } from 'ish-core/store/core/messages'; import { selectRouteParam } from 'ish-core/store/core/router'; -import { SetBreadcrumbData } from 'ish-core/store/core/viewconf'; +import { setBreadcrumbData } from 'ish-core/store/core/viewconf'; import { getUserAuthorized } from 'ish-core/store/customer/user'; import { distinctCompareWith, @@ -19,28 +19,27 @@ import { Wishlist, WishlistHeader } from '../../models/wishlist/wishlist.model'; import { WishlistService } from '../../services/wishlist/wishlist.service'; import { - AddProductToNewWishlist, - AddProductToWishlist, - AddProductToWishlistFail, - AddProductToWishlistSuccess, - CreateWishlist, - CreateWishlistFail, - CreateWishlistSuccess, - DeleteWishlist, - DeleteWishlistFail, - DeleteWishlistSuccess, - LoadWishlists, - LoadWishlistsFail, - LoadWishlistsSuccess, - MoveItemToWishlist, - RemoveItemFromWishlist, - RemoveItemFromWishlistFail, - RemoveItemFromWishlistSuccess, - SelectWishlist, - UpdateWishlist, - UpdateWishlistFail, - UpdateWishlistSuccess, - WishlistsActionTypes, + addProductToNewWishlist, + addProductToWishlist, + addProductToWishlistFail, + addProductToWishlistSuccess, + createWishlist, + createWishlistFail, + createWishlistSuccess, + deleteWishlist, + deleteWishlistFail, + deleteWishlistSuccess, + loadWishlists, + loadWishlistsFail, + loadWishlistsSuccess, + moveItemToWishlist, + removeItemFromWishlist, + removeItemFromWishlistFail, + removeItemFromWishlistSuccess, + selectWishlist, + updateWishlist, + updateWishlistFail, + updateWishlistSuccess, } from './wishlist.actions'; import { getSelectedWishlistDetails, getSelectedWishlistId, getWishlistDetails } from './wishlist.selectors'; @@ -48,72 +47,76 @@ import { getSelectedWishlistDetails, getSelectedWishlistId, getWishlistDetails } export class WishlistEffects { constructor(private actions$: Actions, private wishlistService: WishlistService, private store: Store) {} - @Effect() - loadWishlists$ = this.actions$.pipe( - ofType(WishlistsActionTypes.LoadWishlists), - withLatestFrom(this.store.pipe(select(getUserAuthorized))), - filter(([, authorized]) => authorized), - switchMap(() => - this.wishlistService.getWishlists().pipe( - map(wishlists => new LoadWishlistsSuccess({ wishlists })), - mapErrorToAction(LoadWishlistsFail) + loadWishlists$ = createEffect(() => + this.actions$.pipe( + ofType(loadWishlists), + withLatestFrom(this.store.pipe(select(getUserAuthorized))), + filter(([, authorized]) => authorized), + switchMap(() => + this.wishlistService.getWishlists().pipe( + map(wishlists => loadWishlistsSuccess({ wishlists })), + mapErrorToAction(loadWishlistsFail) + ) ) ) ); - @Effect() - createWishlist$ = this.actions$.pipe( - ofType(WishlistsActionTypes.CreateWishlist), - mapToPayloadProperty('wishlist'), - mergeMap((wishlistData: WishlistHeader) => - this.wishlistService.createWishlist(wishlistData).pipe( - mergeMap(wishlist => [ - new CreateWishlistSuccess({ wishlist }), - new DisplaySuccessMessage({ - message: 'account.wishlists.new_wishlist.confirmation', - messageParams: { 0: wishlist.title }, - }), - ]), - mapErrorToAction(CreateWishlistFail) + createWishlist$ = createEffect(() => + this.actions$.pipe( + ofType(createWishlist), + mapToPayloadProperty('wishlist'), + mergeMap((wishlistData: WishlistHeader) => + this.wishlistService.createWishlist(wishlistData).pipe( + mergeMap(wishlist => [ + createWishlistSuccess({ wishlist }), + displaySuccessMessage({ + message: 'account.wishlists.new_wishlist.confirmation', + messageParams: { 0: wishlist.title }, + }), + ]), + mapErrorToAction(createWishlistFail) + ) ) ) ); - @Effect() - deleteWishlist$ = this.actions$.pipe( - ofType(WishlistsActionTypes.DeleteWishlist), - mapToPayloadProperty('wishlistId'), - mergeMap(wishlistId => this.store.pipe(select(getWishlistDetails, { id: wishlistId }))), - whenTruthy(), - map(wishlist => ({ wishlistId: wishlist.id, title: wishlist.title })), - mergeMap(({ wishlistId, title }) => - this.wishlistService.deleteWishlist(wishlistId).pipe( - mergeMap(() => [ - new DeleteWishlistSuccess({ wishlistId }), - new DisplaySuccessMessage({ - message: 'account.wishlists.delete_wishlist.confirmation', - messageParams: { 0: title }, - }), - ]), - mapErrorToAction(DeleteWishlistFail) + deleteWishlist$ = createEffect(() => + this.actions$.pipe( + ofType(deleteWishlist), + mapToPayloadProperty('wishlistId'), + mergeMap(wishlistId => this.store.pipe(select(getWishlistDetails, { id: wishlistId }))), + whenTruthy(), + map(wishlist => ({ wishlistId: wishlist.id, title: wishlist.title })), + mergeMap(({ wishlistId, title }) => + this.wishlistService.deleteWishlist(wishlistId).pipe( + mergeMap(() => [ + deleteWishlistSuccess({ wishlistId }), + displaySuccessMessage({ + message: 'account.wishlists.delete_wishlist.confirmation', + messageParams: { 0: title }, + }), + ]), + mapErrorToAction(deleteWishlistFail) + ) ) ) ); - @Effect() - updateWishlist$ = this.actions$.pipe( - ofType(WishlistsActionTypes.UpdateWishlist), - mapToPayloadProperty('wishlist'), - mergeMap((newWishlist: Wishlist) => - this.wishlistService.updateWishlist(newWishlist).pipe( - mergeMap(wishlist => [ - new UpdateWishlistSuccess({ wishlist }), - new DisplaySuccessMessage({ - message: 'account.wishlists.edit_wishlist.confirmation', - messageParams: { 0: wishlist.title }, - }), - ]), - mapErrorToAction(UpdateWishlistFail) + updateWishlist$ = createEffect(() => + this.actions$.pipe( + ofType(updateWishlist), + mapToPayloadProperty('wishlist'), + mergeMap((newWishlist: Wishlist) => + this.wishlistService.updateWishlist(newWishlist).pipe( + mergeMap(wishlist => [ + updateWishlistSuccess({ wishlist }), + displaySuccessMessage({ + message: 'account.wishlists.edit_wishlist.confirmation', + messageParams: { 0: wishlist.title }, + }), + ]), + mapErrorToAction(updateWishlistFail) + ) ) ) ); @@ -121,112 +124,111 @@ export class WishlistEffects { /** * Reload Wishlists after a creation or update to ensure integrity with server concerning the preferred wishlist */ - @Effect() - reloadWishlists$ = this.actions$.pipe( - ofType( - WishlistsActionTypes.UpdateWishlistSuccess, - WishlistsActionTypes.CreateWishlistSuccess - ), - mapToPayloadProperty('wishlist'), - filter(wishlist => wishlist && wishlist.preferred), - mapTo(new LoadWishlists()) + reloadWishlists$ = createEffect(() => + this.actions$.pipe( + ofType(updateWishlistSuccess, createWishlistSuccess), + mapToPayloadProperty('wishlist'), + filter(wishlist => wishlist && wishlist.preferred), + mapTo(loadWishlists()) + ) ); - @Effect() - addProductToWishlist$ = this.actions$.pipe( - ofType(WishlistsActionTypes.AddProductToWishlist), - mapToPayload(), - mergeMap(payload => - this.wishlistService.addProductToWishlist(payload.wishlistId, payload.sku, payload.quantity).pipe( - map(wishlist => new AddProductToWishlistSuccess({ wishlist })), - mapErrorToAction(AddProductToWishlistFail) + addProductToWishlist$ = createEffect(() => + this.actions$.pipe( + ofType(addProductToWishlist), + mapToPayload(), + mergeMap(payload => + this.wishlistService.addProductToWishlist(payload.wishlistId, payload.sku, payload.quantity).pipe( + map(wishlist => addProductToWishlistSuccess({ wishlist })), + mapErrorToAction(addProductToWishlistFail) + ) ) ) ); - @Effect() - addProductToNewWishlist$ = this.actions$.pipe( - ofType(WishlistsActionTypes.AddProductToNewWishlist), - mapToPayload(), - mergeMap(payload => - this.wishlistService - .createWishlist({ - title: payload.title, - preferred: false, - }) - .pipe( - // use created wishlist data to dispatch addProduct action - mergeMap(wishlist => [ - new CreateWishlistSuccess({ wishlist }), - new AddProductToWishlist({ wishlistId: wishlist.id, sku: payload.sku }), - new SelectWishlist({ id: wishlist.id }), - ]), - mapErrorToAction(CreateWishlistFail) - ) + addProductToNewWishlist$ = createEffect(() => + this.actions$.pipe( + ofType(addProductToNewWishlist), + mapToPayload(), + mergeMap(payload => + this.wishlistService + .createWishlist({ + title: payload.title, + preferred: false, + }) + .pipe( + // use created wishlist data to dispatch addProduct action + mergeMap(wishlist => [ + createWishlistSuccess({ wishlist }), + addProductToWishlist({ wishlistId: wishlist.id, sku: payload.sku }), + selectWishlist({ id: wishlist.id }), + ]), + mapErrorToAction(createWishlistFail) + ) + ) ) ); - @Effect() - moveItemToWishlist$ = this.actions$.pipe( - ofType(WishlistsActionTypes.MoveItemToWishlist), - mapToPayload(), - mergeMap(payload => { - if (!payload.target.id) { - return [ - new AddProductToNewWishlist({ title: payload.target.title, sku: payload.target.sku }), - new RemoveItemFromWishlist({ wishlistId: payload.source.id, sku: payload.target.sku }), - ]; - } else { - return [ - new AddProductToWishlist({ wishlistId: payload.target.id, sku: payload.target.sku }), - new RemoveItemFromWishlist({ wishlistId: payload.source.id, sku: payload.target.sku }), - ]; - } - }) + moveItemToWishlist$ = createEffect(() => + this.actions$.pipe( + ofType(moveItemToWishlist), + mapToPayload(), + mergeMap(payload => { + if (!payload.target.id) { + return [ + addProductToNewWishlist({ title: payload.target.title, sku: payload.target.sku }), + removeItemFromWishlist({ wishlistId: payload.source.id, sku: payload.target.sku }), + ]; + } else { + return [ + addProductToWishlist({ wishlistId: payload.target.id, sku: payload.target.sku }), + removeItemFromWishlist({ wishlistId: payload.source.id, sku: payload.target.sku }), + ]; + } + }) + ) ); - @Effect() - removeProductFromWishlist$ = this.actions$.pipe( - ofType(WishlistsActionTypes.RemoveItemFromWishlist), - mapToPayload(), - mergeMap(payload => - this.wishlistService.removeProductFromWishlist(payload.wishlistId, payload.sku).pipe( - map(wishlist => new RemoveItemFromWishlistSuccess({ wishlist })), - mapErrorToAction(RemoveItemFromWishlistFail) + removeProductFromWishlist$ = createEffect(() => + this.actions$.pipe( + ofType(removeItemFromWishlist), + mapToPayload(), + mergeMap(payload => + this.wishlistService.removeProductFromWishlist(payload.wishlistId, payload.sku).pipe( + map(wishlist => removeItemFromWishlistSuccess({ wishlist })), + mapErrorToAction(removeItemFromWishlistFail) + ) ) ) ); - @Effect() - routeListenerForSelectedWishlist$ = this.store.pipe( - select(selectRouteParam('wishlistName')), - distinctCompareWith(this.store.pipe(select(getSelectedWishlistId))), - map(id => new SelectWishlist({ id })) + routeListenerForSelectedWishlist$ = createEffect(() => + this.store.pipe( + select(selectRouteParam('wishlistName')), + distinctCompareWith(this.store.pipe(select(getSelectedWishlistId))), + map(id => selectWishlist({ id })) + ) ); /** * Trigger LoadWishlists action after LoginUserSuccess. */ - @Effect() - loadWishlistsAfterLogin$ = this.store.pipe( - select(getUserAuthorized), - whenTruthy(), - debounceTime(1000), - mapTo(new LoadWishlists()) + loadWishlistsAfterLogin$ = createEffect(() => + this.store.pipe(select(getUserAuthorized), whenTruthy(), debounceTime(1000), mapTo(loadWishlists())) ); - @Effect() - setWishlistBreadcrumb$ = this.store.pipe( - select(getSelectedWishlistDetails), - whenTruthy(), - map( - wishlist => - new SetBreadcrumbData({ + setWishlistBreadcrumb$ = createEffect(() => + this.store.pipe( + select(getSelectedWishlistDetails), + whenTruthy(), + map(wishlist => + setBreadcrumbData({ breadcrumbData: [ { key: 'account.wishlists.breadcrumb_link', link: '/account/wishlists' }, { text: wishlist.title }, ], }) + ) ) ); } diff --git a/src/app/extensions/wishlists/store/wishlist/wishlist.reducer.ts b/src/app/extensions/wishlists/store/wishlist/wishlist.reducer.ts index 62040d1179..23bb493842 100644 --- a/src/app/extensions/wishlists/store/wishlist/wishlist.reducer.ts +++ b/src/app/extensions/wishlists/store/wishlist/wishlist.reducer.ts @@ -1,10 +1,30 @@ import { EntityState, createEntityAdapter } from '@ngrx/entity'; +import { createReducer, on } from '@ngrx/store'; import { HttpError } from 'ish-core/models/http-error/http-error.model'; +import { setLoadingOn } from 'ish-core/utils/ngrx-creators'; import { Wishlist } from '../../models/wishlist/wishlist.model'; -import { WishlistsAction, WishlistsActionTypes } from './wishlist.actions'; +import { + addProductToWishlistSuccess, + createWishlist, + createWishlistFail, + createWishlistSuccess, + deleteWishlist, + deleteWishlistFail, + deleteWishlistSuccess, + loadWishlists, + loadWishlistsFail, + loadWishlistsSuccess, + moveItemToWishlist, + removeItemFromWishlist, + removeItemFromWishlistSuccess, + selectWishlist, + updateWishlist, + updateWishlistFail, + updateWishlistSuccess, +} from './wishlist.actions'; export interface WishlistState extends EntityState { loading: boolean; @@ -22,44 +42,38 @@ export const initialState: WishlistState = wishlistsAdapter.getInitialState({ error: undefined, }); -export function wishlistReducer(state = initialState, action: WishlistsAction): WishlistState { - switch (action.type) { - case WishlistsActionTypes.LoadWishlists: - case WishlistsActionTypes.CreateWishlist: - case WishlistsActionTypes.DeleteWishlist: - case WishlistsActionTypes.UpdateWishlist: - case WishlistsActionTypes.RemoveItemFromWishlist: - case WishlistsActionTypes.MoveItemToWishlist: { - return { - ...state, - loading: true, - }; - } - case WishlistsActionTypes.LoadWishlistsFail: - case WishlistsActionTypes.DeleteWishlistFail: - case WishlistsActionTypes.CreateWishlistFail: - case WishlistsActionTypes.UpdateWishlistFail: { - const { error } = action.payload; - return { - ...state, - loading: false, - error, - selected: undefined, - }; - } - - case WishlistsActionTypes.LoadWishlistsSuccess: { - const { wishlists } = action.payload; - return wishlistsAdapter.setAll(wishlists, { - ...state, - loading: false, - }); - } - - case WishlistsActionTypes.UpdateWishlistSuccess: - case WishlistsActionTypes.AddProductToWishlistSuccess: - case WishlistsActionTypes.RemoveItemFromWishlistSuccess: - case WishlistsActionTypes.CreateWishlistSuccess: { +export const wishlistReducer = createReducer( + initialState, + setLoadingOn( + loadWishlists, + createWishlist, + deleteWishlist, + updateWishlist, + removeItemFromWishlist, + moveItemToWishlist + ), + on(loadWishlistsFail, deleteWishlistFail, createWishlistFail, updateWishlistFail, (state: WishlistState, action) => { + const { error } = action.payload; + return { + ...state, + loading: false, + error, + selected: undefined, + }; + }), + on(loadWishlistsSuccess, (state: WishlistState, action) => { + const { wishlists } = action.payload; + return wishlistsAdapter.setAll(wishlists, { + ...state, + loading: false, + }); + }), + on( + updateWishlistSuccess, + addProductToWishlistSuccess, + removeItemFromWishlistSuccess, + createWishlistSuccess, + (state: WishlistState, action) => { const { wishlist } = action.payload; return wishlistsAdapter.upsertOne(wishlist, { @@ -67,23 +81,19 @@ export function wishlistReducer(state = initialState, action: WishlistsAction): loading: false, }); } - - case WishlistsActionTypes.DeleteWishlistSuccess: { - const { wishlistId } = action.payload; - return wishlistsAdapter.removeOne(wishlistId, { - ...state, - loading: false, - }); - } - - case WishlistsActionTypes.SelectWishlist: { - const { id } = action.payload; - return { - ...state, - selected: id, - }; - } - } - - return state; -} + ), + on(deleteWishlistSuccess, (state: WishlistState, action) => { + const { wishlistId } = action.payload; + return wishlistsAdapter.removeOne(wishlistId, { + ...state, + loading: false, + }); + }), + on(selectWishlist, (state: WishlistState, action) => { + const { id } = action.payload; + return { + ...state, + selected: id, + }; + }) +); diff --git a/src/app/extensions/wishlists/store/wishlist/wishlist.selectors.spec.ts b/src/app/extensions/wishlists/store/wishlist/wishlist.selectors.spec.ts index 58d95a9378..6e0f2f37bb 100644 --- a/src/app/extensions/wishlists/store/wishlist/wishlist.selectors.spec.ts +++ b/src/app/extensions/wishlists/store/wishlist/wishlist.selectors.spec.ts @@ -7,19 +7,19 @@ import { StoreWithSnapshots, provideStoreSnapshots } from 'ish-core/utils/dev/ng import { WishlistsStoreModule } from '../wishlists-store.module'; import { - CreateWishlist, - CreateWishlistFail, - CreateWishlistSuccess, - DeleteWishlist, - DeleteWishlistFail, - DeleteWishlistSuccess, - LoadWishlists, - LoadWishlistsFail, - LoadWishlistsSuccess, - SelectWishlist, - UpdateWishlist, - UpdateWishlistFail, - UpdateWishlistSuccess, + createWishlist, + createWishlistFail, + createWishlistSuccess, + deleteWishlist, + deleteWishlistFail, + deleteWishlistSuccess, + loadWishlists, + loadWishlistsFail, + loadWishlistsSuccess, + selectWishlist, + updateWishlist, + updateWishlistFail, + updateWishlistSuccess, } from './wishlist.actions'; import { getAllWishlists, @@ -76,7 +76,7 @@ describe('Wishlist Selectors', () => { describe('loading wishlists', () => { describe('LoadWishlists', () => { - const loadWishlistAction = new LoadWishlists(); + const loadWishlistAction = loadWishlists(); beforeEach(() => { store$.dispatch(loadWishlistAction); @@ -89,7 +89,7 @@ describe('Wishlist Selectors', () => { describe('LoadWishlistsSuccess', () => { beforeEach(() => { - store$.dispatch(new LoadWishlistsSuccess({ wishlists })); + store$.dispatch(loadWishlistsSuccess({ wishlists })); }); it('should set loading to false', () => { @@ -103,7 +103,7 @@ describe('Wishlist Selectors', () => { describe('LoadWishlistsFail', () => { beforeEach(() => { - store$.dispatch(new LoadWishlistsFail({ error: { message: 'invalid' } as HttpError })); + store$.dispatch(loadWishlistsFail({ error: { message: 'invalid' } as HttpError })); }); it('should set loading to false', () => { @@ -118,7 +118,7 @@ describe('Wishlist Selectors', () => { describe('create a wishlist', () => { describe('CreateWishlist', () => { - const createWishlistAction = new CreateWishlist({ + const createWishlistAction = createWishlist({ wishlist: { title: 'create title', preferred: true, @@ -136,7 +136,7 @@ describe('Wishlist Selectors', () => { describe('CreateWishlistSuccess', () => { beforeEach(() => { - store$.dispatch(new CreateWishlistSuccess({ wishlist: wishlists[0] })); + store$.dispatch(createWishlistSuccess({ wishlist: wishlists[0] })); }); it('should set loading to false', () => { @@ -150,7 +150,7 @@ describe('Wishlist Selectors', () => { describe('CreateWishlistFail', () => { beforeEach(() => { - store$.dispatch(new CreateWishlistFail({ error: { message: 'invalid' } as HttpError })); + store$.dispatch(createWishlistFail({ error: { message: 'invalid' } as HttpError })); }); it('should set loading to false', () => { @@ -166,7 +166,7 @@ describe('Wishlist Selectors', () => { describe('delete a wishlist', () => { describe('DeleteWishlist', () => { beforeEach(() => { - store$.dispatch(new DeleteWishlist({ wishlistId: 'id' })); + store$.dispatch(deleteWishlist({ wishlistId: 'id' })); }); it('should set loading to true', () => { @@ -175,8 +175,8 @@ describe('Wishlist Selectors', () => { }); describe('DeleteWishlistSuccess', () => { - const loadWishlistSuccessAction = new LoadWishlistsSuccess({ wishlists }); - const deleteWishlistSuccessAction = new DeleteWishlistSuccess({ wishlistId: wishlists[0].id }); + const loadWishlistSuccessAction = loadWishlistsSuccess({ wishlists }); + const deleteWishlistSuccessAction = deleteWishlistSuccess({ wishlistId: wishlists[0].id }); it('should set loading to false', () => { store$.dispatch(deleteWishlistSuccessAction); @@ -194,7 +194,7 @@ describe('Wishlist Selectors', () => { describe('DeleteWishlistFail', () => { beforeEach(() => { - store$.dispatch(new DeleteWishlistFail({ error: { message: 'invalid' } as HttpError })); + store$.dispatch(deleteWishlistFail({ error: { message: 'invalid' } as HttpError })); }); it('should set loading to false', () => { @@ -210,7 +210,7 @@ describe('Wishlist Selectors', () => { describe('updating a wishlist', () => { describe('UpdateWishlist', () => { beforeEach(() => { - store$.dispatch(new UpdateWishlist({ wishlist: wishlists[0] })); + store$.dispatch(updateWishlist({ wishlist: wishlists[0] })); }); it('should set loading to true', () => { @@ -223,10 +223,10 @@ describe('Wishlist Selectors', () => { ...wishlists[0], title: 'new title', }; - const updateWishlistSuccessAction = new UpdateWishlistSuccess({ + const updateWishlistSuccessAction = updateWishlistSuccess({ wishlist: updated, }); - const loadWishlistSuccess = new LoadWishlistsSuccess({ wishlists }); + const loadWishlistSuccess = loadWishlistsSuccess({ wishlists }); it('should set loading to false', () => { store$.dispatch(updateWishlistSuccessAction); @@ -244,7 +244,7 @@ describe('Wishlist Selectors', () => { describe('UpdateWishlistFail', () => { beforeEach(() => { - store$.dispatch(new UpdateWishlistFail({ error: { message: 'invalid' } as HttpError })); + store$.dispatch(updateWishlistFail({ error: { message: 'invalid' } as HttpError })); }); it('should set loading to false', () => { @@ -258,8 +258,8 @@ describe('Wishlist Selectors', () => { }); describe('Get Selected Wishlist', () => { - const loadWishlistsSuccessActions = new LoadWishlistsSuccess({ wishlists }); - const selectWishlistAction = new SelectWishlist({ id: wishlists[1].id }); + const loadWishlistsSuccessActions = loadWishlistsSuccess({ wishlists }); + const selectWishlistAction = selectWishlist({ id: wishlists[1].id }); beforeEach(() => { store$.dispatch(loadWishlistsSuccessActions); @@ -277,7 +277,7 @@ describe('Wishlist Selectors', () => { describe('Get Wishlist Details', () => { beforeEach(() => { - store$.dispatch(new LoadWishlistsSuccess({ wishlists })); + store$.dispatch(loadWishlistsSuccess({ wishlists })); }); it('should return correct wishlist for given id', () => { @@ -287,7 +287,7 @@ describe('Wishlist Selectors', () => { describe('Get Preferred Wishlist', () => { beforeEach(() => { - store$.dispatch(new LoadWishlistsSuccess({ wishlists })); + store$.dispatch(loadWishlistsSuccess({ wishlists })); }); it('should return correct wishlist for given title', () => { diff --git a/src/app/shared/cms/sfe-adapter/sfe-adapter.service.ts b/src/app/shared/cms/sfe-adapter/sfe-adapter.service.ts index 44e294e019..a4a4db2fc6 100644 --- a/src/app/shared/cms/sfe-adapter/sfe-adapter.service.ts +++ b/src/app/shared/cms/sfe-adapter/sfe-adapter.service.ts @@ -5,7 +5,7 @@ import { Store, select } from '@ngrx/store'; import { fromEvent, merge } from 'rxjs'; import { debounceTime, distinctUntilChanged, filter, map, switchMapTo, take, withLatestFrom } from 'rxjs/operators'; -import { LoadContentInclude, getContentIncludeLoading } from 'ish-core/store/content/includes'; +import { getContentIncludeLoading, loadContentInclude } from 'ish-core/store/content/includes'; import { getICMBaseURL } from 'ish-core/store/core/configuration'; import { whenTruthy } from 'ish-core/utils/operators'; @@ -191,7 +191,7 @@ export class SfeAdapterService { switch (message.type) { case 'dv-designchange': { const { includeId } = message.payload; - this.store.dispatch(new LoadContentInclude({ includeId })); + this.store.dispatch(loadContentInclude({ includeId })); return; } }