Skip to content

Commit

Permalink
fix: do not override product information in the state with informatio…
Browse files Browse the repository at this point in the history
…n with lower completeness level (#1256)

* overriding product detail response information with product information from a products list call removed product images, description etc.
* now only the availability information from product information with a lower completeness level is merged into the existing product information
  • Loading branch information
shauke committed Aug 26, 2022
1 parent 4fdfa5e commit 81b2220
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 29 deletions.
103 changes: 103 additions & 0 deletions src/app/core/models/product/product.helper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,4 +379,107 @@ describe('Product Helper', () => {
});
});
});

describe('updateProductInformation()', () => {
let detailProduct: Product;
let listProduct: Product;
let stubProduct: Product;
let stubProduct2: Product;

beforeEach(() => {
detailProduct = {
sku: '110',
completenessLevel: ProductCompletenessLevel.Detail,
name: 'Detail Product',
manufacturer: 'Detail Manufacturer',
shortDescription: 'The best product',
available: true,
} as Product;
listProduct = {
sku: '110',
completenessLevel: ProductCompletenessLevel.List,
name: 'List Product',
manufacturer: 'List Manufacturer',
available: false,
} as Product;
stubProduct = {
sku: '110',
completenessLevel: 0,
name: 'Stub Product',
available: false,
} as Product;
stubProduct2 = {
sku: '110',
completenessLevel: 0,
name: 'Stub Product 2',
available: true,
} as Product;
});

it('should return current product information if no new product information is provided', () => {
expect(ProductHelper.updateProductInformation(detailProduct, undefined)).toMatchInlineSnapshot(`
Object {
"available": true,
"completenessLevel": 3,
"manufacturer": "Detail Manufacturer",
"name": "Detail Product",
"shortDescription": "The best product",
"sku": "110",
}
`);
});

it('should return new product information if no product information exists', () => {
expect(ProductHelper.updateProductInformation(undefined, stubProduct)).toMatchInlineSnapshot(`
Object {
"available": false,
"completenessLevel": 0,
"name": "Stub Product",
"sku": "110",
}
`);
});

it('should return new product information if completeness level ist higher', () => {
expect(ProductHelper.updateProductInformation(listProduct, detailProduct)).toMatchInlineSnapshot(`
Object {
"available": true,
"completenessLevel": 3,
"manufacturer": "Detail Manufacturer",
"name": "Detail Product",
"shortDescription": "The best product",
"sku": "110",
}
`);
});

it('should return new product information if completeness level ist equal', () => {
expect(ProductHelper.updateProductInformation(stubProduct, stubProduct2)).toMatchInlineSnapshot(`
Object {
"available": true,
"completenessLevel": 0,
"name": "Stub Product 2",
"sku": "110",
}
`);
});

it('should return updated current product information if completeness level ist lower', () => {
expect(ProductHelper.updateProductInformation(detailProduct, stubProduct)).toMatchInlineSnapshot(`
Object {
"available": false,
"availableStock": undefined,
"completenessLevel": 3,
"manufacturer": "Detail Manufacturer",
"name": "Detail Product",
"shortDescription": "The best product",
"sku": "110",
}
`);
});

it('should return undefined if no current or new product information is provided', () => {
expect(ProductHelper.updateProductInformation(undefined, undefined)).toMatchInlineSnapshot(`undefined`);
});
});
});
32 changes: 32 additions & 0 deletions src/app/core/models/product/product.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,36 @@ export class ProductHelper {
const attributes = product.attributes?.filter(att => !common.includes(att.name));
return { ...product, attributes };
}

/**
* Updates current product information with new product information considering completeness levels and dynamic product information
*
* @param currentProduct The already available product information
* @param newProduct The new product information to update the existing information with
* @returns The updated product information
*/
static updateProductInformation(
currentProduct: Partial<AllProductTypes>,
newProduct: Partial<AllProductTypes>
): AllProductTypes {
// if there is no new product information return the current product information
if (!newProduct) {
return currentProduct as AllProductTypes;
}
// set the current product information as base or construct an empty product stub
let product = currentProduct || { completenessLevel: 0 };
// update the complete product information if the newProduct information
// has a higher or equal completeness level than the product information
if (!product.completenessLevel || newProduct.completenessLevel >= product.completenessLevel) {
return newProduct as AllProductTypes;
}
// always update dynamic product information with the new product information (e.g. availability)
product = {
...product,
// list of product properties that should be updated
available: newProduct.available ?? product.available,
availableStock: newProduct.availableStock ?? product.availableStock,
};
return product as AllProductTypes;
}
}
11 changes: 2 additions & 9 deletions src/app/core/store/shopping/products/products.reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { EntityState, createEntityAdapter } from '@ngrx/entity';
import { createReducer, on } from '@ngrx/store';

import { ProductLinksDictionary } from 'ish-core/models/product-links/product-links.model';
import { AllProductTypes, SkuQuantityType } from 'ish-core/models/product/product.model';
import { AllProductTypes, ProductHelper, SkuQuantityType } from 'ish-core/models/product/product.model';

import {
loadProductFail,
Expand Down Expand Up @@ -50,14 +50,7 @@ export const productsReducer = createReducer(
})),
on(loadProductSuccess, (state, action) => {
const product = action.payload.product;
const oldProduct = state.entities[product.sku] || { completenessLevel: 0 };

const newProduct = { ...product };
if (product.completenessLevel || oldProduct?.completenessLevel) {
newProduct.completenessLevel = Math.max(product.completenessLevel, oldProduct.completenessLevel);
}

return productAdapter.upsertOne(newProduct as AllProductTypes, {
return productAdapter.upsertOne(ProductHelper.updateProductInformation(state.entities[product.sku], product), {
...state,
failed: removeFailed(state.failed, product.sku),
});
Expand Down
49 changes: 29 additions & 20 deletions src/app/core/store/shopping/products/products.selectors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,26 +76,35 @@ describe('Products Selectors', () => {
expect(getProductEntities(store$.state)).toEqual({ [prod.sku]: prod });
});

it.each([
[ProductCompletenessLevel.Detail, ProductCompletenessLevel.Detail - 1],
[ProductCompletenessLevel.Detail + 1, ProductCompletenessLevel.Detail + 1],
])(
'should merge product updates with "%s" completeness when new info with "%s" completeness is available',
(resultCompletenessLevel, completenessLevel) => {
const newProd = {
...prod,
completenessLevel,
manufacturer: 'Microsoft',
name: 'Updated product',
available: false,
} as Product;
store$.dispatch(loadProductSuccess({ product: newProd }));

expect(getProductEntities(store$.state)).toEqual({
[prod.sku]: { ...newProd, completenessLevel: resultCompletenessLevel },
});
}
);
it('should update product information with the same completeness level', () => {
const newProd = {
...prod,
completenessLevel: ProductCompletenessLevel.Detail,
manufacturer: 'Microsoft',
name: 'Updated product',
available: false,
} as Product;
store$.dispatch(loadProductSuccess({ product: newProd }));

expect(getProductEntities(store$.state)).toEqual({
[prod.sku]: { ...newProd },
});
});

it('should only partially update product information with a lower completeness level', () => {
const newProd = {
...prod,
completenessLevel: ProductCompletenessLevel.List,
manufacturer: 'Microsoft',
name: 'Updated product',
available: false,
} as Product;
store$.dispatch(loadProductSuccess({ product: newProd }));

expect(getProductEntities(store$.state)).toEqual({
[prod.sku]: { ...prod, available: false, availableStock: undefined },
});
});
});

describe('and reporting failure', () => {
Expand Down

0 comments on commit 81b2220

Please sign in to comment.