Skip to content

Commit

Permalink
feat(cart): add retrieval action reducer factories (#2633)
Browse files Browse the repository at this point in the history
  • Loading branch information
griest024 committed Dec 1, 2023
1 parent 2a8cec3 commit 57c8f83
Show file tree
Hide file tree
Showing 6 changed files with 363 additions and 0 deletions.
1 change: 1 addition & 0 deletions libs/cart/state/src/models/public_api.ts
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';
11 changes: 11 additions & 0 deletions libs/cart/state/src/models/stateful-cart.ts
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[];
}

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);
});
});
});
});
});
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;
};
}
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 libs/cart/state/src/reducers/cart/retrieval-actions.reducer.ts
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;
};
}

0 comments on commit 57c8f83

Please sign in to comment.