-
-
Notifications
You must be signed in to change notification settings - Fork 32
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 retrieval action reducer factories (#2633)
- Loading branch information
Showing
6 changed files
with
363 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export * from './stateful-cart'; | ||
export * from './stateful-cart-item'; | ||
export * from './stateful-composite-cart-item'; | ||
export * from './stateful-configurable-cart-item'; |
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,11 @@ | ||
import { DaffCart } from '@daffodil/cart'; | ||
|
||
import { DaffStatefulCartItem } from './stateful-cart-item'; | ||
|
||
/** | ||
* A cart with stateful cart items. | ||
*/ | ||
export interface DaffStatefulCart extends DaffCart { | ||
items: DaffStatefulCartItem[]; | ||
} | ||
|
195 changes: 195 additions & 0 deletions
195
libs/cart/state/src/reducers/cart-item-entities/retrieval-actions.reducer.spec.ts
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,195 @@ | ||
|
||
import { TestBed } from '@angular/core/testing'; | ||
import { EntityState } from '@ngrx/entity'; | ||
import { | ||
Action, | ||
ActionReducer, | ||
} from '@ngrx/store'; | ||
|
||
import { | ||
DaffCartItemStateEnum, | ||
DaffCartRetrievalAction, | ||
DaffStatefulCart, | ||
DaffStatefulCartItem, | ||
daffCartItemEntitiesAdapter, | ||
} from '@daffodil/cart/state'; | ||
import { DaffStatefulCartItemFactory } from '@daffodil/cart/state/testing'; | ||
import { DaffCartFactory } from '@daffodil/cart/testing'; | ||
import { DaffStateError } from '@daffodil/core/state'; | ||
|
||
import { daffCartItemEntitiesRetrievalActionsReducerFactory } from './retrieval-actions.reducer'; | ||
|
||
class DirectAction implements DaffCartRetrievalAction { | ||
type = 'direct'; | ||
constructor(public payload: DaffStatefulCart) {} | ||
} | ||
|
||
class TransformAction<T extends DaffStatefulCart = DaffStatefulCart> implements Action { | ||
type = 'transform'; | ||
constructor(public response: T) {} | ||
} | ||
|
||
describe('@daffodil/cart/state | daffCartItemEntitiesRetrievalActionsReducerFactory', () => { | ||
let cartItemFactory: DaffStatefulCartItemFactory; | ||
let cartFactory: DaffCartFactory; | ||
|
||
let error: DaffStateError; | ||
let cart: DaffStatefulCart; | ||
let reducer: ActionReducer<EntityState<DaffStatefulCartItem>>; | ||
let result: EntityState<DaffStatefulCartItem>; | ||
let initialState: EntityState<DaffStatefulCartItem>; | ||
|
||
beforeEach(() => { | ||
initialState = daffCartItemEntitiesAdapter().getInitialState(); | ||
cartFactory = TestBed.inject(DaffCartFactory); | ||
cartItemFactory = TestBed.inject(DaffStatefulCartItemFactory); | ||
|
||
cart = { | ||
...cartFactory.create(), | ||
items: cartItemFactory.createMany(3), | ||
}; | ||
error = { | ||
code: 'error code', | ||
message: 'error message', | ||
}; | ||
|
||
reducer = daffCartItemEntitiesRetrievalActionsReducerFactory([ | ||
{ type: 'direct' }, | ||
{ type: 'transform', transform: <any>((action: TransformAction) => action.response) }, | ||
]); | ||
}); | ||
|
||
describe('when an unknown action is triggered', () => { | ||
it('should return the current state', () => { | ||
const action = <any>{}; | ||
|
||
result = reducer(initialState, action); | ||
|
||
expect(result).toEqual(initialState); | ||
}); | ||
}); | ||
|
||
describe('when DirectAction is triggered', () => { | ||
beforeEach(() => { | ||
result = reducer(initialState, new DirectAction(cart)); | ||
}); | ||
|
||
it('should store the cart items', () => { | ||
cart.items.forEach((item) => { | ||
expect(result.entities[item.id]).toEqual(item); | ||
}); | ||
}); | ||
|
||
describe('and when an incoming cart item has an existing daffState', () => { | ||
beforeEach(() => { | ||
cart.items[0].daffState = DaffCartItemStateEnum.New; | ||
result = reducer(initialState, new DirectAction(cart)); | ||
}); | ||
|
||
it('should retain the existing daffState', () => { | ||
expect(result.entities[cart.items[0].id].daffState).toEqual(cart.items[0].daffState); | ||
}); | ||
}); | ||
|
||
describe('and when an incoming cart item does not have an existing daffState', () => { | ||
beforeEach(() => { | ||
cart.items[0].daffState = undefined; | ||
result = reducer(initialState, new DirectAction(cart)); | ||
}); | ||
|
||
describe('and the item in state has an existing daffState', () => { | ||
let state: EntityState<DaffStatefulCartItem>; | ||
|
||
beforeEach(() => { | ||
state = daffCartItemEntitiesAdapter().addOne({ | ||
...cart.items[0], | ||
daffState: DaffCartItemStateEnum.New, | ||
}, initialState); | ||
result = reducer(state, new DirectAction(cart)); | ||
}); | ||
|
||
it('should retain the existing daffState in entity state', () => { | ||
expect(result.entities[cart.items[0].id].daffState).toEqual(state.entities[cart.items[0].id].daffState); | ||
}); | ||
}); | ||
|
||
describe('and the item in state does not have an existing daffState', () => { | ||
let state: EntityState<DaffStatefulCartItem>; | ||
|
||
beforeEach(() => { | ||
state = daffCartItemEntitiesAdapter().addOne({ | ||
...cart.items[0], | ||
daffState: undefined, | ||
}, initialState); | ||
result = reducer(state, new DirectAction(cart)); | ||
}); | ||
|
||
it('should initialize the daffState field', () => { | ||
expect(result.entities[cart.items[0].id].daffState).toEqual(DaffCartItemStateEnum.Default); | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('when TransformAction is triggered', () => { | ||
beforeEach(() => { | ||
result = reducer(initialState, new TransformAction(cart)); | ||
}); | ||
|
||
it('should store the cart items', () => { | ||
cart.items.forEach((item) => { | ||
expect(result.entities[item.id]).toEqual(item); | ||
}); | ||
}); | ||
|
||
describe('when an incoming cart item has an existing daffState', () => { | ||
beforeEach(() => { | ||
cart.items[0].daffState = DaffCartItemStateEnum.New; | ||
result = reducer(initialState, new TransformAction(cart)); | ||
}); | ||
|
||
it('should retain the existing daffState', () => { | ||
expect(result.entities[cart.items[0].id].daffState).toEqual(cart.items[0].daffState); | ||
}); | ||
}); | ||
|
||
describe('when an incoming cart item does not have an existing daffState', () => { | ||
beforeEach(() => { | ||
cart.items[0].daffState = undefined; | ||
result = reducer(initialState, new TransformAction(cart)); | ||
}); | ||
|
||
describe('and the item in state has an existing daffState', () => { | ||
let state: EntityState<DaffStatefulCartItem>; | ||
|
||
beforeEach(() => { | ||
state = daffCartItemEntitiesAdapter().addOne({ | ||
...cart.items[0], | ||
daffState: DaffCartItemStateEnum.New, | ||
}, initialState); | ||
result = reducer(state, new TransformAction(cart)); | ||
}); | ||
|
||
it('should retain the existing daffState in entity state', () => { | ||
expect(result.entities[cart.items[0].id].daffState).toEqual(state.entities[cart.items[0].id].daffState); | ||
}); | ||
}); | ||
|
||
describe('and the item in state does not have an existing daffState', () => { | ||
let state: EntityState<DaffStatefulCartItem>; | ||
|
||
beforeEach(() => { | ||
state = daffCartItemEntitiesAdapter().addOne({ | ||
...cart.items[0], | ||
daffState: undefined, | ||
}, initialState); | ||
result = reducer(state, new TransformAction(cart)); | ||
}); | ||
|
||
it('should initialize the daffState field', () => { | ||
expect(result.entities[cart.items[0].id].daffState).toEqual(DaffCartItemStateEnum.Default); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
38 changes: 38 additions & 0 deletions
38
libs/cart/state/src/reducers/cart-item-entities/retrieval-actions.reducer.ts
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,38 @@ | ||
import { EntityState } from '@ngrx/entity'; | ||
import { Action } from '@ngrx/store'; | ||
|
||
import { DaffCart } from '@daffodil/cart'; | ||
|
||
import { daffCartItemEntitiesAdapter } from './cart-item-entities-reducer-adapter'; | ||
import { | ||
DaffCartRetrievalActionInjection, | ||
daffCartRetrievalGetResponse, | ||
} from '../../cart-retrieval/public_api'; | ||
import { | ||
DaffCartItemStateEnum, | ||
DaffStatefulCartItem, | ||
} from '../../models/stateful-cart-item'; | ||
|
||
/** | ||
* Reducer function factory that updates cart item entities according to a list of passed cart retrieval actions. | ||
* See {@link DaffCartRetrievalActionInjection}. | ||
*/ | ||
export function daffCartItemEntitiesRetrievalActionsReducerFactory< | ||
T extends DaffStatefulCartItem = DaffStatefulCartItem, | ||
V extends DaffCart = DaffCart, | ||
>(retrievalActions: DaffCartRetrievalActionInjection[]) { | ||
return ( | ||
state = daffCartItemEntitiesAdapter<T>().getInitialState(), | ||
action: Action, | ||
): EntityState<T> => { | ||
const adapter = daffCartItemEntitiesAdapter<T>(); | ||
const cart = daffCartRetrievalGetResponse<V>(action, retrievalActions); | ||
|
||
return cart?.items | ||
? adapter.setAll(cart.items.map((item: T) => ({ | ||
...item, | ||
daffState: item.daffState || state.entities[item.id]?.daffState || DaffCartItemStateEnum.Default, | ||
})), state) | ||
: state; | ||
}; | ||
} |
80 changes: 80 additions & 0 deletions
80
libs/cart/state/src/reducers/cart/retrieval-actions.reducer.spec.ts
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,80 @@ | ||
|
||
import { TestBed } from '@angular/core/testing'; | ||
import { | ||
Action, | ||
ActionReducer, | ||
} from '@ngrx/store'; | ||
|
||
import { DaffCart } from '@daffodil/cart'; | ||
import { | ||
DaffCartReducerState, | ||
daffCartReducerInitialState, | ||
DaffCartRetrievalAction, | ||
} from '@daffodil/cart/state'; | ||
import { DaffCartFactory } from '@daffodil/cart/testing'; | ||
import { DaffStateError } from '@daffodil/core/state'; | ||
|
||
import { daffCartRetrievalActionsReducerFactory } from './retrieval-actions.reducer'; | ||
|
||
class DirectAction implements DaffCartRetrievalAction { | ||
type = 'direct'; | ||
constructor(public payload: DaffCart) {} | ||
} | ||
|
||
class TransformAction implements Action { | ||
type = 'transform'; | ||
constructor(public response: DaffCart) {} | ||
} | ||
|
||
describe('@daffodil/cart/state | daffCartRetrievalActionsReducerFactory', () => { | ||
let cartFactory: DaffCartFactory; | ||
let error: DaffStateError; | ||
let cart: DaffCart; | ||
let reducer: ActionReducer<DaffCartReducerState>; | ||
let result: DaffCartReducerState; | ||
|
||
beforeEach(() => { | ||
cartFactory = TestBed.inject(DaffCartFactory); | ||
|
||
cart = cartFactory.create(); | ||
error = { | ||
code: 'error code', | ||
message: 'error message', | ||
}; | ||
|
||
reducer = daffCartRetrievalActionsReducerFactory([ | ||
{ type: 'direct' }, | ||
{ type: 'transform', transform: <any>((action: TransformAction) => action.response) }, | ||
]); | ||
}); | ||
|
||
describe('when an unknown action is triggered', () => { | ||
it('should return the current state', () => { | ||
const action = <any>{}; | ||
|
||
result = reducer(daffCartReducerInitialState, action); | ||
|
||
expect(result).toEqual(daffCartReducerInitialState); | ||
}); | ||
}); | ||
|
||
describe('when DirectAction is triggered', () => { | ||
beforeEach(() => { | ||
result = reducer(daffCartReducerInitialState, new DirectAction(cart)); | ||
}); | ||
|
||
it('should store the cart', () => { | ||
expect(result.cart).toEqual(cart); | ||
}); | ||
}); | ||
|
||
describe('when TransformAction is triggered', () => { | ||
beforeEach(() => { | ||
result = reducer(daffCartReducerInitialState, new TransformAction(cart)); | ||
}); | ||
|
||
it('should store the cart', () => { | ||
expect(result.cart).toEqual(cart); | ||
}); | ||
}); | ||
}); |
38 changes: 38 additions & 0 deletions
38
libs/cart/state/src/reducers/cart/retrieval-actions.reducer.ts
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,38 @@ | ||
import { | ||
Action, | ||
ActionReducer, | ||
} from '@ngrx/store'; | ||
|
||
import { DaffCart } from '@daffodil/cart'; | ||
|
||
import { | ||
DaffCartRetrievalActionInjection, | ||
daffCartRetrievalGetResponse, | ||
} from '../../cart-retrieval/public_api'; | ||
import { daffCartReducerInitialState } from '../cart-initial-state'; | ||
import { DaffCartReducerState } from '../cart-state.interface'; | ||
|
||
/** | ||
* Reducer function factory that updates cart state according to a list of passed cart retrieval actions. | ||
* See {@link DaffCartRetrievalActionInjection}. | ||
*/ | ||
export function daffCartRetrievalActionsReducerFactory< | ||
T extends DaffCart = DaffCart, | ||
>(retrievalActions: DaffCartRetrievalActionInjection[]): ActionReducer<DaffCartReducerState<T>> { | ||
return ( | ||
state = daffCartReducerInitialState, | ||
action: Action, | ||
): DaffCartReducerState<T> => { | ||
const cart = daffCartRetrievalGetResponse<T>(action, retrievalActions); | ||
|
||
return cart | ||
? { | ||
...state, | ||
cart: { | ||
...state.cart, | ||
...cart, | ||
}, | ||
} | ||
: state; | ||
}; | ||
} |