Skip to content

Commit eb2d91a

Browse files
authored
[CI-4327] Add cnstrc data attributes to Product Card (#146)
* add cnstrc data attributes to Product Card * update type * add itemId to productInfo * add simple test and fix results * update types * remove unusued * update test name
1 parent 3f47d9e commit eb2d91a

File tree

8 files changed

+115
-53
lines changed

8 files changed

+115
-53
lines changed

spec/hooks/useProductInfo/useProductInfo.test.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,16 @@ describe('Testing Hook: useProductInfo', () => {
2222

2323
const transformedItem = transformResultItem(mockItem);
2424

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

2828
await waitFor(() => {
2929
const {
30-
current: { productSwatch, itemName, itemImageUrl, itemUrl, itemPrice },
30+
current: { productSwatch, itemName, itemImageUrl, itemUrl, itemPrice, itemId },
3131
} = result;
3232

3333
expect(productSwatch).not.toBeNull();
34+
expect(itemId).toEqual(transformedItem.itemId);
3435
expect(itemName).toEqual(transformedItem.itemName);
3536
expect(itemImageUrl).toEqual(transformedItem.imageUrl);
3637
expect(itemUrl).toEqual(transformedItem.url);

spec/utils.test.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { waitFor } from '@testing-library/react';
2+
import { transformResultItem } from '../src/utils/transformers';
3+
import { getProductCardCnstrcDataAttributes } from '../src/utils';
4+
import useProductInfo from '../src/hooks/useProduct';
5+
import mockItem from './local_examples/item.json';
6+
import { renderHookWithCioPlp } from './test-utils';
7+
8+
const transformedItem = transformResultItem(mockItem);
9+
10+
describe('Testing Utils, getProductCardCnstrcDataAttributes', () => {
11+
test('Should return relevant data attributes for Product Card', async () => {
12+
const { result } = renderHookWithCioPlp(() => useProductInfo({ item: transformedItem }));
13+
14+
await waitFor(() => {
15+
const dataAttributes = getProductCardCnstrcDataAttributes(result.current);
16+
17+
expect(dataAttributes['data-cnstrc-item-id']).toBe('KNITS00423-park bench dot');
18+
expect(dataAttributes['data-cnstrc-item-name']).toBe(
19+
'Jersey Riviera Shirt (Red Park Bench Dot)',
20+
);
21+
expect(dataAttributes['data-cnstrc-item-price']).toBe(90);
22+
expect(dataAttributes['data-cnstrc-item-variation-id']).toBe('BKT00110DG1733LR');
23+
});
24+
});
25+
});

src/components/ProductCard/ProductCard.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import React from 'react';
22
import { useCioPlpContext } from '../../hooks/useCioPlpContext';
33
import { useOnAddToCart, useOnProductCardClick } from '../../hooks/callbacks';
4-
import { IncludeRenderProps, Item, ProductInfoObject } from '../../types';
4+
import { CnstrcData, IncludeRenderProps, Item, ProductInfoObject } from '../../types';
55
import ProductSwatch from '../ProductSwatch';
66
import useProductInfo from '../../hooks/useProduct';
7+
import { getProductCardCnstrcDataAttributes } from '../../utils';
78

89
interface Props {
910
/**
@@ -40,6 +41,10 @@ export interface ProductCardRenderProps extends ProductCardProps {
4041
* Set globally at the CioPlp provider level.
4142
*/
4243
onClick: (event: React.MouseEvent, item: Item) => void;
44+
/**
45+
* Data Attributes to surface on parent div of product card.
46+
*/
47+
productCardCnstrcDataAttributes: CnstrcData;
4348
}
4449

4550
export type ProductCardProps = IncludeRenderProps<Props, ProductCardRenderProps>;
@@ -66,6 +71,8 @@ export default function ProductCard(props: ProductCardProps) {
6671
const { formatPrice } = state.formatters;
6772
const onClick = useOnProductCardClick(client, state.callbacks.onProductCardClick);
6873

74+
const cnstrcData = getProductCardCnstrcDataAttributes(productInfo);
75+
6976
return (
7077
<>
7178
{typeof children === 'function' ? (
@@ -75,9 +82,11 @@ export default function ProductCard(props: ProductCardProps) {
7582
formatPrice,
7683
onAddToCart,
7784
onClick,
85+
productCardCnstrcDataAttributes: cnstrcData,
7886
})
7987
) : (
8088
<a
89+
{...cnstrcData}
8190
className='cio-product-card'
8291
href={itemUrl}
8392
onClick={(e) => onClick(e, item, productSwatch?.selectedVariation?.variationId)}>

src/hooks/useProduct.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,17 @@ const useProductInfo: UseProductInfo = ({ item }) => {
1717
const itemPrice = productSwatch?.selectedVariation?.price || getPrice(item);
1818
const itemImageUrl = productSwatch?.selectedVariation?.imageUrl || item.imageUrl;
1919
const itemUrl = productSwatch?.selectedVariation?.url || item.url;
20+
const variationId = productSwatch?.selectedVariation?.variationId;
21+
const { itemId } = item;
2022

2123
return {
2224
productSwatch,
2325
itemName,
2426
itemPrice,
2527
itemImageUrl,
2628
itemUrl,
29+
variationId,
30+
itemId,
2731
};
2832
};
2933

src/types.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -240,11 +240,13 @@ export type UseProductSwatchProps = {
240240
export type UseProductSwatch = (props: UseProductSwatchProps) => ProductSwatchObject;
241241

242242
export interface ProductInfoObject {
243-
productSwatch: ProductSwatchObject | undefined;
243+
productSwatch?: ProductSwatchObject;
244244
itemName: string;
245-
itemPrice: number | undefined;
246-
itemUrl: string | undefined;
247-
itemImageUrl: string | undefined;
245+
itemId: string;
246+
itemPrice?: number;
247+
itemUrl?: string;
248+
itemImageUrl?: string;
249+
variationId?: string;
248250
}
249251

250252
export type UseProductInfoProps = {
@@ -308,6 +310,8 @@ export interface PlpItemGroup {
308310
parents: Pick<PlpItemGroup, 'groupId' | 'displayName'>[];
309311
}
310312

313+
export type CnstrcData = Record<`data-cnstrc-${string}`, string | number | boolean>;
314+
311315
// Type Extenders
312316
export type PropsWithChildren<P> = P & { children?: ReactNode };
313317

src/utils/dataAttributeHelpers.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import {
2+
RequestConfigs,
3+
PlpSearchDataResults,
4+
PlpSearchDataRedirect,
5+
PlpBrowseData,
6+
CnstrcData,
7+
ProductInfoObject,
8+
} from '../types';
9+
import { isPlpBrowseDataResults, isPlpSearchDataResults } from './typeHelpers';
10+
import { getPageType } from './requestConfigsHelpers';
11+
12+
export function getProductCardCnstrcDataAttributes(productInfo: ProductInfoObject) {
13+
let dataCnstrc: CnstrcData = {};
14+
15+
const { itemId, itemPrice, itemName, variationId } = productInfo;
16+
17+
dataCnstrc = {
18+
'data-cnstrc-item-id': itemId,
19+
'data-cnstrc-item-name': itemName,
20+
'data-cnstrc-item-price': itemPrice!,
21+
'data-cnstrc-item-variation-id': variationId!,
22+
};
23+
24+
return dataCnstrc;
25+
}
26+
27+
export function getPlpContainerCnstrcDataAttributes(
28+
data: PlpSearchDataResults | PlpSearchDataRedirect | PlpBrowseData | null,
29+
requestConfigs: RequestConfigs,
30+
) {
31+
if (!data || (!isPlpSearchDataResults(data) && !isPlpBrowseDataResults(data))) return {};
32+
33+
const { filterName, filterValue } = requestConfigs;
34+
const pageType = getPageType(requestConfigs);
35+
let dataCnstrc: Record<`data-cnstrc-${string}`, string | number | boolean> = {};
36+
37+
switch (pageType) {
38+
case 'browse':
39+
dataCnstrc = {
40+
'data-cnstrc-browse': true,
41+
'data-cnstrc-num-results': data.response.totalNumResults,
42+
'data-cnstrc-filter-name': filterName!,
43+
'data-cnstrc-filter-value': filterValue!,
44+
};
45+
break;
46+
47+
case 'search':
48+
dataCnstrc = {
49+
'data-cnstrc-search': true,
50+
'data-cnstrc-num-results': data.response.totalNumResults,
51+
};
52+
break;
53+
54+
case 'unknown':
55+
dataCnstrc = {};
56+
break;
57+
58+
default:
59+
break;
60+
}
61+
62+
return dataCnstrc;
63+
}

src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export * from './itemFieldGetters';
33
export * from './styleHelpers';
44
export * from './transformers';
55
export * from './typeHelpers';
6+
export * from './dataAttributeHelpers';
67
export * from './urlHelpers';
78
export * from './requestConfigsHelpers';
89
export * from './common';
Lines changed: 1 addition & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
import { SearchParameters } from '@constructor-io/constructorio-client-javascript/lib/types';
2-
import {
3-
RequestConfigs,
4-
RequestQueryParams,
5-
PlpSearchDataResults,
6-
PlpSearchDataRedirect,
7-
PlpBrowseData,
8-
} from '../types';
2+
import { RequestConfigs, RequestQueryParams } from '../types';
93
import { removeNullValuesFromObject } from './common';
10-
import { isPlpBrowseDataResults, isPlpSearchDataResults } from './typeHelpers';
114

125
export function getSearchParamsFromRequestConfigs(requestConfigs: RequestConfigs): {
136
query: string;
@@ -52,41 +45,3 @@ export function getPageType(requestConfigs: RequestConfigs): PageType {
5245
}
5346
return 'unknown';
5447
}
55-
56-
export function getPlpContainerCnstrcDataAttributes(
57-
data: PlpSearchDataResults | PlpSearchDataRedirect | PlpBrowseData | null,
58-
requestConfigs: RequestConfigs,
59-
) {
60-
if (!data || (!isPlpSearchDataResults(data) && !isPlpBrowseDataResults(data))) return {};
61-
62-
const { filterName, filterValue } = requestConfigs;
63-
const pageType = getPageType(requestConfigs);
64-
let dataCnstrc: Record<`data-cnstrc-${string}`, string | number | boolean> = {};
65-
66-
switch (pageType) {
67-
case 'browse':
68-
dataCnstrc = {
69-
'data-cnstrc-browse': true,
70-
'data-cnstrc-num-results': data.response.totalNumResults,
71-
'data-cnstrc-filter-name': filterName!,
72-
'data-cnstrc-filter-value': filterValue!,
73-
};
74-
break;
75-
76-
case 'search':
77-
dataCnstrc = {
78-
'data-cnstrc-search': true,
79-
'data-cnstrc-num-results': data.response.totalNumResults,
80-
};
81-
break;
82-
83-
case 'unknown':
84-
dataCnstrc = {};
85-
break;
86-
87-
default:
88-
break;
89-
}
90-
91-
return dataCnstrc;
92-
}

0 commit comments

Comments
 (0)