Skip to content

Commit 81bf77f

Browse files
[CI-4786] Add removing search term on track recommendation click (#230)
* [CI-4786] Add removing search term on track recommendation click * [CI-4786] Implement reusable tracking capture utility and update tests for search term clearing on recommendation selection * [CI-4786] Capture tracking request for recommendation selection click and validate tracking
1 parent ba5031d commit 81bf77f

File tree

4 files changed

+111
-12
lines changed

4 files changed

+111
-12
lines changed

src/hooks/useDownShift.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import { useCombobox, UseComboboxProps, UseComboboxReturnValue } from 'downshift
22
import ConstructorIOClient from '@constructor-io/constructorio-client-javascript';
33
import { Nullable } from '@constructor-io/constructorio-client-javascript/lib/types';
44
import { Item, OnSubmit } from '../types';
5-
import { trackSearchSubmit, trackAutocompleteSelect } from '../utils/tracking';
5+
import {
6+
trackSearchSubmit,
7+
trackAutocompleteSelect,
8+
trackRecommendationSelect,
9+
} from '../utils/tracking';
610

711
let idCounter = 0;
812

@@ -44,15 +48,17 @@ const useDownShift: UseDownShift = ({
4448
// Autocomplete Select tracking
4549
// Recommendation Select tracking
4650
if (selectedItem.podId && selectedItem.data?.id && selectedItem.strategy) {
47-
cioClient?.tracker.trackRecommendationClick({
51+
const recommendationData = {
4852
itemName: selectedItem.value,
4953
itemId: selectedItem.data.id,
5054
variationId: selectedItem.data.variation_id,
5155
podId: selectedItem.podId,
5256
strategyId: selectedItem.strategy.id,
5357
section: selectedItem.section,
5458
resultId: selectedItem.result_id,
55-
});
59+
};
60+
trackRecommendationSelect(cioClient, recommendationData);
61+
5662
// Select tracking for all other Constructor sections:
5763
// (ie: Search Suggestions, Products, Custom Cio sections, etc)
5864
// This does not apply to custom user defined sections that aren't part of Constructor index

src/stories/tests/ComponentTests.stories.tsx

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { storageGetItem, storageSetItem } from '../../utils/storage';
66
import { ComponentTemplate } from '../Autocomplete/Component';
77
import { apiKey, onSubmitDefault as onSubmit } from '../../constants';
88
import { CioAutocompleteProps } from '../../types';
9-
import { isTrackingRequestSent } from '../../utils/tracking';
9+
import { isTrackingRequestSent, captureTrackingRequest } from '../../utils/tracking';
1010
import { CONSTANTS } from '../../utils/beaconUtils';
1111

1212
export default {
@@ -76,8 +76,13 @@ FocusFiresTrackingEvent.args = defaultArgs;
7676
FocusFiresTrackingEvent.play = async ({ canvasElement }) => {
7777
await sleep(100);
7878
const canvas = within(canvasElement);
79-
await userEvent.click(canvas.getByTestId('cio-input'));
80-
const isFocusTrackingRequestSent = isTrackingRequestSent('action=focus');
79+
const input = canvas.getByTestId('cio-input');
80+
81+
// Use the reusable tracking capture utility
82+
const isFocusTrackingRequestSent = await captureTrackingRequest('action=focus', async () => {
83+
await userEvent.click(input);
84+
});
85+
8186
expect(isFocusTrackingRequestSent).toBeTruthy();
8287
};
8388

@@ -357,6 +362,56 @@ SelectProductSuggestionClearsSearchTermStorage.play = async ({ canvasElement })
357362
await sleep(1000);
358363
};
359364

365+
// - select recommendation from zero state => Search Term Storage is Cleared
366+
export const SelectZeroStateRecommendationClearsSearchTermStorage = ComponentTemplate.bind({});
367+
SelectZeroStateRecommendationClearsSearchTermStorage.args = {
368+
...defaultArgs,
369+
autocompleteClassName: 'cio-autocomplete full-example-autocomplete-styles',
370+
advancedParameters: {
371+
displaySearchSuggestionImages: true,
372+
displaySearchSuggestionResultCounts: true,
373+
numTermsWithGroupSuggestions: 6,
374+
},
375+
sections: [
376+
{
377+
indexSectionName: 'Search Suggestions',
378+
numResults: 8,
379+
displaySearchTermHighlights: true,
380+
},
381+
],
382+
zeroStateSections: [
383+
{
384+
podId: 'bestsellers',
385+
type: 'recommendations',
386+
numResults: 6,
387+
},
388+
],
389+
};
390+
SelectZeroStateRecommendationClearsSearchTermStorage.play = async ({ canvasElement }) => {
391+
const canvas = within(canvasElement);
392+
storageSetItem(CONSTANTS.SEARCH_TERM_STORAGE_KEY, 'test search term');
393+
394+
await userEvent.click(canvas.getByTestId('cio-input'));
395+
await sleep(1000);
396+
expect(canvas.getAllByText('Best Sellers').length).toBeGreaterThan(0);
397+
398+
const bestSellersSection = canvas.getByTestId('cio-results').querySelector('.cio-section');
399+
const recommendationItems = bestSellersSection?.querySelectorAll('[data-cnstrc-item-id]');
400+
401+
const firstRecommendation = recommendationItems?.[0];
402+
const isSelectTrackingRequestSent = await captureTrackingRequest(
403+
'/recommendation_result_click',
404+
async () => {
405+
if (firstRecommendation) {
406+
await userEvent.click(firstRecommendation);
407+
}
408+
}
409+
);
410+
411+
expect(isSelectTrackingRequestSent).toBeTruthy();
412+
expect(storageGetItem(CONSTANTS.SEARCH_TERM_STORAGE_KEY)).toBeNull();
413+
};
414+
360415
// - click search icon => network search submit event
361416
export const SearchIconSubmitSearch = ComponentTemplate.bind({});
362417
SearchIconSubmitSearch.args = defaultArgs;
@@ -505,7 +560,7 @@ InGroupSuggestions.play = async ({ canvasElement }) => {
505560
const canvas = within(canvasElement);
506561
await userEvent.type(canvas.getByTestId('cio-input'), 'socks', { delay: 100 });
507562
await sleep(1000);
508-
expect(canvas.getAllByText('in Socks').length).toEqual(1);
563+
expect(canvas.getAllByText(/in Socks/)).toHaveLength(1);
509564
};
510565

511566
export const InGroupSuggestionsTwo = ComponentTemplate.bind({});

src/stories/tests/HooksTests.stories.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { within, userEvent, expect, fn } from '@storybook/test';
22
import { CioAutocomplete } from '../../index';
33
import { argTypes } from '../Autocomplete/argTypes';
44
import { getCioClient, sleep } from '../../utils/helpers';
5-
import { isTrackingRequestSent } from '../../utils/tracking';
6-
import { HooksTemplate } from '../Autocomplete/Hook/index';
5+
import { isTrackingRequestSent, captureTrackingRequest } from '../../utils/tracking';
6+
import { HooksTemplate } from '../Autocomplete/Hook';
77
import { apiKey, onSubmitDefault as onSubmit } from '../../constants';
88
import { CioAutocompleteProps } from '../../types';
99

@@ -73,8 +73,13 @@ FocusFiresTrackingEvent.args = defaultArgs;
7373
FocusFiresTrackingEvent.play = async ({ canvasElement }) => {
7474
await sleep(100);
7575
const canvas = within(canvasElement);
76-
await userEvent.click(canvas.getByTestId('cio-input'));
77-
const isFocusTrackingRequestSent = isTrackingRequestSent('action=focus');
76+
const input = canvas.getByTestId('cio-input');
77+
78+
// Use the reusable tracking capture utility
79+
const isFocusTrackingRequestSent = await captureTrackingRequest('action=focus', async () => {
80+
await userEvent.click(input);
81+
});
82+
7883
expect(isFocusTrackingRequestSent).toBeTruthy();
7984
};
8085

@@ -435,7 +440,7 @@ InGroupSuggestions.play = async ({ canvasElement }) => {
435440
const canvas = within(canvasElement);
436441
await userEvent.type(canvas.getByTestId('cio-input'), 'socks', { delay: 100 });
437442
await sleep(1000);
438-
expect(canvas.getAllByText('in Socks').length).toEqual(1);
443+
expect(canvas.getAllByText(/in Socks/)).toHaveLength(1);
439444
};
440445

441446
export const InGroupSuggestionsTwo = HooksTemplate.bind({});

src/utils/tracking.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,36 @@ export const trackAutocompleteSelect = (cioClient, itemName, autocompleteData: a
5555
storageRemoveItem(CONSTANTS.SEARCH_TERM_STORAGE_KEY);
5656
}
5757
};
58+
59+
export const trackRecommendationSelect = (cioClient, recommendationData: any = {}) => {
60+
storageRemoveItem(CONSTANTS.SEARCH_TERM_STORAGE_KEY);
61+
cioClient?.tracker.trackRecommendationClick(recommendationData);
62+
};
63+
64+
export const captureTrackingRequest = async (
65+
urlPattern: string,
66+
action: () => Promise<void>
67+
): Promise<boolean> => {
68+
let trackingCaptured = false;
69+
const originalSetItem = Storage.prototype.setItem;
70+
71+
Storage.prototype.setItem = function setItemInterceptor(key, value) {
72+
if (key === '_constructorio_requests') {
73+
try {
74+
const requests = JSON.parse(value);
75+
if (requests.some((req: any) => req?.url?.includes(urlPattern))) {
76+
trackingCaptured = true;
77+
}
78+
} catch (e) {
79+
// Ignore parsing errors
80+
}
81+
}
82+
return originalSetItem.call(this, key, value);
83+
};
84+
85+
await action();
86+
87+
Storage.prototype.setItem = originalSetItem;
88+
89+
return trackingCaptured || isTrackingRequestSent(urlPattern);
90+
};

0 commit comments

Comments
 (0)