Skip to content

Commit

Permalink
feat: Quick order (#11872)
Browse files Browse the repository at this point in the history
Co-authored-by: Marcin Lasak <[email protected]>
Co-authored-by: morganm58 <[email protected]>
Co-authored-by: Michal Szczepaniak <[email protected]>

Closes #11817
  • Loading branch information
dydome authored Aug 26, 2021
1 parent c4d7269 commit 9ed6216
Show file tree
Hide file tree
Showing 87 changed files with 3,452 additions and 14 deletions.
7 changes: 7 additions & 0 deletions core-libs/setup/tsconfig.lib.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@
"@spartacus/user/profile/occ": ["dist/user/profile/occ"],
"@spartacus/user/profile/root": ["dist/user/profile/root"],
"@spartacus/cart": ["dist/cart"],
"@spartacus/cart/quick-order/assets": ["dist/cart/quick-order/assets"],
"@spartacus/cart/quick-order/components": [
"dist/cart/quick-order/components"
],
"@spartacus/cart/quick-order/core": ["dist/cart/quick-order/core"],
"@spartacus/cart/quick-order": ["dist/cart/quick-order"],
"@spartacus/cart/quick-order/root": ["dist/cart/quick-order/root"],
"@spartacus/cart/saved-cart/assets": ["dist/cart/saved-cart/assets"],
"@spartacus/cart/saved-cart/components": [
"dist/cart/saved-cart/components"
Expand Down
1 change: 1 addition & 0 deletions feature-libs/cart/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ Cart can be added to the existing Spartacus application by running `ng add @spar
The Spartacus Cart library contains the following packages:

- `@spartacus/cart/saved-cart` is a package that adds the saved cart feature.
- `@spartacus/cart/quick-order` is a package that adds the quick order feature.

For more information about Spartacus, see [Spartacus](https://github.com/SAP/spartacus).
1 change: 1 addition & 0 deletions feature-libs/cart/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
}

@import './saved-cart/index';
@import './quick-order/index';
3 changes: 2 additions & 1 deletion feature-libs/cart/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"cart",
"shopping",
"shop",
"saved cart"
"saved cart",
"quick order"
],
"homepage": "https://github.com/SAP/spartacus",
"repository": "https://github.com/SAP/spartacus/tree/develop/feature-libs/cart",
Expand Down
1 change: 1 addition & 0 deletions feature-libs/cart/quick-order/_index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import './styles/index';
6 changes: 6 additions & 0 deletions feature-libs/cart/quick-order/assets/ng-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
"lib": {
"entryFile": "./public_api.ts"
}
}
1 change: 1 addition & 0 deletions feature-libs/cart/quick-order/assets/public_api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './translations/translations';
5 changes: 5 additions & 0 deletions feature-libs/cart/quick-order/assets/translations/en/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { quickOrder } from './quick-order.i18n';

export const en = {
quickOrder,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
export const quickOrderCartForm = {
entriesWasAdded: '({{ quantity }}) {{ product }} has been added to the cart',
entryWasAdded: '{{ product }} has been added to the cart',
noResults: 'We could not find any products',
stockLevelReached: 'The maximum stock level has been reached',
title: 'Quick Order',
productCodePlaceholder: 'Enter ID',
addToCart: 'Add',
product: 'Product',
products: 'Products',
productCodeLabel: 'Product ID',
quantityLabel: 'Qty',
};

export const quickOrderForm = {
placeholder: 'Enter Product SKU',
listLimitReached: 'The product limit has been reached',
};

export const quickOrderList = {
addToCart: 'Add to cart',
emptyList: 'Empty list',
header: 'Add Products/Skus',
subHeader: 'You can add up to {{ limit }} valid SKU at a time.',
errorProceedingToCart: 'Error proceeding to Cart.',
errors: {
productIsOutOfStock: '{{ name }} (#{{code}}) is out of stock.',
productWasReduced:
'Quantity for {{ name }} (#{{code}}) was reduced to {{ quantityAdded}}.',
},
};

export const quickOrderTable = {
product: 'Product',
id: 'ID',
price: 'Price',
quantity: 'QTY',
itemPrice: 'Item price',
qty: 'Qty',
inStock: 'In Stock',
lowStock: 'Low Stock',
outOfStock: 'Out of Stock',
listCleared: 'Quick order list has been cleared',
addedtoCart: 'Quick order list has been added to the cart',
};

export const quickOrder = {
quickOrderCartForm,
quickOrderForm,
quickOrderList,
quickOrderTable,
};
16 changes: 16 additions & 0 deletions feature-libs/cart/quick-order/assets/translations/translations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TranslationChunksConfig, TranslationResources } from '@spartacus/core';
import { en } from './en/index';

export const quickOrderTranslations: TranslationResources = {
en,
};

// expose all translation chunk mapping for quickOrder feature
export const quickOrderTranslationChunksConfig: TranslationChunksConfig = {
quickOrder: [
'quickOrderCartForm',
'quickOrderForm',
'quickOrderList',
'quickOrderTable',
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<ng-container *ngIf="cart$ | async as cart">
<div class="cx-cart-quick-order-form-title">
{{ 'quickOrderCartForm.title' | cxTranslate }}
</div>
<div class="form-group">
<form (ngSubmit)="applyQuickOrder()" [formGroup]="quickOrderForm">
<div class="cx-cart-quick-order-form-container">
<span class="cx-cart-quick-order-form-productID">
<label class="cx-cart-quick-order-form-label">
{{ 'quickOrderCartForm.productCodeLabel' | cxTranslate }}
</label>
<input
class="form-control input-product-code"
formControlName="productCode"
placeholder="{{
'quickOrderCartForm.productCodePlaceholder' | cxTranslate
}}"
type="text"
/>
</span>

<span class="cx-cart-quick-order-form-qty">
<label class="cx-cart-quick-order-form-label">
{{ 'quickOrderCartForm.quantityLabel' | cxTranslate }}
</label>
<input
[min]="min"
class="form-control input-quantity"
formControlName="quantity"
type="number"
/>
</span>
<button
[class.disabled]="cartIsLoading$ | async"
[disabled]="cartIsLoading$ | async"
class="btn btn-block btn-action apply-quick-order-button"
type="submit"
>
{{ 'quickOrderCartForm.addToCart' | cxTranslate }}
</button>
<cx-form-errors
[control]="quickOrderForm.get('productCode')"
></cx-form-errors>
<cx-form-errors
[control]="quickOrderForm.get('quantity')"
></cx-form-errors>
</div>
</form></div
></ng-container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { StoreModule } from '@ngrx/store';
import {
ActiveCartService,
Cart,
CartAddEntryFailEvent,
CartAddEntrySuccessEvent,
EventService,
GlobalMessageService,
GlobalMessageType,
I18nTestingModule,
Translatable,
} from '@spartacus/core';
import { FormErrorsModule } from '@spartacus/storefront';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { CartQuickOrderFormComponent } from './cart-quick-order-form.component';

const mockCart: Cart = {
code: '123456789',
description: 'testCartDescription',
name: 'testCartName',
};

const mockUserId = 'test-user';
const mockCartId = '123456789';

const mockCartAddEntryFailEvent: CartAddEntryFailEvent = {
cartCode: mockCartId,
cartId: mockCartId,
productCode: '123456789',
quantity: 1,
userId: mockUserId,
};
const mockCartAddEntrySuccessEvent: CartAddEntrySuccessEvent = {
cartCode: mockCartId,
cartId: mockCartId,
deliveryModeChanged: false,
entry: {
product: {
name: 'test-product',
},
},
productCode: '123456789',
quantity: 1,
quantityAdded: 1,
userId: mockUserId,
};

const cart$ = new BehaviorSubject<Cart>(mockCart);

class MockEventService implements Partial<EventService> {
get(): Observable<any> {
return of();
}
}

class MockGlobalMessageService implements Partial<GlobalMessageService> {
add(
_text: string | Translatable,
_type: GlobalMessageType,
_timeout?: number
): void {}
}

class MockActiveCartService implements Partial<ActiveCartService> {
getActive(): Observable<Cart> {
return cart$.asObservable();
}
getActiveCartId(): Observable<string> {
return of('123456789');
}
isStable(): Observable<boolean> {
return of(true);
}
addEntry(_productCode: string, _quantity: number): void {}
}

describe('CartQuickOrderFormComponent', () => {
let component: CartQuickOrderFormComponent;
let fixture: ComponentFixture<CartQuickOrderFormComponent>;
let activeCartService: ActiveCartService;
let eventService: EventService;
let globalMessageService: GlobalMessageService;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
StoreModule.forRoot({}),
FormErrorsModule,
I18nTestingModule,
ReactiveFormsModule,
],
declarations: [CartQuickOrderFormComponent],
providers: [
{ provide: ActiveCartService, useClass: MockActiveCartService },
{
provide: EventService,
useClass: MockEventService,
},
{ provide: GlobalMessageService, useClass: MockGlobalMessageService },
],
}).compileComponents();

fixture = TestBed.createComponent(CartQuickOrderFormComponent);
component = fixture.componentInstance;

activeCartService = TestBed.inject(ActiveCartService);
eventService = TestBed.inject(EventService);
globalMessageService = TestBed.inject(GlobalMessageService);

fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should create form on init', () => {
expect(component.quickOrderForm.valid).toBeFalsy();
expect(component.quickOrderForm.controls['productCode'].value).toBe('');
expect(component.quickOrderForm.controls['quantity'].value).toBe(1);
});

it('should add entry on form submit', () => {
spyOn(activeCartService, 'addEntry').and.callThrough();

component.quickOrderForm.controls['productCode'].setValue('test');
component.applyQuickOrder();

expect(activeCartService.addEntry).toHaveBeenCalledWith('test', 1);
});

it('should set quantity value to min when it is smaller than min value', () => {
component.min = 3;
component.quickOrderForm.controls['quantity'].setValue(2);
fixture.detectChanges();

expect(component.quickOrderForm.controls['quantity'].value).toEqual(3);
});

it('should show global confirmation message on add entry success event', () => {
spyOn(globalMessageService, 'add').and.callThrough();
spyOn(eventService, 'get').and.returnValue(
of(mockCartAddEntrySuccessEvent)
);

component.ngOnInit();
component.quickOrderForm.controls['productCode'].setValue('test');
component.applyQuickOrder();

expect(globalMessageService.add).toHaveBeenCalledWith(
{
key: 'quickOrderCartForm.entryWasAdded',
params: {
product: mockCartAddEntrySuccessEvent.entry?.product?.name,
quantity: mockCartAddEntrySuccessEvent.quantityAdded,
},
},
GlobalMessageType.MSG_TYPE_CONFIRMATION
);
});

it('should show global error message on add entry fail event', () => {
spyOn(globalMessageService, 'add').and.callThrough();
spyOn(eventService, 'get').and.returnValue(of(mockCartAddEntryFailEvent));

component.ngOnInit();
component.quickOrderForm.controls['productCode'].setValue('test');
component.applyQuickOrder();

expect(globalMessageService.add).toHaveBeenCalledWith(
{
key: 'quickOrderCartForm.noResults',
},
GlobalMessageType.MSG_TYPE_ERROR
);
});
});
Loading

0 comments on commit 9ed6216

Please sign in to comment.