From a70da6484f9b82c85639665515e3f56e7f3e796b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20H=C3=A4hnlein?= Date: Wed, 5 Feb 2020 16:54:14 +0100 Subject: [PATCH] fix: "Add Quote to Cart" behaviour changed to only route on success and hide the button on error (#51) - includes reset quote error functionality closes #51 --- .../quoting/facades/quoting.facade.ts | 5 ++++ .../quote-edit-page.component.spec.ts | 9 +------ .../quote-edit/quote-edit-page.component.ts | 4 +-- .../quote-edit/quote-edit.component.html | 2 +- .../quote-edit/quote-edit.component.spec.ts | 6 ++++- .../quote/quote-edit/quote-edit.component.ts | 19 ++++++++++++-- .../quoting/store/quote/quote.actions.ts | 8 +++++- .../quoting/store/quote/quote.effects.spec.ts | 26 +++++++++++++++---- .../quoting/store/quote/quote.effects.ts | 11 ++++++++ .../quoting/store/quote/quote.reducer.ts | 7 +++++ 10 files changed, 76 insertions(+), 21 deletions(-) diff --git a/src/app/extensions/quoting/facades/quoting.facade.ts b/src/app/extensions/quoting/facades/quoting.facade.ts index 16982dac0b..09ad33d92c 100644 --- a/src/app/extensions/quoting/facades/quoting.facade.ts +++ b/src/app/extensions/quoting/facades/quoting.facade.ts @@ -13,6 +13,7 @@ import { DeleteQuote, LoadQuotes, RejectQuote, + ResetQuoteError, getCurrentQuotes, getQuoteError, getQuoteLoading, @@ -74,6 +75,10 @@ export class QuotingFacade { this.store.dispatch(new AddQuoteToBasket({ quoteId })); } + resetQuoteError() { + this.store.dispatch(new ResetQuoteError()); + } + // QUOTE REQUEST quoteRequest$ = this.store.pipe(select(getSelectedQuoteRequestWithProducts)); quoteRequestLoading$ = this.store.pipe(select(getQuoteRequestLoading)); diff --git a/src/app/extensions/quoting/pages/quote-edit/quote-edit-page.component.spec.ts b/src/app/extensions/quoting/pages/quote-edit/quote-edit-page.component.spec.ts index 84b796750e..fed3d42af8 100644 --- a/src/app/extensions/quoting/pages/quote-edit/quote-edit-page.component.spec.ts +++ b/src/app/extensions/quoting/pages/quote-edit/quote-edit-page.component.spec.ts @@ -1,5 +1,5 @@ import { Location } from '@angular/common'; -import { ComponentFixture, TestBed, async, fakeAsync, tick } from '@angular/core/testing'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { Store, combineReducers } from '@ngrx/store'; import { TranslateModule } from '@ngx-translate/core'; @@ -20,7 +20,6 @@ describe('Quote Edit Page Component', () => { let fixture: ComponentFixture; let element: HTMLElement; let store$: Store<{}>; - let location: Location; beforeEach(async(() => { TestBed.configureTestingModule({ @@ -58,10 +57,4 @@ describe('Quote Edit Page Component', () => { fixture.detectChanges(); expect(element.querySelector('ish-loading')).toBeTruthy(); }); - - it('should navigate to basket when addToBasket is clicked', fakeAsync(() => { - component.addQuoteToBasket(undefined); - tick(50); - expect(location.path()).toBe('/basket'); - })); }); diff --git a/src/app/extensions/quoting/pages/quote-edit/quote-edit-page.component.ts b/src/app/extensions/quoting/pages/quote-edit/quote-edit-page.component.ts index 0878d7780d..0db06a37db 100644 --- a/src/app/extensions/quoting/pages/quote-edit/quote-edit-page.component.ts +++ b/src/app/extensions/quoting/pages/quote-edit/quote-edit-page.component.ts @@ -1,5 +1,4 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; import { Observable } from 'rxjs'; import { AccountFacade } from 'ish-core/facades/account.facade'; @@ -20,7 +19,7 @@ export class QuoteEditPageComponent implements OnInit { quoteError$: Observable; user$: Observable; - constructor(private quotingFacade: QuotingFacade, private accountFacade: AccountFacade, private router: Router) {} + constructor(private quotingFacade: QuotingFacade, private accountFacade: AccountFacade) {} ngOnInit() { this.quote$ = this.quotingFacade.quote$; @@ -39,6 +38,5 @@ export class QuoteEditPageComponent implements OnInit { addQuoteToBasket(quoteId: string) { this.quotingFacade.addQuoteToBasket(quoteId); - this.router.navigate(['/basket']); } } diff --git a/src/app/extensions/quoting/shared/quote/quote-edit/quote-edit.component.html b/src/app/extensions/quoting/shared/quote/quote-edit/quote-edit.component.html index 23307dd32a..fa75c07321 100644 --- a/src/app/extensions/quoting/shared/quote/quote-edit/quote-edit.component.html +++ b/src/app/extensions/quoting/shared/quote/quote-edit/quote-edit.component.html @@ -211,7 +211,7 @@

{{ 'quote.items.table.heading' | translate }}

- + diff --git a/src/app/extensions/quoting/shared/quote/quote-edit/quote-edit.component.spec.ts b/src/app/extensions/quoting/shared/quote/quote-edit/quote-edit.component.spec.ts index e02ffe3de2..d2633dc8f9 100644 --- a/src/app/extensions/quoting/shared/quote/quote-edit/quote-edit.component.spec.ts +++ b/src/app/extensions/quoting/shared/quote/quote-edit/quote-edit.component.spec.ts @@ -3,7 +3,7 @@ import { ReactiveFormsModule } from '@angular/forms'; import { RouterTestingModule } from '@angular/router/testing'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { MockComponent, MockDirective } from 'ng-mocks'; -import { anything, capture, spy, verify } from 'ts-mockito'; +import { anything, capture, instance, mock, spy, verify } from 'ts-mockito'; import { ServerHtmlDirective } from 'ish-core/directives/server-html.directive'; import { User } from 'ish-core/models/user/user.model'; @@ -14,6 +14,7 @@ import { LoadingComponent } from 'ish-shared/components/common/loading/loading.c import { RecentlyViewedComponent } from 'ish-shared/components/recently/recently-viewed/recently-viewed.component'; import { InputComponent } from 'ish-shared/forms/components/input/input.component'; +import { QuotingFacade } from '../../../facades/quoting.facade'; import { QuoteRequest } from '../../../models/quote-request/quote-request.model'; import { Quote } from '../../../models/quote/quote.model'; import { QuoteStateComponent } from '../quote-state/quote-state.component'; @@ -24,8 +25,10 @@ describe('Quote Edit Component', () => { let fixture: ComponentFixture; let component: QuoteEditComponent; let element: HTMLElement; + let quotingFacade: QuotingFacade; beforeEach(async(() => { + quotingFacade = mock(QuotingFacade); TestBed.configureTestingModule({ declarations: [ DatePipe, @@ -39,6 +42,7 @@ describe('Quote Edit Component', () => { QuoteEditComponent, ], imports: [ReactiveFormsModule, RouterTestingModule, TranslateModule.forRoot()], + providers: [{ provide: QuotingFacade, useFactory: () => instance(quotingFacade) }], }).compileComponents(); })); diff --git a/src/app/extensions/quoting/shared/quote/quote-edit/quote-edit.component.ts b/src/app/extensions/quoting/shared/quote/quote-edit/quote-edit.component.ts index 72a91a643f..b82ce5ca4c 100644 --- a/src/app/extensions/quoting/shared/quote/quote-edit/quote-edit.component.ts +++ b/src/app/extensions/quoting/shared/quote/quote-edit/quote-edit.component.ts @@ -4,6 +4,7 @@ import { EventEmitter, Input, OnChanges, + OnInit, Output, SimpleChanges, } from '@angular/core'; @@ -16,6 +17,7 @@ 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 { User } from 'ish-core/models/user/user.model'; +import { QuotingFacade } from '../../../facades/quoting.facade'; import { QuoteRequest } from '../../../models/quote-request/quote-request.model'; import { Quote } from '../../../models/quote/quote.model'; @@ -45,7 +47,7 @@ import { Quote } from '../../../models/quote/quote.model'; templateUrl: './quote-edit.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class QuoteEditComponent implements OnChanges { +export class QuoteEditComponent implements OnChanges, OnInit { @Input() quote: Quote | QuoteRequest; @Input() user: User; @Input() error: HttpError; @@ -68,15 +70,22 @@ export class QuoteEditComponent implements OnChanges { validToDate: number; saved = false; displaySavedMessage$: Observable; + showAddQuoteToCartButton = true; - constructor(private router: Router) { + constructor(private router: Router, private quotingFacade: QuotingFacade) { this.form = new FormGroup({ displayName: new FormControl(undefined, [Validators.maxLength(255)]), description: new FormControl(undefined, []), }); } + ngOnInit() { + this.quotingFacade.resetQuoteError(); + } + ngOnChanges(c: SimpleChanges) { + this.setShowAddQuoteToCartButton(c); + const quote = this.quote as Quote; this.sellerComment = quote.sellerComment; @@ -90,6 +99,12 @@ export class QuoteEditComponent implements OnChanges { this.toggleSaveMessage(); } + private setShowAddQuoteToCartButton(c: SimpleChanges) { + if (c.error && c.error.currentValue && !c.error.firstChange) { + this.showAddQuoteToCartButton = false; + } + } + private toggleSaveMessage() { if (!this.submitted && this.saved && !this.error && this.quote.state === 'New') { this.displaySavedMessage$ = merge(of(true), timer(5000).pipe(mapTo(false))); diff --git a/src/app/extensions/quoting/store/quote/quote.actions.ts b/src/app/extensions/quoting/store/quote/quote.actions.ts index 797ea9fdcd..f270c9993e 100644 --- a/src/app/extensions/quoting/store/quote/quote.actions.ts +++ b/src/app/extensions/quoting/store/quote/quote.actions.ts @@ -23,6 +23,7 @@ export enum QuoteActionTypes { 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 { @@ -102,6 +103,10 @@ export class AddQuoteToBasketSuccess implements Action { constructor(public payload: { link: Link }) {} } +export class ResetQuoteError implements Action { + readonly type = QuoteActionTypes.ResetQuoteError; +} + export type QuoteAction = | SelectQuote | LoadQuotes @@ -118,4 +123,5 @@ export type QuoteAction = | CreateQuoteRequestFromQuoteSuccess | AddQuoteToBasket | AddQuoteToBasketFail - | AddQuoteToBasketSuccess; + | AddQuoteToBasketSuccess + | ResetQuoteError; 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 8b122767d3..622e86b965 100644 --- a/src/app/extensions/quoting/store/quote/quote.effects.spec.ts +++ b/src/app/extensions/quoting/store/quote/quote.effects.spec.ts @@ -1,10 +1,11 @@ +import { Location } from '@angular/common'; import { Component } from '@angular/core'; -import { TestBed } from '@angular/core/testing'; +import { TestBed, async, fakeAsync, tick } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { provideMockActions } from '@ngrx/effects/testing'; import { Store, combineReducers } from '@ngrx/store'; import { cold, hot } from 'jest-marbles'; -import { of, throwError } from 'rxjs'; +import { noop, of, throwError } from 'rxjs'; import { anyString, anything, instance, mock, verify, when } from 'ts-mockito'; import { FeatureToggleModule } from 'ish-core/feature-toggle.module'; @@ -39,10 +40,11 @@ describe('Quote Effects', () => { let basketServiceMock: BasketService; let effects: QuoteEffects; let store$: Store<{}>; + let location: Location; const customer = { customerNo: 'CID', type: 'SMBCustomer' } as Customer; - beforeEach(() => { + beforeEach(async(() => { quoteServiceMock = mock(QuoteService); basketServiceMock = mock(BasketService); @@ -53,7 +55,10 @@ describe('Quote Effects', () => { declarations: [DummyComponent], imports: [ FeatureToggleModule, - RouterTestingModule.withRoutes([{ path: 'account/quote-request/:quoteRequestId', component: DummyComponent }]), + RouterTestingModule.withRoutes([ + { path: 'account/quote-request/:quoteRequestId', component: DummyComponent }, + { path: 'basket', component: DummyComponent }, + ]), ngrxTesting({ reducers: { quoting: combineReducers(quotingReducers), @@ -74,11 +79,12 @@ describe('Quote Effects', () => { effects = TestBed.get(QuoteEffects); store$ = TestBed.get(Store); + location = TestBed.get(Location); store$.dispatch(new ApplyConfiguration({ features: ['quoting'] })); store$.dispatch(new LoginUserSuccess({ customer })); store$.dispatch(new LoadCompanyUserSuccess({ user: { email: 'test' } as User })); - }); + })); describe('loadQuotes$', () => { beforeEach(() => { @@ -377,4 +383,14 @@ describe('Quote Effects', () => { expect(effects.addQuoteToBasket$).toBeObservable(expected$); }); }); + + describe('gotoBasketAfterAddQuoteToBasketSuccess$', () => { + it('should navigate to basket when success', fakeAsync(() => { + const action = new quoteActions.AddQuoteToBasketSuccess({ link: {} as Link }); + actions$ = of(action); + effects.gotoBasketAfterAddQuoteToBasketSuccess$.subscribe(noop, fail, noop); + tick(1000); + expect(location.path()).toBe('/basket'); + })); + }); }); diff --git a/src/app/extensions/quoting/store/quote/quote.effects.ts b/src/app/extensions/quoting/store/quote/quote.effects.ts index 54db6c4854..bba634e162 100644 --- a/src/app/extensions/quoting/store/quote/quote.effects.ts +++ b/src/app/extensions/quoting/store/quote/quote.effects.ts @@ -188,4 +188,15 @@ export class QuoteEffects { ofType(actions.QuoteActionTypes.AddQuoteToBasketSuccess, actions.QuoteActionTypes.AddQuoteToBasketFail), mapTo(new 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(actions.QuoteActionTypes.AddQuoteToBasketSuccess), + tap(() => { + this.router.navigate(['/basket']); + }) + ); } diff --git a/src/app/extensions/quoting/store/quote/quote.reducer.ts b/src/app/extensions/quoting/store/quote/quote.reducer.ts index 2a57a17015..f0f6c0ceb6 100644 --- a/src/app/extensions/quoting/store/quote/quote.reducer.ts +++ b/src/app/extensions/quoting/store/quote/quote.reducer.ts @@ -75,6 +75,13 @@ export function quoteReducer(state = initialState, action: QuoteAction): QuoteSt loading: false, }; } + + case QuoteActionTypes.ResetQuoteError: { + return { + ...state, + error: undefined, + }; + } } return state;