Skip to content

Commit

Permalink
feat: button for clearing cart (#1537)
Browse files Browse the repository at this point in the history
Co-authored-by: Stefan Hauke <s.hauke@intershop.de>
Co-authored-by: Silke <s.grueber@intershop.de>
Co-authored-by: Susanne Schneider <s.schneider@intershop.de>
  • Loading branch information
4 people authored Dec 21, 2023
1 parent 367fe70 commit dd5f34f
Show file tree
Hide file tree
Showing 16 changed files with 237 additions and 11 deletions.
5 changes: 5 additions & 0 deletions src/app/core/facades/checkout.facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
createBasketPayment,
deleteBasketAttribute,
deleteBasketItem,
deleteBasketItems,
deleteBasketPayment,
deleteBasketShippingAddress,
getBasketEligiblePaymentMethods,
Expand Down Expand Up @@ -117,6 +118,10 @@ export class CheckoutFacade {
this.store.dispatch(deleteBasketItem({ itemId }));
}

deleteBasketItems() {
this.store.dispatch(deleteBasketItems());
}

updateBasketItem(update: LineItemUpdate) {
if (update.quantity) {
this.store.dispatch(updateBasketItem({ lineItemUpdate: update }));
Expand Down
2 changes: 1 addition & 1 deletion src/app/core/models/basket-info/basket-info.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export class BasketInfoMapper {
const { itemId } = payload;
const infos = payload?.infos
?.filter(info => !minorInfos.includes(info.code))
?.filter(info => !info.causes.find(cause => minorCauses.includes(cause.code)));
?.filter(info => !info.causes?.find(cause => minorCauses.includes(cause.code)));

return itemId
? infos?.map(info => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ describe('Basket Items Service', () => {
});
});

it("should remove all line items from specific basket when 'deleteBasketItems' is called", done => {
when(apiServiceMock.delete(anyString(), anything())).thenReturn(of({}));

basketItemsService.deleteBasketItems().subscribe(() => {
verify(apiServiceMock.delete(`items`, anything())).once();
done();
});
});

describe('Update Basket Items desired delivery date', () => {
const lineItems: LineItem[] = [
...BasketMockData.getBasket().lineItems,
Expand Down
17 changes: 16 additions & 1 deletion src/app/core/services/basket-items/basket-items.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export class BasketItemsService {
}

/**
* Remove specific line item from the currently used basket.
* Removes a specific line item from the currently used basket.
*
* @param itemId The id of the line item that should be deleted.
*/
Expand All @@ -123,6 +123,21 @@ export class BasketItemsService {
);
}

/**
* Removes all line items from the currently used basket.
*/
deleteBasketItems(): Observable<BasketInfo[]> {
return this.apiService
.currentBasketEndpoint()
.delete<{ infos: BasketInfo[] }>(`items`, {
headers: this.basketHeaders,
})
.pipe(
map(payload => ({ ...payload })),
map(BasketInfoMapper.fromInfo)
);
}

/**
* Updates the desired delivery date at all those line items of the current basket, whose desired delivery date differs from the given date.
*
Expand Down
61 changes: 60 additions & 1 deletion src/app/core/store/customer/basket/basket-items.effects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import {
deleteBasketItem,
deleteBasketItemFail,
deleteBasketItemSuccess,
deleteBasketItems,
deleteBasketItemsFail,
deleteBasketItemsSuccess,
loadBasket,
loadBasketSuccess,
updateBasketItem,
Expand Down Expand Up @@ -327,7 +330,54 @@ describe('Basket Items Effects', () => {
});
});

describe('loadBasketAfterDeleteBasketItem$', () => {
describe('deleteBasketItems$', () => {
beforeEach(() => {
when(basketItemsServiceMock.deleteBasketItems()).thenReturn(of(undefined));

store.dispatch(
loadBasketSuccess({
basket: {
id: 'BID',
lineItems: [],
} as Basket,
})
);
});

it('should call the basketItemService for DeleteBasketItems action', done => {
const action = deleteBasketItems();
actions$ = of(action);

effects.deleteBasketItems$.subscribe(() => {
verify(basketItemsServiceMock.deleteBasketItems()).once();
done();
});
});

it('should map to action of type DeleteBasketItemsSuccess', () => {
const action = deleteBasketItems();
const completion = deleteBasketItemsSuccess({ info: undefined });
actions$ = hot('-a-a-a', { a: action });
const expected$ = cold('-c-c-c', { c: completion });

expect(effects.deleteBasketItems$).toBeObservable(expected$);
});

it('should map invalid request to action of type DeleteBasketItemsFail', () => {
when(basketItemsServiceMock.deleteBasketItems()).thenReturn(
throwError(() => makeHttpError({ message: 'invalid' }))
);

const action = deleteBasketItems();
const completion = deleteBasketItemsFail({ error: makeHttpError({ message: 'invalid' }) });
actions$ = hot('-a-a-a', { a: action });
const expected$ = cold('-c-c-c', { c: completion });

expect(effects.deleteBasketItems$).toBeObservable(expected$);
});
});

describe('loadBasketAfterBasketItemsChangeSuccess$', () => {
it('should map to action of type LoadBasket if DeleteBasketItemSuccess action triggered', () => {
const action = deleteBasketItemSuccess({ itemId: '123', info: undefined });
const completion = loadBasket();
Expand All @@ -336,6 +386,15 @@ describe('Basket Items Effects', () => {

expect(effects.loadBasketAfterBasketItemsChangeSuccess$).toBeObservable(expected$);
});

it('should map to action of type LoadBasket if DeleteBasketItemsSuccess action triggered', () => {
const action = deleteBasketItemsSuccess({ info: undefined });
const completion = loadBasket();
actions$ = hot('-a-a-a', { a: action });
const expected$ = cold('-c-c-c', { c: completion });

expect(effects.loadBasketAfterBasketItemsChangeSuccess$).toBeObservable(expected$);
});
});

describe('redirectToBasketIfBasketInteractionHasInfo$', () => {
Expand Down
20 changes: 19 additions & 1 deletion src/app/core/store/customer/basket/basket-items.effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import {
deleteBasketItem,
deleteBasketItemFail,
deleteBasketItemSuccess,
deleteBasketItems,
deleteBasketItemsFail,
deleteBasketItemsSuccess,
loadBasket,
updateBasketItem,
updateBasketItemFail,
Expand Down Expand Up @@ -140,12 +143,27 @@ export class BasketItemsEffects {
)
);

/**
* Delete all basket items effect.
*/
deleteBasketItems$ = createEffect(() =>
this.actions$.pipe(
ofType(deleteBasketItems),
concatMap(() =>
this.basketItemsService.deleteBasketItems().pipe(
map(info => deleteBasketItemsSuccess({ info })),
mapErrorToAction(deleteBasketItemsFail)
)
)
)
);

/**
* Triggers a LoadBasket action after successful interaction with the Basket API.
*/
loadBasketAfterBasketItemsChangeSuccess$ = createEffect(() =>
this.actions$.pipe(
ofType(addItemsToBasketSuccess, updateBasketItemSuccess, deleteBasketItemSuccess),
ofType(addItemsToBasketSuccess, updateBasketItemSuccess, deleteBasketItemSuccess, deleteBasketItemsSuccess),
map(() => loadBasket())
)
);
Expand Down
9 changes: 9 additions & 0 deletions src/app/core/store/customer/basket/basket.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,15 @@ export const deleteBasketItemSuccess = createAction(
payload<{ itemId: string; info: BasketInfo[] }>()
);

export const deleteBasketItems = createAction('[Basket] Delete Basket Items');

export const deleteBasketItemsFail = createAction('[Basket API] Delete Basket Items Fail', httpError());

export const deleteBasketItemsSuccess = createAction(
'[Basket API] Delete Basket Items Success',
payload<{ info: BasketInfo[] }>()
);

export const removePromotionCodeFromBasket = createAction(
'[Basket Internal] Remove Promotion Code From Basket',
payload<{ code: string }>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,16 @@ <h1 class="d-flex flex-wrap align-items-baseline">

<ish-loading *ngIf="loading" />

<div class="button-group">
<ish-lazy-basket-add-to-quote />
<ish-lazy-basket-create-order-template
*ngIf="basket"
[cssClass]="'btn btn-secondary'"
[products]="basket.lineItems"
/>
<div class="button-group clearfix">
<div class="float-md-left">
<ish-lazy-basket-add-to-quote />
<ish-lazy-basket-create-order-template
*ngIf="basket"
[cssClass]="'btn btn-secondary'"
[products]="basket.lineItems"
/>
</div>
<ish-clear-basket cssClass="float-md-right" />
</div>
</div>
<ish-lazy-direct-order />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { BasketErrorMessageComponent } from 'ish-shared/components/basket/basket
import { BasketInfoComponent } from 'ish-shared/components/basket/basket-info/basket-info.component';
import { BasketPromotionCodeComponent } from 'ish-shared/components/basket/basket-promotion-code/basket-promotion-code.component';
import { BasketValidationResultsComponent } from 'ish-shared/components/basket/basket-validation-results/basket-validation-results.component';
import { ClearBasketComponent } from 'ish-shared/components/basket/clear-basket/clear-basket.component';
import { ErrorMessageComponent } from 'ish-shared/components/common/error-message/error-message.component';
import { ModalDialogLinkComponent } from 'ish-shared/components/common/modal-dialog-link/modal-dialog-link.component';
import { LineItemListComponent } from 'ish-shared/components/line-item/line-item-list/line-item-list.component';
Expand All @@ -38,6 +39,7 @@ describe('Shopping Basket Component', () => {
MockComponent(BasketInfoComponent),
MockComponent(BasketPromotionCodeComponent),
MockComponent(BasketValidationResultsComponent),
MockComponent(ClearBasketComponent),
MockComponent(ContentIncludeComponent),
MockComponent(ErrorMessageComponent),
MockComponent(LazyBasketAddToQuoteComponent),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<button
type="button"
class="btn btn-link"
[ngClass]="cssClass"
[attr.title]="'shopping_cart.clear_cart.button' | translate"
data-testing-id="clearBasketButton"
(click)="clearDialog.show()"
>
{{ 'shopping_cart.clear_cart.button' | translate }}
</button>

<ish-modal-dialog
#clearDialog
[options]="{
confirmText: 'shopping_cart.clear_cart.confirm_button' | translate,
rejectText: 'shopping_cart.button.cancel' | translate,
titleText: 'shopping_cart.clear_cart.dialog_header' | translate
}"
(confirmed)="clearBasket()"
>
{{ 'shopping_cart.clear_cart.question' | translate }}
</ish-modal-dialog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { MockComponent } from 'ng-mocks';
import { instance, mock, verify, when } from 'ts-mockito';

import { CheckoutFacade } from 'ish-core/facades/checkout.facade';
import { ModalDialogComponent } from 'ish-shared/components/common/modal-dialog/modal-dialog.component';

import { ClearBasketComponent } from './clear-basket.component';

describe('Clear Basket Component', () => {
let component: ClearBasketComponent;
let fixture: ComponentFixture<ClearBasketComponent>;
let element: HTMLElement;
let checkoutFacade: CheckoutFacade;

beforeEach(async () => {
checkoutFacade = mock(CheckoutFacade);

await TestBed.configureTestingModule({
declarations: [ClearBasketComponent, MockComponent(ModalDialogComponent)],
imports: [TranslateModule.forRoot()],
providers: [{ provide: CheckoutFacade, useFactory: () => instance(checkoutFacade) }],
}).compileComponents();

when(checkoutFacade.deleteBasketItems()).thenReturn(undefined);
});

beforeEach(() => {
fixture = TestBed.createComponent(ClearBasketComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;
});

it('should be created', () => {
expect(component).toBeTruthy();
expect(element).toBeTruthy();
expect(() => fixture.detectChanges()).not.toThrow();
});

it('should display a button to trigger the clearing of the basket', () => {
fixture.detectChanges();

expect(element.querySelector('button[data-testing-id=clearBasketButton]')).toBeTruthy();
});

it('should call facade when triggered.', () => {
component.clearBasket();

verify(checkoutFacade.deleteBasketItems()).once();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';

import { CheckoutFacade } from 'ish-core/facades/checkout.facade';

@Component({
selector: 'ish-clear-basket',
templateUrl: './clear-basket.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ClearBasketComponent {
@Input() cssClass: string;

constructor(private checkoutFacade: CheckoutFacade) {}

clearBasket() {
this.checkoutFacade.deleteBasketItems();
}
}
2 changes: 2 additions & 0 deletions src/app/shared/shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import { BasketShippingMethodComponent } from './components/basket/basket-shippi
import { BasketValidationItemsComponent } from './components/basket/basket-validation-items/basket-validation-items.component';
import { BasketValidationProductsComponent } from './components/basket/basket-validation-products/basket-validation-products.component';
import { BasketValidationResultsComponent } from './components/basket/basket-validation-results/basket-validation-results.component';
import { ClearBasketComponent } from './components/basket/clear-basket/clear-basket.component';
import { MiniBasketContentComponent } from './components/basket/mini-basket-content/mini-basket-content.component';
import { BasketInvoiceAddressWidgetComponent } from './components/checkout/basket-invoice-address-widget/basket-invoice-address-widget.component';
import { BasketShippingAddressWidgetComponent } from './components/checkout/basket-shipping-address-widget/basket-shipping-address-widget.component';
Expand Down Expand Up @@ -254,6 +255,7 @@ const exportedComponents = [
BasketShippingMethodComponent,
BasketValidationResultsComponent,
BreadcrumbComponent,
ClearBasketComponent,
ConfirmLeaveModalComponent,
ContentIncludeComponent,
ContentNavigationComponent,
Expand Down
4 changes: 4 additions & 0 deletions src/assets/i18n/de_DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,10 @@
"shopping.product.readyforshipment.details.title": "Versandfertig",
"shopping_cart.adjusted_items.warnung": "Wir haben die Anzahl eines oder mehrerer Artikel in Ihrem Warenkorb angepasst. Weitere Informationen dazu finden Sie unten.",
"shopping_cart.button.cancel": "Abbrechen",
"shopping_cart.clear_cart.button": "Warenkorb Leeren",
"shopping_cart.clear_cart.confirm_button": "Warenkorb leeren",
"shopping_cart.clear_cart.dialog_header": "Ihren Warenkorb leeren",
"shopping_cart.clear_cart.question": "Möchten Sie wirklich Ihren Warenkorb leeren?",
"shopping_cart.detail.text": "Details",
"shopping_cart.detailed.continue_shopping.link": "Weiter einkaufen",
"shopping_cart.direct_order.heading": "Einen Artikel in den Warenkorb legen",
Expand Down
4 changes: 4 additions & 0 deletions src/assets/i18n/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,10 @@
"shopping.product.readyforshipment.details.title": "Ready For Shipment Time",
"shopping_cart.adjusted_items.warnung": "We have adjusted the quantity of one or more items in your cart. Please see details below.",
"shopping_cart.button.cancel": "Cancel",
"shopping_cart.clear_cart.button": "Clear Cart",
"shopping_cart.clear_cart.confirm_button": "Clear",
"shopping_cart.clear_cart.dialog_header": "Clear your shopping cart",
"shopping_cart.clear_cart.question": "Do you really want to clear your shopping cart?",
"shopping_cart.detail.text": "Details",
"shopping_cart.detailed.continue_shopping.link": "Continue Shopping",
"shopping_cart.direct_order.heading": "Add an Item to Cart",
Expand Down
4 changes: 4 additions & 0 deletions src/assets/i18n/fr_FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,10 @@
"shopping.product.readyforshipment.details.title": "Durée « Prêt à l’expédition »",
"shopping_cart.adjusted_items.warnung": "Nous avons ajusté la quantité d’un ou plusieurs articles dans votre panier. Veuillez trouver des détails ci-dessous.",
"shopping_cart.button.cancel": "Annuler",
"shopping_cart.clear_cart.button": "Vider le Panier",
"shopping_cart.clear_cart.confirm_button": "Vider",
"shopping_cart.clear_cart.dialog_header": "Vider votre Panier",
"shopping_cart.clear_cart.question": "Voulez-vous vraiment vider votre panier ?",
"shopping_cart.detail.text": "Détails",
"shopping_cart.detailed.continue_shopping.link": "Poursuivre mes achats",
"shopping_cart.direct_order.heading": "Ajouter un article au panier",
Expand Down

0 comments on commit dd5f34f

Please sign in to comment.