Skip to content
2 changes: 1 addition & 1 deletion spec/components/CioPlp/CioPlp.server.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe('CioPlp React Server-Side Rendering', () => {
</CioPlp>,
);
expect(html).toContain(
'<div class=\"cio-plp\"><div>{&quot;cioClient&quot;:null,&quot;cioClientOptions&quot;:{},&quot;staticRequestConfigs&quot;:{},&quot;itemFieldGetters&quot;:{},&quot;formatters&quot;:{},&quot;callbacks&quot;:{},&quot;urlHelpers&quot;:{&quot;defaultQueryStringMap&quot;:{&quot;query&quot;:&quot;q&quot;,&quot;page&quot;:&quot;page&quot;,&quot;offset&quot;:&quot;offset&quot;,&quot;resultsPerPage&quot;:&quot;numResults&quot;,&quot;filters&quot;:&quot;filters&quot;,&quot;sortBy&quot;:&quot;sortBy&quot;,&quot;sortOrder&quot;:&quot;sortOrder&quot;,&quot;section&quot;:&quot;section&quot;}}}</div></div>',
'<div class=\"cio-plp\"><div>{&quot;cioClient&quot;:null,&quot;cioClientOptions&quot;:{},&quot;staticRequestConfigs&quot;:{},&quot;customConfigs&quot;:{},&quot;itemFieldGetters&quot;:{},&quot;formatters&quot;:{},&quot;callbacks&quot;:{},&quot;urlHelpers&quot;:{&quot;defaultQueryStringMap&quot;:{&quot;query&quot;:&quot;q&quot;,&quot;page&quot;:&quot;page&quot;,&quot;offset&quot;:&quot;offset&quot;,&quot;resultsPerPage&quot;:&quot;numResults&quot;,&quot;filters&quot;:&quot;filters&quot;,&quot;sortBy&quot;:&quot;sortBy&quot;,&quot;sortOrder&quot;:&quot;sortOrder&quot;,&quot;section&quot;:&quot;section&quot;}}}</div></div>',
);
});
});
80 changes: 61 additions & 19 deletions spec/hooks/useProductInfo/useProductInfo.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,14 @@ describe('Testing Hook: useProductInfo', () => {

const transformedItem = transformResultItem(mockItem);

it('Should return productSwatch, itemId, itemName, itemImageUrl, itemUrl, itemPrice', async () => {
it('Should return itemId, itemName, itemImageUrl, itemUrl, itemPrice', async () => {
const { result } = renderHookWithCioPlp(() => useProductInfo({ item: transformedItem }));

await waitFor(() => {
const {
current: { productSwatch, itemName, itemImageUrl, itemUrl, itemPrice, itemId },
current: { itemName, itemImageUrl, itemUrl, itemPrice, itemId },
} = result;

expect(productSwatch).not.toBeNull();
expect(itemId).toEqual(transformedItem.itemId);
expect(itemName).toEqual(transformedItem.itemName);
expect(itemImageUrl).toEqual(transformedItem.imageUrl);
Expand All @@ -46,37 +45,67 @@ describe('Testing Hook: useProductInfo', () => {
getPrice: () => {},
getSwatches: () => {},
getSwatchPreview: () => {},
getName: () => {},
getItemUrl: () => {},
getImageUrl: () => {},
},
},
});

await waitFor(() => {
const {
current: { productSwatch, itemName, itemImageUrl, itemUrl, itemPrice },
current: { itemName, itemImageUrl, itemUrl, itemPrice },
} = result;

expect(productSwatch).not.toBeNull();
expect(itemName).toEqual(transformedItem.itemName);
expect(itemImageUrl).toEqual(transformedItem.imageUrl);
expect(itemUrl).toEqual(transformedItem.url);
expect(itemName).toBeUndefined();
expect(itemImageUrl).toBeUndefined();
expect(itemUrl).toBeUndefined();
expect(itemPrice).toBeUndefined();
});
});

it('Should return correctly after different variation is selected', async () => {
const { result } = renderHookWithCioPlp(() => useProductInfo({ item: transformedItem }));
it('Should return properly with getters that override defaults', async () => {
const { result } = renderHookWithCioPlp(() => useProductInfo({ item: transformedItem }), {
initialProps: {
itemFieldGetters: {
getPrice: () => 'override',
getSwatches: () => [],
getSwatchPreview: () => 'override',
getName: () => 'override',
getItemUrl: () => 'override',
getImageUrl: () => 'override',
},
},
});

await waitFor(() => {
const {
current: { itemName, itemImageUrl, itemUrl, itemPrice },
} = result;

expect(itemName).toEqual('override');
expect(itemUrl).toEqual('override');
expect(itemImageUrl).toEqual('override');
expect(itemPrice).toEqual('override');
});
});

it('Should return image properly with overridden baseUrl', async () => {
const { result } = renderHookWithCioPlp(() => useProductInfo({ item: transformedItem }), {
initialProps: {
customConfigs: { imageBaseUrl: 'test.com' },
},
});

await waitFor(() => {
const {
current: { productSwatch, itemName, itemImageUrl, itemUrl, itemPrice },
current: { itemName, itemImageUrl, itemUrl, itemPrice },
} = result;
const { selectVariation, swatchList } = productSwatch;
selectVariation(swatchList[1]);

expect(itemName).toEqual(swatchList[1].itemName);
expect(itemImageUrl).toEqual(swatchList[1].imageUrl || transformedItem.imageUrl);
expect(itemUrl).toEqual(swatchList[1].url || transformedItem.url);
expect(itemPrice).toEqual(swatchList[1].price || transformedItem.data.price);
expect(itemName).toEqual(transformedItem.itemName);
expect(itemImageUrl).toEqual(`test.com${transformedItem.imageUrl}`);
expect(itemUrl).toEqual(transformedItem.url);
expect(itemPrice).toEqual(transformedItem.data.price);
});
});

Expand All @@ -99,14 +128,27 @@ describe('Testing Hook: useProductInfo', () => {

await waitFor(() => {
const {
current: { productSwatch, itemName, itemImageUrl, itemUrl, itemPrice },
current: { itemName, itemImageUrl, itemUrl, itemPrice },
} = result;

expect(productSwatch).not.toBeNull();
expect(itemName).toEqual(transformedItem.itemName);
expect(itemImageUrl).toEqual(transformedItem.imageUrl);
expect(itemUrl).toEqual(transformedItem.url);
expect(itemPrice).toBeUndefined();
});
});

it('should merge product info fields with selectedVariation when provided', async () => {
const transformedItem = transformResultItem(mockItem);
const { result } = renderHookWithCioPlp(() => useProductInfo({ item: transformedItem, selectedVariation: transformedItem.variations[0] }));

console.log(transformedItem.variations[0])
await waitFor(() => {
const { current: { itemName, itemPrice, itemImageUrl, itemUrl } } = result;
expect(itemName).toEqual(transformedItem.variations[0].itemName);
expect(itemPrice).toEqual(transformedItem.variations[0].data.price);
expect(itemImageUrl).toEqual(transformedItem.variations[0].imageUrl);
expect(itemUrl).toEqual(transformedItem.variations[0].url);
});
});
});
6 changes: 3 additions & 3 deletions spec/hooks/useProductSwatch/useProductSwatch.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('Testing Hook: useProductSwatch', () => {
});

const transformedItem = transformResultItem(mockItem);
const expectedSwatch = getSwatches(transformedItem, getPrice, getSwatchPreview);
const expectedSwatch = getSwatches(transformedItem, getSwatchPreview);

it('Should throw error if called outside of PlpContext', () => {
expect(() => renderHook(() => useProductSwatch())).toThrow();
Expand Down Expand Up @@ -73,7 +73,7 @@ describe('Testing Hook: useProductSwatch', () => {
} = result;

expect(typeof selectVariation).toBe('function');
expect(selectedVariation).toBeUndefined();
expect(selectedVariation).toBe(transformedItem.variations[0]);
expect(swatchList.length).toBe(0);
});
});
Expand Down Expand Up @@ -101,7 +101,7 @@ describe('Testing Hook: useProductSwatch', () => {
} = result;

expect(typeof selectVariation).toBe('function');
expect(selectedVariation).toBeUndefined();
expect(selectedVariation).toBe(transformedItem.variations[0]);
expect(swatchList.length).toBe(0);
});
});
Expand Down
2 changes: 2 additions & 0 deletions spec/local_examples/item.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
"variation_id": "BKT00110DG1733LR",
"swatchPreview": "#e04062",
"price": 90,
"image_url": "https://constructorio-integrations.s3.amazonaws.com/tikus-threads/2022-06-29/Casual-Shirts_Washed-Poplin-Shirts_19145-MTX64_40_category-outfitter.jpg",
"url": "https://constructorio-integrations.s3.amazonaws.com/tikus-threads/2022-06-29/Casual-Shirts_Washed-Poplin-Shirts_19145-MTX64_40_category-outfitter.jpg",
"facets": [
{
"name": "Color",
Expand Down
2 changes: 1 addition & 1 deletion spec/utils.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const transformedItem = transformResultItem(mockItem);

describe('Testing Utils, getProductCardCnstrcDataAttributes', () => {
test('Should return relevant data attributes for Product Card', async () => {
const { result } = renderHookWithCioPlp(() => useProductInfo({ item: transformedItem }));
const { result } = renderHookWithCioPlp(() => useProductInfo({ item: transformedItem, selectedVariation: { variationId: 'BKT00110DG1733LR', swatchPreview: '#FFFFFF' }}));

await waitFor(() => {
const dataAttributes = getProductCardCnstrcDataAttributes(result.current);
Expand Down
7 changes: 5 additions & 2 deletions src/components/ProductCard/ProductCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { CnstrcData, IncludeRenderProps, Item, ProductInfoObject } from '../../types';
import ProductSwatch from '../ProductSwatch';
import useProductInfo from '../../hooks/useProduct';
import useProductSwatch from '../../hooks/useProductSwatch';
import { getProductCardCnstrcDataAttributes } from '../../utils';

interface Props {
Expand Down Expand Up @@ -55,8 +56,10 @@
export default function ProductCard(props: ProductCardProps) {
const { item, children } = props;
const state = useCioPlpContext();
const productInfo = useProductInfo({ item });
const { productSwatch, itemName, itemPrice, itemImageUrl, itemUrl } = productInfo;
const productSwatch = useProductSwatch({ item });
const { selectedVariation } = productSwatch;
const productInfo = useProductInfo({ item, selectedVariation });

Check failure on line 61 in src/components/ProductCard/ProductCard.tsx

View workflow job for this annotation

GitHub Actions / check-types

Type 'Variation | undefined' is not assignable to type 'SwatchItem | undefined'.
const { itemName, itemPrice, itemImageUrl, itemUrl } = productInfo;

if (!state) {
throw new Error('This component is meant to be used within the CioPlp provider.');
Expand Down
3 changes: 3 additions & 0 deletions src/hooks/useCioPlpProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default function useCioPlpProvider(
itemFieldGetters,
urlHelpers,
staticRequestConfigs = {},
customConfigs = {},
cioClient: customCioClient,
cioClientOptions: customCioClientOptions = {},
} = props;
Expand All @@ -28,6 +29,7 @@ export default function useCioPlpProvider(
cioClientOptions,
setCioClientOptions,
staticRequestConfigs,
customConfigs,
itemFieldGetters: { ...defaultGetters, ...itemFieldGetters },
formatters: { ...defaultFormatters, ...formatters },
callbacks: { ...callbacks },
Expand All @@ -41,6 +43,7 @@ export default function useCioPlpProvider(
callbacks,
urlHelpers,
staticRequestConfigs,
customConfigs,
],
);

Expand Down
28 changes: 17 additions & 11 deletions src/hooks/useProduct.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,38 @@
import useProductSwatch from './useProductSwatch';
import { useCioPlpContext } from './useCioPlpContext';
import { UseProductInfo } from '../types';
import { tryCatchify } from '../utils';
import { Item, SwatchItem } from '../types';

const useProductInfo: UseProductInfo = ({ item }) => {
interface UseProductInfoArgs {
item: Item;
selectedVariation?: SwatchItem;
}

const useProductInfo = ({ item, selectedVariation }: UseProductInfoArgs) => {
const state = useCioPlpContext();
const productSwatch = useProductSwatch({ item });

if (!item.data || !item.itemId || !item.itemName) {
throw new Error('data, itemId, or itemName are required.');
}

const getPrice = tryCatchify(state?.itemFieldGetters?.getPrice);
const getImageUrl = tryCatchify(state?.itemFieldGetters?.getImageUrl);
const getItemUrl = tryCatchify(state?.itemFieldGetters?.getItemUrl);
const getName = tryCatchify(state?.itemFieldGetters?.getName);

const itemName = productSwatch?.selectedVariation?.itemName || item.itemName;
const itemPrice = productSwatch?.selectedVariation?.price || getPrice(item);
const itemImageUrl = productSwatch?.selectedVariation?.imageUrl || item.imageUrl;
const itemUrl = productSwatch?.selectedVariation?.url || item.url;
const variationId = productSwatch?.selectedVariation?.variationId;
const itemName = getName(item, selectedVariation);
const itemPrice = getPrice(item, selectedVariation);
const itemImageUrl = getImageUrl(item, selectedVariation, {
imageBaseUrl: state.customConfigs.imageBaseUrl,
});
const itemUrl = getItemUrl(item, selectedVariation);
const { itemId } = item;

return {
productSwatch,
itemName,
itemPrice,
itemImageUrl,
itemUrl,
variationId,
variationId: selectedVariation?.variationId,
itemId,
};
};
Expand Down
21 changes: 10 additions & 11 deletions src/hooks/useProductSwatch.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,42 @@
import { useEffect, useState } from 'react';
import { useCioPlpContext } from './useCioPlpContext';
import { SwatchItem, UseProductSwatch } from '../types';
import { SwatchItem, UseProductSwatch, Variation } from '../types';
import {
getSwatches as defaultGetSwatches,
getPrice as defaultGetPrice,
getSwatchPreview as defaultGetSwatchPreview,
} from '../utils/itemFieldGetters';

const useProductSwatch: UseProductSwatch = ({ item }) => {
const [selectedVariation, setSelectedVariation] = useState<SwatchItem>();
const [selectedVariation, setSelectedVariation] = useState<Variation>();
const [swatchList, setSwatchList] = useState<SwatchItem[]>([]);

const state = useCioPlpContext();

const getSwatches = state?.itemFieldGetters?.getSwatches || defaultGetSwatches;
const getPrice = state?.itemFieldGetters?.getPrice || defaultGetPrice;
const getSwatchPreview = state?.itemFieldGetters?.getSwatchPreview || defaultGetSwatchPreview;

useEffect(() => {
if (item?.variations) {
try {
setSwatchList(getSwatches(item, getPrice, getSwatchPreview) || []);
const swatches = getSwatches(item, getSwatchPreview);
setSwatchList(swatches || []);
} catch (e) {
// do nothing
}
}
}, [item, getSwatches, getPrice, getSwatchPreview]);
}, [item, getSwatches, getSwatchPreview]);

useEffect(() => {
if (item?.variations) {
const initialSwatch = swatchList?.find((swatch) => swatch?.variationId === item?.variationId);
if (initialSwatch) {
setSelectedVariation(initialSwatch);
const initialVariation = item?.variations?.[0];
if (initialVariation) {
setSelectedVariation(initialVariation);
}
}
}, [swatchList, item]);

const selectVariation = (swatch: SwatchItem) => {
setSelectedVariation(swatch);
const selectVariation = (variation: Variation) => {
setSelectedVariation(variation);
};

return {
Expand Down
9 changes: 6 additions & 3 deletions src/stories/components/CioPlp/CioPlpProps.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ Callbacks will be composed with the library's internal tracking calls for a give

ItemFieldGetters maps the fields sent in the catalog feeds to the fields the libary expects for rendering

| property | type | description |
| -------- | ------------------------ | ------------------ |
| getPrice | `(item: Item) => number` | Get price funciton |
| property | type | description |
| ----------- | -----------------------------------------------| ---------------------- |
| getPrice | `(item: Item, variation: Variation) => number` | Get price funciton |
| getImageUrl | `(item: Item, variation: Variation) => string` | Get image url funciton |
| getItemUrl | `(item: Item, variation: Variation) => stirng` | Get href url funciton |
| getName | `(item: Item, variation: Variation) => string` | Get item name funciton |

<br>

Expand Down
Loading
Loading