-
-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cart): add resolver action and effect for loading the cart (#923)
- Loading branch information
1 parent
3b0d47a
commit ddf84a4
Showing
5 changed files
with
279 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
import { TestBed } from '@angular/core/testing'; | ||
import { provideMockActions } from '@ngrx/effects/testing'; | ||
import { Observable, of } from 'rxjs'; | ||
import { hot, cold } from 'jasmine-marbles'; | ||
|
||
import { | ||
DaffCartFactory, | ||
} from '@daffodil/cart/testing'; | ||
|
||
import { DaffCartResolverEffects } from './cart-resolver.effects'; | ||
import { DaffCartDriver, DaffCartServiceInterface } from '../drivers/public_api'; | ||
import { DaffCart } from '../models/cart'; | ||
import { DaffCartStorageService } from '../storage/cart-storage.service'; | ||
import { | ||
DaffResolveCart, | ||
DaffCartLoadFailure, | ||
DaffCartLoadSuccess, | ||
DaffCartCreate, | ||
DaffCartStorageFailure | ||
} from '../actions/public_api'; | ||
import { DaffCartNotFoundError } from '../errors/not-found'; | ||
import { DaffStorageServiceError } from '@daffodil/core'; | ||
|
||
describe('DaffCartResolverEffects', () => { | ||
let actions$: Observable<any>; | ||
let effects: DaffCartResolverEffects; | ||
let driver: jasmine.SpyObj<DaffCartServiceInterface>; | ||
|
||
let cartFactory: DaffCartFactory; | ||
let stubCart: DaffCart; | ||
|
||
let cartStorageService: jasmine.SpyObj<DaffCartStorageService>; | ||
|
||
beforeEach(() => { | ||
TestBed.configureTestingModule({ | ||
providers: [ | ||
DaffCartResolverEffects, | ||
provideMockActions(() => actions$), | ||
{ | ||
provide: DaffCartDriver, | ||
useValue: jasmine.createSpyObj('DaffCartDriver', ['get', 'create', 'clear', 'addToCart']) | ||
}, | ||
{ | ||
provide: DaffCartStorageService, | ||
useValue: jasmine.createSpyObj('DaffCartStorageService', ['setCartId', 'getCartId']) | ||
} | ||
], | ||
}); | ||
|
||
effects = TestBed.get(DaffCartResolverEffects); | ||
driver = TestBed.get(DaffCartDriver); | ||
cartFactory = TestBed.get(DaffCartFactory); | ||
cartStorageService = TestBed.get(DaffCartStorageService); | ||
stubCart = cartFactory.create(); | ||
}); | ||
|
||
it('should be created', () => { | ||
expect(effects).toBeTruthy(); | ||
}); | ||
|
||
describe('handling storage errors e.g. being in SSR', () => { | ||
it('should dispatch a DaffCartLoadFailure action', () => { | ||
cartStorageService.getCartId.and.throwError('Storage error'); | ||
|
||
const loadCartFailureAction = new DaffCartLoadFailure( | ||
'Cart loading has failed', | ||
); | ||
|
||
actions$ = hot('--a', { a: new DaffResolveCart() }); | ||
const expected = cold('--(b|)', { | ||
b: loadCartFailureAction | ||
}); | ||
|
||
expect(effects.onResolveCart$).toBeObservable(expected); | ||
}); | ||
}); | ||
|
||
describe('successfully resolving a cart when theres a cart id in storage', () => { | ||
beforeEach(() => { | ||
cartStorageService.getCartId.and.returnValue( | ||
stubCart.id.toString(), | ||
); | ||
driver.get.and.returnValue(of(stubCart)); | ||
}); | ||
|
||
it('should not attempt to create a cart', () => { | ||
const loadCartSuccessAction = new DaffCartLoadSuccess(stubCart); | ||
|
||
actions$ = hot('--a', { a: new DaffResolveCart() }); | ||
const expected = cold('--(b)', { | ||
b: loadCartSuccessAction, | ||
}); | ||
|
||
expect(effects.onResolveCart$).toBeObservable(expected); | ||
expect(driver.create).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it('should indicate that a cart has loaded', () => { | ||
const loadCartSuccessAction = new DaffCartLoadSuccess(stubCart); | ||
|
||
actions$ = hot('--a', { a: new DaffResolveCart() }); | ||
const expected = cold('--(b)', { | ||
b: loadCartSuccessAction | ||
}); | ||
|
||
expect(effects.onResolveCart$).toBeObservable(expected); | ||
}); | ||
}); | ||
|
||
describe('handling XHR errors when creating a cart', () => { | ||
it('should dispatch DaffCartLoadFailure action', () => { | ||
const response = cold('#', {}); | ||
driver.create.and.returnValue(response); | ||
|
||
const loadCartFailureAction = new DaffCartLoadFailure( | ||
'Cart loading has failed', | ||
); | ||
|
||
actions$ = hot('--a', { a: new DaffResolveCart() }); | ||
const expected = cold('--(b|)', { | ||
b: loadCartFailureAction | ||
}); | ||
|
||
expect(effects.onResolveCart$).toBeObservable(expected); | ||
}); | ||
}); | ||
|
||
describe('handling XHR errors when retrieving a cart', () => { | ||
it('should dispatch a DaffCartLoadFailure action', () => { | ||
const response = cold('#', {}); | ||
driver.get.and.returnValue(response); | ||
driver.create.and.returnValue(response); | ||
|
||
const loadCartFailureAction = new DaffCartLoadFailure( | ||
'Cart loading has failed', | ||
); | ||
|
||
actions$ = hot('--a', { a: new DaffResolveCart() }); | ||
const expected = cold('--(b|)', { | ||
b: loadCartFailureAction | ||
}); | ||
|
||
expect(effects.onResolveCart$).toBeObservable(expected); | ||
}); | ||
}); | ||
|
||
describe('creating a cart when there is no cart id in storage', () => { | ||
it('should create a cart', () => { | ||
cartStorageService.getCartId.and.returnValue(undefined); | ||
driver.create.and.returnValue(of({ id: stubCart.id })); | ||
driver.get.and.returnValue(of(stubCart)); | ||
|
||
const loadCartSuccessAction = new DaffCartLoadSuccess(stubCart); | ||
|
||
actions$ = hot('--a', { a: new DaffResolveCart() }); | ||
const expected = cold('--(b)', { | ||
b: loadCartSuccessAction | ||
}); | ||
|
||
expect(effects.onResolveCart$).toBeObservable(expected); | ||
expect(driver.create).toHaveBeenCalled(); | ||
}); | ||
|
||
it('should set the cart id in local storage', () => { | ||
cartStorageService.getCartId.and.returnValue(undefined); | ||
driver.create.and.returnValue(of({ id: stubCart.id })); | ||
driver.get.and.returnValue(of(stubCart)); | ||
|
||
const loadCartSuccessAction = new DaffCartLoadSuccess(stubCart); | ||
|
||
actions$ = hot('--a', { a: new DaffResolveCart() }); | ||
const expected = cold('--(b)', { | ||
b: loadCartSuccessAction | ||
}); | ||
|
||
expect(effects.onResolveCart$).toBeObservable(expected); | ||
expect(cartStorageService.setCartId).toHaveBeenCalledWith(String(stubCart.id)); | ||
}); | ||
}); | ||
|
||
describe('when the error thrown is a DaffCartNotFoundError', () => { | ||
|
||
it('should create a new cart', () => { | ||
cartStorageService.getCartId.and.returnValue('id'); | ||
const response = cold('#', {}, new DaffCartNotFoundError('error')); | ||
driver.get.and.returnValue(response); | ||
|
||
const cartCreateAction = new DaffCartCreate(); | ||
|
||
actions$ = hot('--a', { a: new DaffResolveCart() }); | ||
const expected = cold('--(b|)', { | ||
b: cartCreateAction, | ||
}); | ||
|
||
expect(effects.onResolveCart$).toBeObservable(expected); | ||
}); | ||
}); | ||
|
||
describe('when the error thrown is a DaffStorageServiceError', () => { | ||
|
||
it('should indicate that the storage service has failed', () => { | ||
const response = cold('#', {}, new DaffStorageServiceError()); | ||
driver.get.and.returnValue(response); | ||
driver.create.and.returnValue(response); | ||
|
||
const cartStorageFailureAction = new DaffCartStorageFailure(); | ||
|
||
actions$ = hot('--a', { a: new DaffResolveCart() }); | ||
const expected = cold('--(b|)', { | ||
b: cartStorageFailureAction | ||
}); | ||
|
||
expect(effects.onResolveCart$).toBeObservable(expected); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { Injectable, Inject } from '@angular/core'; | ||
import { Actions, Effect, ofType } from '@ngrx/effects'; | ||
import { Action } from '@ngrx/store'; | ||
import { Observable, of } from 'rxjs'; | ||
import { switchMap, catchError, map } from 'rxjs/operators'; | ||
|
||
import { | ||
DaffCartActionTypes, | ||
DaffCartLoadSuccess, | ||
DaffCartCreate, | ||
DaffCartLoadFailure, | ||
DaffCartStorageFailure | ||
} from '../actions/public_api'; | ||
import { DaffCartStorageService } from '../storage/cart-storage.service'; | ||
import { DaffCartDriver, DaffCartServiceInterface } from '../drivers/public_api'; | ||
import { DaffCart } from '../models/cart'; | ||
import { DaffCartNotFoundError } from '../errors/not-found'; | ||
import { DaffStorageServiceError } from '@daffodil/core'; | ||
|
||
/** | ||
* An effect for resolving the Cart. It will check local state for a cart id, and retrieve the cart if it exists. If a cart | ||
* of that id does not exist, it will create a new cart. | ||
*/ | ||
@Injectable() | ||
export class DaffCartResolverEffects<T extends DaffCart = DaffCart> { | ||
constructor( | ||
private actions$: Actions, | ||
private cartStorage: DaffCartStorageService, | ||
@Inject(DaffCartDriver) private driver: DaffCartServiceInterface<T>, | ||
) {} | ||
|
||
@Effect() | ||
onResolveCart$: Observable<Action> = this.actions$.pipe( | ||
ofType(DaffCartActionTypes.ResolveCartAction), | ||
map(() => this.cartStorage.getCartId()), | ||
switchMap(id => id ? of({ id }) : this.driver.create()), | ||
switchMap(({ id }) => this.driver.get(id)), | ||
switchMap(resp => { | ||
this.cartStorage.setCartId(String(resp.id)) | ||
return [new DaffCartLoadSuccess(resp)] | ||
}), | ||
catchError(error => { | ||
switch(error.constructor) { | ||
case DaffStorageServiceError: | ||
return of(new DaffCartStorageFailure()) | ||
case DaffCartNotFoundError: | ||
return of(new DaffCartCreate()); | ||
default: | ||
return of(new DaffCartLoadFailure('Cart loading has failed')); | ||
} | ||
}), | ||
); | ||
} |