Skip to content

Commit 8dfd620

Browse files
authored
[CI-3307] useProductInfo hook tests (#72)
* useProductInfo hook tests * address comemnts * cspell addition
1 parent d1ed75b commit 8dfd620

File tree

7 files changed

+145
-8
lines changed

7 files changed

+145
-8
lines changed

cspell.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"Favorited",
2020
"favorited",
2121
"dtype",
22-
"cnstrc"
22+
"cnstrc",
23+
"Catchify",
2324
]
2425
}

spec/ProductCard.server.test.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ describe('ProductCard: React Server-Side Rendering', () => {
3636
<ProductCard item={transformResultItem(testItem)} />
3737
</CioPlp>,
3838
);
39-
expect(html).toContain('$79.00');
39+
expect(html).toContain('$90.00');
4040
});
4141

4242
test('Should render custom price formatting if overridden at the PlpContext level', () => {
@@ -46,7 +46,7 @@ describe('ProductCard: React Server-Side Rendering', () => {
4646
<ProductCard item={transformResultItem(testItem)} />
4747
</CioPlp>,
4848
);
49-
expect(html).toContain('USD$79.00');
49+
expect(html).toContain('USD$90.00');
5050
});
5151

5252
test('Should retrieve custom price if overridden at the PlpContext level', () => {
@@ -72,7 +72,7 @@ describe('ProductCard: React Server-Side Rendering', () => {
7272
);
7373

7474
// React injects <!-- --> on the server to mark dynamic content for rehydration
75-
expect(html).toContain('My Rendered Price: <!-- -->$79.00');
75+
expect(html).toContain('My Rendered Price: <!-- -->$90.00');
7676
});
7777

7878
test('Should throw error for invalid item format', () => {

spec/local_examples/item.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"data": {
66
"id": "KNITS00423-park bench dot",
77
"url": "https://constructorio-integrations.s3.amazonaws.com/tikus-threads/2022-06-29/KNIT-CASUAL-SHIRT_BUTTON-DOWN-KNIT-SHIRT_BKT00110SG1733_3_category.jpg",
8-
"price": 79,
8+
"price": 90,
99
"altPrice": 69,
1010
"sale_price": 23,
1111
"image_url": "https://constructorio-integrations.s3.amazonaws.com/tikus-threads/2022-06-29/KNIT-CASUAL-SHIRT_BUTTON-DOWN-KNIT-SHIRT_BKT00110SG1733_3_category.jpg",
@@ -23,7 +23,7 @@
2323
],
2424
"variation_id": "BKT00110DG1733LR"
2525
},
26-
"value": "Jersey Riviera Shirt (Park Bench Dot)",
26+
"value": "Jersey Riviera Shirt (Red Park Bench Dot)",
2727
"is_slotted": false,
2828
"labels": {},
2929
"variations": [

spec/useProductInfo.test.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import '@testing-library/jest-dom';
2+
import { renderHook, waitFor } from '@testing-library/react';
3+
import useProductInfo from '../src/hooks/useProduct';
4+
import { transformResultItem } from '../src/utils/transformers';
5+
import mockItem from './local_examples/item.json';
6+
import { renderHookWithCioPlp } from './test-utils';
7+
8+
describe('Testing Hook: useProductInfo', () => {
9+
beforeEach(() => {
10+
// Mock console error to de-clutter the console for expected errors
11+
const spy = jest.spyOn(console, 'error');
12+
spy.mockImplementation(() => {});
13+
});
14+
15+
afterEach(() => {
16+
jest.restoreAllMocks(); // This will reset all mocks after each test
17+
});
18+
19+
it('Should throw error if called outside of PlpContext', () => {
20+
expect(() => renderHook(() => useProductInfo())).toThrow();
21+
});
22+
23+
const transformedItem = transformResultItem(mockItem);
24+
25+
it('Should return productSwatch, itemName, itemImageUrl, itemUrl, itemPrice', async () => {
26+
const { result } = renderHookWithCioPlp(() => useProductInfo({ item: transformedItem }));
27+
28+
await waitFor(() => {
29+
const {
30+
current: { productSwatch, itemName, itemImageUrl, itemUrl, itemPrice },
31+
} = result;
32+
33+
expect(productSwatch).not.toBeNull();
34+
expect(itemName).toEqual(transformedItem.itemName);
35+
expect(itemImageUrl).toEqual(transformedItem.imageUrl);
36+
expect(itemUrl).toEqual(transformedItem.url);
37+
expect(itemPrice).toEqual(transformedItem.data.price);
38+
});
39+
});
40+
41+
it('Should return nothing properly with getters that return nothing', async () => {
42+
const { result } = renderHookWithCioPlp(() => useProductInfo({ item: transformedItem }), {
43+
initialProps: {
44+
itemFieldGetters: {
45+
getPrice: () => {},
46+
getSwatches: () => {},
47+
getSwatchPreview: () => {},
48+
},
49+
},
50+
});
51+
52+
await waitFor(() => {
53+
const {
54+
current: { productSwatch, itemName, itemImageUrl, itemUrl, itemPrice },
55+
} = result;
56+
57+
expect(productSwatch).not.toBeNull();
58+
expect(itemName).toEqual(transformedItem.itemName);
59+
expect(itemImageUrl).toEqual(transformedItem.imageUrl);
60+
expect(itemUrl).toEqual(transformedItem.url);
61+
expect(itemPrice).toBeUndefined();
62+
});
63+
});
64+
65+
it('Should return correctly after different variation is selected', async () => {
66+
const { result } = renderHookWithCioPlp(() => useProductInfo({ item: transformedItem }));
67+
68+
await waitFor(() => {
69+
const {
70+
current: { productSwatch, itemName, itemImageUrl, itemUrl, itemPrice },
71+
} = result;
72+
const { selectVariation, swatchList } = productSwatch;
73+
selectVariation(swatchList[1]);
74+
75+
expect(itemName).toEqual(swatchList[1].itemName);
76+
expect(itemImageUrl).toEqual(swatchList[1].imageUrl || transformedItem.imageUrl);
77+
expect(itemUrl).toEqual(swatchList[1].url || transformedItem.url);
78+
expect(itemPrice).toEqual(swatchList[1].price || transformedItem.data.price);
79+
});
80+
});
81+
82+
it('Should return nothing properly with getters that throw errors', async () => {
83+
const { result } = renderHookWithCioPlp(() => useProductInfo({ item: transformedItem }), {
84+
initialProps: {
85+
itemFieldGetters: {
86+
getPrice: () => {
87+
throw new Error();
88+
},
89+
getSwatches: () => {
90+
throw new Error();
91+
},
92+
getSwatchPreview: () => {
93+
throw new Error();
94+
},
95+
},
96+
},
97+
});
98+
99+
await waitFor(() => {
100+
const {
101+
current: { productSwatch, itemName, itemImageUrl, itemUrl, itemPrice },
102+
} = result;
103+
104+
expect(productSwatch).not.toBeNull();
105+
expect(itemName).toEqual(transformedItem.itemName);
106+
expect(itemImageUrl).toEqual(transformedItem.imageUrl);
107+
expect(itemUrl).toEqual(transformedItem.url);
108+
expect(itemPrice).toBeUndefined();
109+
});
110+
});
111+
});

src/hooks/useProduct.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import useProductSwatch from '../components/ProductSwatch/useProductSwatch';
22
import { useCioPlpContext } from './useCioPlpContext';
33
import { UseProductInfo } from '../types';
4+
import { tryCatchify } from '../utils';
45

56
const useProductInfo: UseProductInfo = ({ item }) => {
67
const state = useCioPlpContext();
@@ -10,7 +11,7 @@ const useProductInfo: UseProductInfo = ({ item }) => {
1011
throw new Error('data, itemId, or itemName are required.');
1112
}
1213

13-
const getPrice = state?.itemFieldGetters?.getPrice;
14+
const getPrice = tryCatchify(state?.itemFieldGetters?.getPrice);
1415

1516
const itemName = productSwatch?.selectedVariation?.itemName || item.itemName;
1617
const itemPrice = productSwatch?.selectedVariation?.price || getPrice(item);

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ export type UseProductSwatch = (props: UseProductSwatchProps) => ProductSwatchOb
266266
export interface ProductInfoObject {
267267
productSwatch: ProductSwatchObject | undefined;
268268
itemName: string;
269-
itemPrice: number;
269+
itemPrice: number | undefined;
270270
itemUrl: string | undefined;
271271
itemImageUrl: string | undefined;
272272
}

src/utils.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,30 @@ export function sleep(ms) {
1313
return new Promise((resolve) => setTimeout(resolve, ms));
1414
}
1515

16+
/* istanbul ignore next */
17+
export const logger = (error: any) => {
18+
try {
19+
if (typeof process !== 'undefined' && process?.env?.LOGGER) {
20+
// eslint-disable-next-line no-console
21+
console.log(error);
22+
}
23+
} catch (e) {
24+
// process variable is not available and logger should not be active
25+
}
26+
};
27+
28+
// eslint-disable-next-line @cspell/spellchecker
29+
export function tryCatchify(func: Function) {
30+
return (...args: any) => {
31+
try {
32+
return func(...args);
33+
} catch (e) {
34+
logger(e);
35+
}
36+
return undefined;
37+
};
38+
}
39+
1640
export function removeNullValuesFromObject(obj: Object) {
1741
const filteredListOfEntries = Object.entries(obj).filter(([, val]) => val != null);
1842

0 commit comments

Comments
 (0)