Skip to content

Commit

Permalink
feat(cart): use operation entity state for item entities
Browse files Browse the repository at this point in the history
  • Loading branch information
griest024 committed May 14, 2024
1 parent 10ff38e commit 6f9ad3c
Show file tree
Hide file tree
Showing 42 changed files with 566 additions and 1,947 deletions.
14 changes: 14 additions & 0 deletions libs/cart/src/helpers/get-affected-item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { DaffCartItem } from '../models/public_api';

/**
* Finds the IDs of any affected cart items between two carts.
*/
export function daffCartGetAffectedItems<T extends DaffCartItem = DaffCartItem>(oldItems: Array<T>, newItems: Array<T>): Array<T['id']> {
return newItems.reduce((acc, newItem) => {
const oldItem = oldItems.find(({ id }) => id === newItem.id);
if (!oldItem || oldItem.qty !== newItem.qty) {
acc.push(newItem.id);
}
return acc;
}, <Array<T['id']>>[]);
}
21 changes: 21 additions & 0 deletions libs/cart/src/helpers/input-to-item-transform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {
DaffCartItem,
DaffCartItemInput,
} from '../models/public_api';

export const daffCartItemInputToItemTransform = (input: DaffCartItemInput): DaffCartItem => ({
type: input.type,
product_id: input.productId,
qty: input.qty,

item_id: null,
id: null,
parent_item_id: null,
sku: null,
name: null,
price: null,
row_total: null,
in_stock: false,
discounts: [],
url: null,
});
2 changes: 2 additions & 0 deletions libs/cart/src/helpers/public_api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './get-affected-item';
export * from './input-to-item-transform';
1 change: 1 addition & 0 deletions libs/cart/src/public_api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './errors/public_api';
export * from './models/public_api';
export * from './injection-tokens/public_api';
export * from './helpers/public_api';

export { DaffCartStorageService } from './storage/cart-storage.service';
19 changes: 10 additions & 9 deletions libs/cart/state/src/effects/cart-item.effects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
DaffCart,
DaffCartStorageService,
DaffCartItemInputType,
DaffCartItem,
} from '@daffodil/cart';
import {
DaffCartItemServiceInterface,
Expand All @@ -44,7 +45,6 @@ import {
DaffCartItemDelete,
DaffCartItemDeleteSuccess,
DaffCartItemDeleteFailure,
DaffStatefulCartItem,
DaffCartItemStateReset,
DaffCartItemStateDebounceTime,
DaffCartItemDeleteOutOfStock,
Expand All @@ -66,6 +66,7 @@ import {
import {
daffComposeReducers,
daffIdentityReducer,
DaffOperationEntity,
DaffStateError,
} from '@daffodil/core/state';

Expand All @@ -77,7 +78,7 @@ describe('@daffodil/cart/state | DaffCartItemEffects', () => {
let store: Store;

let mockCart: DaffCart;
let mockCartItem: DaffStatefulCartItem;
let mockCartItem: DaffOperationEntity<DaffCartItem>;
let mockCartItemInput: DaffCartItemInput;

let cartFactory: DaffCartFactory;
Expand Down Expand Up @@ -245,7 +246,7 @@ describe('@daffodil/cart/state | DaffCartItemEffects', () => {
beforeEach(() => {
mockCart.items.push(mockCartItem);
driverAddSpy.and.returnValue(of(mockCart));
const cartItemAddSuccessAction = new DaffCartItemAddSuccess(mockCart);
const cartItemAddSuccessAction = new DaffCartItemAddSuccess(mockCart, mockCartItem.id);
actions$ = hot('--a', { a: cartItemAddAction });
expected = cold('--b', { b: cartItemAddSuccessAction });
});
Expand Down Expand Up @@ -356,8 +357,8 @@ describe('@daffodil/cart/state | DaffCartItemEffects', () => {
});
testScheduler.run(helpers => {
const expectedMarble = '4000ms a';
const cartItemAddSuccess = new DaffCartItemAddSuccess(mockCart);
const shopCartItemReset = new DaffCartItemStateReset();
const cartItemAddSuccess = new DaffCartItemAddSuccess(mockCart, mockCartItem.id);
const shopCartItemReset = new DaffCartItemStateReset(mockCartItem.id);
actions$ = helpers.hot('a', { a: cartItemAddSuccess });
expectedObservable = { a: shopCartItemReset };

Expand All @@ -372,10 +373,10 @@ describe('@daffodil/cart/state | DaffCartItemEffects', () => {
});
testScheduler.run(helpers => {
const expectedMarble = '4000ms ----a';
const cartItemAddSuccess = new DaffCartItemAddSuccess(mockCart);
const cartItemAddSuccess = new DaffCartItemAddSuccess(mockCart, mockCartItem.id);
const cartItemUpdateAction = new DaffCartItemUpdate(mockCartItem.id, mockCartItem);
const cartItemUpdateSuccessAction = new DaffCartItemUpdateSuccess(mockCart, mockCartItem.id);
const shopCartItemReset = new DaffCartItemStateReset();
const shopCartItemReset = new DaffCartItemStateReset(mockCartItem.id);
actions$ = helpers.hot('a-b-c', { a: cartItemAddSuccess, b: cartItemUpdateAction, c: cartItemUpdateSuccessAction });
expectedObservable = { a: shopCartItemReset };

Expand All @@ -394,7 +395,7 @@ describe('@daffodil/cart/state | DaffCartItemEffects', () => {
testScheduler.run(helpers => {
const expectedMarble = '4000ms a';
const cartItemUpdateSuccess = new DaffCartItemUpdateSuccess(mockCart, mockCartItem.id);
const shopCartItemReset = new DaffCartItemStateReset();
const shopCartItemReset = new DaffCartItemStateReset(mockCartItem.id);
actions$ = helpers.hot('a', { a: cartItemUpdateSuccess });
expectedObservable = { a: shopCartItemReset };

Expand Down Expand Up @@ -460,7 +461,7 @@ describe('@daffodil/cart/state | DaffCartItemEffects', () => {
let expected;
let cartItemDeleteOutOfStockAction: DaffCartItemDeleteOutOfStock;
let cartItemDeleteOutOfStockSuccessAction: DaffCartItemDeleteOutOfStockSuccess;
let outOfStockCartItems: DaffStatefulCartItem[];
let outOfStockCartItems: DaffOperationEntity<DaffCartItem>[];

beforeEach(() => {
cartItemDeleteOutOfStockAction = new DaffCartItemDeleteOutOfStock();
Expand Down
72 changes: 25 additions & 47 deletions libs/cart/state/src/effects/cart-item.effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,13 @@ import {
createEffect,
ofType,
} from '@ngrx/effects';
import {
select,
Store,
} from '@ngrx/store';
import {
combineLatest,
of,
} from 'rxjs';
import {
switchMap,
map,
catchError,
debounceTime,
mergeMap,
take,
Expand All @@ -29,6 +24,7 @@ import {
DaffCartItemInput,
DaffCart,
DaffCartStorageService,
daffCartGetAffectedItems,
} from '@daffodil/cart';
import {
DaffCartDriverResolveService,
Expand All @@ -41,30 +37,24 @@ import { ErrorTransformer } from '@daffodil/core/state';

import {
DaffCartItemActionTypes,
DaffCartItemLoad,
DaffCartItemLoadSuccess,
DaffCartItemLoadFailure,
DaffCartItemDelete,
DaffCartItemDeleteSuccess,
DaffCartItemDeleteFailure,
DaffCartItemUpdate,
DaffCartItemUpdateSuccess,
DaffCartItemUpdateFailure,
DaffCartItemList,
DaffCartItemListSuccess,
DaffCartItemListFailure,
DaffCartItemAddSuccess,
DaffCartItemAddFailure,
DaffCartItemStateReset,
DaffCartItemDeleteOutOfStock,
DaffCartItemDeleteOutOfStockSuccess,
DaffCartItemDeleteOutOfStockFailure,
DaffCartItemActions,
} from '../actions/public_api';
import { DaffCartFacade } from '../facades/cart/cart.facade';
import { DaffCartItemStateDebounceTime } from '../injection-tokens/cart-item-state-debounce-time';
import { DAFF_CART_ERROR_MATCHER } from '../injection-tokens/public_api';
import { DaffStatefulCartItem } from '../models/public_api';
import { getDaffCartSelectors } from '../selectors/public_api';

@Injectable()
export class DaffCartItemEffects<
Expand All @@ -77,11 +67,10 @@ export class DaffCartItemEffects<
@Inject(DaffCartItemDriver) private driver: DaffCartItemServiceInterface<T, U>,
private storage: DaffCartStorageService,
@Inject(DaffCartItemStateDebounceTime) private cartItemStateDebounceTime: number,
private store: Store,
private cartResolver: DaffCartDriverResolveService,
private cartResolver: DaffCartDriverResolveService<T>,
private cartFacade: DaffCartFacade<T>,
) {}


list$ = createEffect(() => this.actions$.pipe(
ofType(DaffCartItemActionTypes.CartItemListAction),
switchMap((action) =>
Expand All @@ -102,36 +91,34 @@ export class DaffCartItemEffects<
),
));

private addCartItem$(
cartId: DaffCart['id'],
input: U,
) {
return this.driver.add(
cartId,
input,
).pipe(
map((resp) => new DaffCartItemAddSuccess(resp)),
catchAndArrayifyErrors(error => of(new DaffCartItemAddFailure(error.map(this.errorMatcher)))),
);
}

add$ = createEffect(() => this.actions$.pipe(
ofType(DaffCartItemActionTypes.CartItemAddAction),
concatMap((action) => combineLatest([
of(action),
this.cartResolver.getCartIdOrFail(),
])),
mergeMap(([action, id]) =>
this.addCartItem$(
id,
action.input,
combineLatest([
this.driver.add(
id,
action.input,
),
this.cartFacade.cart$.pipe(
take(1),
),
]).pipe(
map(([newCart, oldCart]) => new DaffCartItemAddSuccess(
newCart,
daffCartGetAffectedItems(oldCart.items, newCart.items)[0],
)),
catchAndArrayifyErrors(error => of(new DaffCartItemAddFailure(error.map(this.errorMatcher), action.placeholderId))),
),
),
daffCartDriverHandleCartNotFound(this.storage),
// TODO: figure out how to get placeholder ID here
catchAndArrayifyErrors(error => of(new DaffCartItemAddFailure(error.map(this.errorMatcher)))),
));


update$ = createEffect(() => this.actions$.pipe(
ofType(DaffCartItemActionTypes.CartItemUpdateAction),
mergeMap((action) =>
Expand All @@ -146,21 +133,14 @@ export class DaffCartItemEffects<
),
));


resetCartItemStateAfterChange$ = createEffect(() => this.actions$.pipe(
ofType(
// these actions will reset the debounce interval
DaffCartItemActionTypes.CartItemAddSuccessAction,
DaffCartItemActionTypes.CartItemUpdateSuccessAction,
DaffCartItemActionTypes.CartItemUpdateAction,
),
debounceTime(this.cartItemStateDebounceTime),
ofType(
// these actions will cause the cart item state reset
DaffCartItemActionTypes.CartItemAddSuccessAction,
DaffCartItemActionTypes.CartItemUpdateSuccessAction,
),
map(() => new DaffCartItemStateReset()),
debounceTime(this.cartItemStateDebounceTime),
map((action) => new DaffCartItemStateReset(action.itemId)),
));

delete$ = createEffect(() => this.actions$.pipe(
Expand All @@ -175,20 +155,18 @@ export class DaffCartItemEffects<

removeOutOfStock$ = createEffect(() => this.actions$.pipe(
ofType(DaffCartItemActionTypes.CartItemDeleteOutOfStockAction),
switchMap((action: DaffCartItemDeleteOutOfStock) => this.store.pipe(
select(getDaffCartSelectors().selectOutOfStockCartItems),
switchMap((action) => this.cartFacade.outOfStockItems$.pipe(
take(1),
)),
switchMap(items => items.length > 0
? combineLatest(items.map(item => this.driver.delete(this.storage.getCartId(), item.id))).pipe(
map(partialCarts => Object.assign({}, ...partialCarts)),
map(partialCarts => <T>Object.assign({}, ...partialCarts)),
)
: this.store.pipe(
select(getDaffCartSelectors().selectCartValue),
: this.cartFacade.cart$.pipe(
take(1),
),
),
map(cart => new DaffCartItemDeleteOutOfStockSuccess(cart)),
map((cart) => new DaffCartItemDeleteOutOfStockSuccess(cart)),
catchAndArrayifyErrors(error => of(new DaffCartItemDeleteOutOfStockFailure(error.map(this.errorMatcher)))),
));
}
Loading

0 comments on commit 6f9ad3c

Please sign in to comment.