@tapcart/mobile-components 0.14.6 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/hooks/use-search-analytics-open-product.d.ts +123 -0
- package/dist/components/hooks/use-search-analytics-open-product.d.ts.map +1 -0
- package/dist/components/hooks/use-search-analytics-open-product.js +191 -0
- package/dist/components/hooks/use-search-analytics-open-product.test.d.ts +2 -0
- package/dist/components/hooks/use-search-analytics-open-product.test.d.ts.map +1 -0
- package/dist/components/hooks/use-search-analytics-open-product.test.js +315 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/styles.css +105 -127
- package/package.json +1 -1
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
interface SearchAnalyticsProduct {
|
|
2
|
+
id?: string;
|
|
3
|
+
objectID?: string;
|
|
4
|
+
variants?: Array<{
|
|
5
|
+
id?: string;
|
|
6
|
+
} | undefined | null>;
|
|
7
|
+
}
|
|
8
|
+
interface EmitProductInteractionPayload {
|
|
9
|
+
client: unknown;
|
|
10
|
+
surface: string;
|
|
11
|
+
product: {
|
|
12
|
+
productId?: string;
|
|
13
|
+
variantId?: string;
|
|
14
|
+
objectId?: string;
|
|
15
|
+
position?: number;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
interface OpenProductProps {
|
|
19
|
+
productId?: string;
|
|
20
|
+
variantId?: string;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
interface UseSearchAnalyticsOpenProductParams {
|
|
24
|
+
products?: Array<SearchAnalyticsProduct | undefined | null>;
|
|
25
|
+
searchClient?: unknown;
|
|
26
|
+
emitProductClick?: (payload: EmitProductInteractionPayload) => void;
|
|
27
|
+
action?: (name: string, params?: unknown) => void;
|
|
28
|
+
surface?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Returns a stable `openProduct` handler that opens the product AND emits a
|
|
32
|
+
* search-analytics click, looking up the clicked product's Algolia `objectID`
|
|
33
|
+
* and 1-based position from the current results.
|
|
34
|
+
*
|
|
35
|
+
* Provider-agnostic: `emitProductClick` reads attribution from whatever search
|
|
36
|
+
* client is active, so this same wiring works for every search provider. The
|
|
37
|
+
* click analytics no-op when the grid isn't search-backed (`searchClient`
|
|
38
|
+
* falsy) or the emitter isn't injected, so non-search grids stay clean.
|
|
39
|
+
*
|
|
40
|
+
* Centralizes the wiring shared by every search-backed grid block
|
|
41
|
+
* (CollectionProductGrid, ProductGrid, TabbedProductGrid, the product
|
|
42
|
+
* carousels, ...) so there's one implementation to maintain. The results are
|
|
43
|
+
* held in a ref so the returned callback keeps a stable identity (preserving
|
|
44
|
+
* a memoized ProductCard) while still reading the latest list on click.
|
|
45
|
+
*/
|
|
46
|
+
export declare function useSearchAnalyticsOpenProduct({ products, searchClient, emitProductClick, action, surface, }: UseSearchAnalyticsOpenProductParams): (props: OpenProductProps) => void;
|
|
47
|
+
interface AddToCartProps {
|
|
48
|
+
lineItems?: Array<{
|
|
49
|
+
variantId?: string;
|
|
50
|
+
quantity?: number;
|
|
51
|
+
} | undefined | null>;
|
|
52
|
+
[key: string]: unknown;
|
|
53
|
+
}
|
|
54
|
+
interface UseSearchAnalyticsAddToCartParams {
|
|
55
|
+
products?: Array<SearchAnalyticsProduct | undefined | null>;
|
|
56
|
+
searchClient?: unknown;
|
|
57
|
+
emitProductConversion?: (payload: EmitProductInteractionPayload) => void;
|
|
58
|
+
action?: (name: string, params?: unknown) => void | Promise<unknown>;
|
|
59
|
+
surface?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Sibling of `useSearchAnalyticsOpenProduct` for the add-to-cart path. Returns a
|
|
63
|
+
* stable `addToCart` handler that adds the item AND emits a search-analytics
|
|
64
|
+
* `add_to_cart` conversion, attributed to the originating search via the
|
|
65
|
+
* product's Algolia `objectID` + the active client's attribution.
|
|
66
|
+
*
|
|
67
|
+
* Unlike open-product, ProductCard's `addToCart` receives a line-item shape
|
|
68
|
+
* (`{ lineItems: [{ variantId }] }`) with no productId, so the results lookup
|
|
69
|
+
* matches on the added variant. Same ref-based stability as the sibling hook.
|
|
70
|
+
*
|
|
71
|
+
* The conversion is emitted only AFTER the add succeeds — `cart/add` is awaited,
|
|
72
|
+
* so a failed add (rejection) never counts as a conversion. This matches the
|
|
73
|
+
* quick-add drawer's on-success semantics (`useSearchAnalyticsConversionEmit`),
|
|
74
|
+
* which the drawer fires from its own add's `onSuccess`. The attribution target
|
|
75
|
+
* is captured from the results BEFORE the await so a list change mid-add can't
|
|
76
|
+
* misattribute. Awaiting a non-promise `action` (e.g. in tests) resolves
|
|
77
|
+
* immediately and still emits, so the conversion is never silently dropped.
|
|
78
|
+
*
|
|
79
|
+
* No-op (the item is still added) when the grid isn't search-backed
|
|
80
|
+
* (`searchClient` falsy), the emitter isn't injected, or the added variant
|
|
81
|
+
* isn't in the current results — without a matched hit there's no `objectID` to
|
|
82
|
+
* attribute, so we skip the emit rather than send a provider-less event.
|
|
83
|
+
*/
|
|
84
|
+
export declare function useSearchAnalyticsAddToCart({ products, searchClient, emitProductConversion, action, surface, }: UseSearchAnalyticsAddToCartParams): (props: AddToCartProps) => Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Emit-only sibling of `useSearchAnalyticsAddToCart` for the quick-add drawer
|
|
87
|
+
* path. Multi-variant products open the quick-add drawer (rendered at app-layout
|
|
88
|
+
* level, OUTSIDE the page's SearchClientProvider), which performs its own
|
|
89
|
+
* `actions.addToCart`. So the drawer can't read the search context or call the
|
|
90
|
+
* combined add-and-emit handler without double-adding. This returns a stable
|
|
91
|
+
* `emitAddToCartConversion(variantId)` the drawer invokes on add success to emit
|
|
92
|
+
* the `add_to_cart` conversion only — attributed via the originating result's
|
|
93
|
+
* Algolia `objectID` + 1-based position, no cart mutation. No-ops when the grid
|
|
94
|
+
* isn't search-backed (`searchClient` falsy), the emitter isn't injected, the
|
|
95
|
+
* variant is missing, or the variant isn't found in the current results.
|
|
96
|
+
*/
|
|
97
|
+
export declare function useSearchAnalyticsConversionEmit({ products, searchClient, emitProductConversion, surface, }: UseSearchAnalyticsAddToCartParams): (variantId?: string) => void;
|
|
98
|
+
interface UseSearchAnalyticsImpressionParams {
|
|
99
|
+
searchClient?: unknown;
|
|
100
|
+
emitProductImpression?: (payload: EmitProductInteractionPayload) => void;
|
|
101
|
+
surface?: string;
|
|
102
|
+
/**
|
|
103
|
+
* Whether search-analytics is enabled (the `app-studio-search-analytics`
|
|
104
|
+
* flag). Required to gate setup: `emitProductImpression` is a truthy no-op
|
|
105
|
+
* when the flag is off, so without this the IntersectionObserver would still
|
|
106
|
+
* be constructed per-card on every search-backed grid. Defaults on for
|
|
107
|
+
* backward compatibility with callers that don't pass it yet.
|
|
108
|
+
*/
|
|
109
|
+
enabled?: boolean;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Returns an `onImpression(product)` callback for ProductCard to fire when a
|
|
113
|
+
* result first scrolls into view, emitting a search-analytics `impression`
|
|
114
|
+
* (Algolia `viewedObjectIDs` — objectID only, no position). Dedupes so each
|
|
115
|
+
* product emits at most once per session, even across virtualized remounts.
|
|
116
|
+
*
|
|
117
|
+
* Returns `undefined` when the grid isn't search-backed or no emitter is
|
|
118
|
+
* injected, so ProductCard skips setting up an IntersectionObserver entirely on
|
|
119
|
+
* non-search surfaces.
|
|
120
|
+
*/
|
|
121
|
+
export declare function useSearchAnalyticsImpression({ searchClient, emitProductImpression, surface, enabled, }: UseSearchAnalyticsImpressionParams): ((product: SearchAnalyticsProduct | undefined | null) => void) | undefined;
|
|
122
|
+
export {};
|
|
123
|
+
//# sourceMappingURL=use-search-analytics-open-product.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-search-analytics-open-product.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-search-analytics-open-product.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
import React from "react";
|
|
12
|
+
import { getIdFromGid } from "../../lib/utils";
|
|
13
|
+
/**
|
|
14
|
+
* Returns a stable `openProduct` handler that opens the product AND emits a
|
|
15
|
+
* search-analytics click, looking up the clicked product's Algolia `objectID`
|
|
16
|
+
* and 1-based position from the current results.
|
|
17
|
+
*
|
|
18
|
+
* Provider-agnostic: `emitProductClick` reads attribution from whatever search
|
|
19
|
+
* client is active, so this same wiring works for every search provider. The
|
|
20
|
+
* click analytics no-op when the grid isn't search-backed (`searchClient`
|
|
21
|
+
* falsy) or the emitter isn't injected, so non-search grids stay clean.
|
|
22
|
+
*
|
|
23
|
+
* Centralizes the wiring shared by every search-backed grid block
|
|
24
|
+
* (CollectionProductGrid, ProductGrid, TabbedProductGrid, the product
|
|
25
|
+
* carousels, ...) so there's one implementation to maintain. The results are
|
|
26
|
+
* held in a ref so the returned callback keeps a stable identity (preserving
|
|
27
|
+
* a memoized ProductCard) while still reading the latest list on click.
|
|
28
|
+
*/
|
|
29
|
+
export function useSearchAnalyticsOpenProduct({ products, searchClient, emitProductClick, action, surface = "collection", }) {
|
|
30
|
+
const productsRef = React.useRef(products);
|
|
31
|
+
productsRef.current = products;
|
|
32
|
+
// Hold `action` in a ref and read the latest at fire time so the returned
|
|
33
|
+
// callback stays a stable reference regardless of whether `Tapcart.action`
|
|
34
|
+
// is stable across renders — otherwise an unstable `action` would rebuild it
|
|
35
|
+
// every render and re-render every memoized ProductCard, defeating the whole
|
|
36
|
+
// point of this hook. Mirrors useSearchAnalyticsEmitter.
|
|
37
|
+
const actionRef = React.useRef(action);
|
|
38
|
+
actionRef.current = action;
|
|
39
|
+
return React.useCallback((props) => {
|
|
40
|
+
const action = actionRef.current;
|
|
41
|
+
action === null || action === void 0 ? void 0 : action("trigger/haptic");
|
|
42
|
+
action === null || action === void 0 ? void 0 : action("product/open", props);
|
|
43
|
+
if (!searchClient || !emitProductClick)
|
|
44
|
+
return;
|
|
45
|
+
const list = productsRef.current || [];
|
|
46
|
+
const index = list.findIndex((item) => getIdFromGid(item === null || item === void 0 ? void 0 : item.id) === (props === null || props === void 0 ? void 0 : props.productId));
|
|
47
|
+
const hit = list[index];
|
|
48
|
+
emitProductClick({
|
|
49
|
+
client: searchClient,
|
|
50
|
+
surface,
|
|
51
|
+
product: {
|
|
52
|
+
productId: props === null || props === void 0 ? void 0 : props.productId,
|
|
53
|
+
variantId: props === null || props === void 0 ? void 0 : props.variantId,
|
|
54
|
+
objectId: hit === null || hit === void 0 ? void 0 : hit.objectID,
|
|
55
|
+
position: index >= 0 ? index + 1 : undefined,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}, [searchClient, emitProductClick, surface]);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Sibling of `useSearchAnalyticsOpenProduct` for the add-to-cart path. Returns a
|
|
62
|
+
* stable `addToCart` handler that adds the item AND emits a search-analytics
|
|
63
|
+
* `add_to_cart` conversion, attributed to the originating search via the
|
|
64
|
+
* product's Algolia `objectID` + the active client's attribution.
|
|
65
|
+
*
|
|
66
|
+
* Unlike open-product, ProductCard's `addToCart` receives a line-item shape
|
|
67
|
+
* (`{ lineItems: [{ variantId }] }`) with no productId, so the results lookup
|
|
68
|
+
* matches on the added variant. Same ref-based stability as the sibling hook.
|
|
69
|
+
*
|
|
70
|
+
* The conversion is emitted only AFTER the add succeeds — `cart/add` is awaited,
|
|
71
|
+
* so a failed add (rejection) never counts as a conversion. This matches the
|
|
72
|
+
* quick-add drawer's on-success semantics (`useSearchAnalyticsConversionEmit`),
|
|
73
|
+
* which the drawer fires from its own add's `onSuccess`. The attribution target
|
|
74
|
+
* is captured from the results BEFORE the await so a list change mid-add can't
|
|
75
|
+
* misattribute. Awaiting a non-promise `action` (e.g. in tests) resolves
|
|
76
|
+
* immediately and still emits, so the conversion is never silently dropped.
|
|
77
|
+
*
|
|
78
|
+
* No-op (the item is still added) when the grid isn't search-backed
|
|
79
|
+
* (`searchClient` falsy), the emitter isn't injected, or the added variant
|
|
80
|
+
* isn't in the current results — without a matched hit there's no `objectID` to
|
|
81
|
+
* attribute, so we skip the emit rather than send a provider-less event.
|
|
82
|
+
*/
|
|
83
|
+
export function useSearchAnalyticsAddToCart({ products, searchClient, emitProductConversion, action, surface = "collection", }) {
|
|
84
|
+
const productsRef = React.useRef(products);
|
|
85
|
+
productsRef.current = products;
|
|
86
|
+
const actionRef = React.useRef(action);
|
|
87
|
+
actionRef.current = action;
|
|
88
|
+
return React.useCallback((props) => __awaiter(this, void 0, void 0, function* () {
|
|
89
|
+
var _a, _b;
|
|
90
|
+
const action = actionRef.current;
|
|
91
|
+
action === null || action === void 0 ? void 0 : action("trigger/haptic");
|
|
92
|
+
// Not search-backed (or no emitter): just add — nothing to attribute.
|
|
93
|
+
if (!searchClient || !emitProductConversion) {
|
|
94
|
+
action === null || action === void 0 ? void 0 : action("cart/add", props);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// Resolve the attribution target from the CURRENT results up front, so a
|
|
98
|
+
// list change while the add is in flight can't misattribute the emit.
|
|
99
|
+
const variantId = (_b = (_a = props === null || props === void 0 ? void 0 : props.lineItems) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.variantId;
|
|
100
|
+
const list = productsRef.current || [];
|
|
101
|
+
const index = variantId
|
|
102
|
+
? list.findIndex((item) => { var _a; return (_a = item === null || item === void 0 ? void 0 : item.variants) === null || _a === void 0 ? void 0 : _a.some((v) => getIdFromGid(v === null || v === void 0 ? void 0 : v.id) === variantId); })
|
|
103
|
+
: -1;
|
|
104
|
+
const hit = index >= 0 ? list[index] : undefined;
|
|
105
|
+
// Emit only on a successful add (see JSDoc): a rejected cart/add skips it.
|
|
106
|
+
try {
|
|
107
|
+
yield (action === null || action === void 0 ? void 0 : action("cart/add", props));
|
|
108
|
+
}
|
|
109
|
+
catch (_c) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// No matched result → no objectID to attribute; skip.
|
|
113
|
+
if (!variantId || index < 0)
|
|
114
|
+
return;
|
|
115
|
+
emitProductConversion({
|
|
116
|
+
client: searchClient,
|
|
117
|
+
surface,
|
|
118
|
+
product: {
|
|
119
|
+
productId: getIdFromGid(hit === null || hit === void 0 ? void 0 : hit.id),
|
|
120
|
+
variantId,
|
|
121
|
+
objectId: hit === null || hit === void 0 ? void 0 : hit.objectID,
|
|
122
|
+
position: index + 1,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
}), [searchClient, emitProductConversion, surface]);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Emit-only sibling of `useSearchAnalyticsAddToCart` for the quick-add drawer
|
|
129
|
+
* path. Multi-variant products open the quick-add drawer (rendered at app-layout
|
|
130
|
+
* level, OUTSIDE the page's SearchClientProvider), which performs its own
|
|
131
|
+
* `actions.addToCart`. So the drawer can't read the search context or call the
|
|
132
|
+
* combined add-and-emit handler without double-adding. This returns a stable
|
|
133
|
+
* `emitAddToCartConversion(variantId)` the drawer invokes on add success to emit
|
|
134
|
+
* the `add_to_cart` conversion only — attributed via the originating result's
|
|
135
|
+
* Algolia `objectID` + 1-based position, no cart mutation. No-ops when the grid
|
|
136
|
+
* isn't search-backed (`searchClient` falsy), the emitter isn't injected, the
|
|
137
|
+
* variant is missing, or the variant isn't found in the current results.
|
|
138
|
+
*/
|
|
139
|
+
export function useSearchAnalyticsConversionEmit({ products, searchClient, emitProductConversion, surface = "collection", }) {
|
|
140
|
+
const productsRef = React.useRef(products);
|
|
141
|
+
productsRef.current = products;
|
|
142
|
+
return React.useCallback((variantId) => {
|
|
143
|
+
if (!searchClient || !emitProductConversion || !variantId)
|
|
144
|
+
return;
|
|
145
|
+
const list = productsRef.current || [];
|
|
146
|
+
const index = list.findIndex((item) => { var _a; return (_a = item === null || item === void 0 ? void 0 : item.variants) === null || _a === void 0 ? void 0 : _a.some((v) => getIdFromGid(v === null || v === void 0 ? void 0 : v.id) === variantId); });
|
|
147
|
+
if (index < 0)
|
|
148
|
+
return;
|
|
149
|
+
const hit = list[index];
|
|
150
|
+
emitProductConversion({
|
|
151
|
+
client: searchClient,
|
|
152
|
+
surface,
|
|
153
|
+
product: {
|
|
154
|
+
productId: getIdFromGid(hit === null || hit === void 0 ? void 0 : hit.id),
|
|
155
|
+
variantId,
|
|
156
|
+
objectId: hit === null || hit === void 0 ? void 0 : hit.objectID,
|
|
157
|
+
position: index + 1,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
}, [searchClient, emitProductConversion, surface]);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Returns an `onImpression(product)` callback for ProductCard to fire when a
|
|
164
|
+
* result first scrolls into view, emitting a search-analytics `impression`
|
|
165
|
+
* (Algolia `viewedObjectIDs` — objectID only, no position). Dedupes so each
|
|
166
|
+
* product emits at most once per session, even across virtualized remounts.
|
|
167
|
+
*
|
|
168
|
+
* Returns `undefined` when the grid isn't search-backed or no emitter is
|
|
169
|
+
* injected, so ProductCard skips setting up an IntersectionObserver entirely on
|
|
170
|
+
* non-search surfaces.
|
|
171
|
+
*/
|
|
172
|
+
export function useSearchAnalyticsImpression({ searchClient, emitProductImpression, surface = "collection", enabled = true, }) {
|
|
173
|
+
const seenRef = React.useRef(new Set());
|
|
174
|
+
return React.useMemo(() => {
|
|
175
|
+
if (!searchClient || !emitProductImpression || !enabled)
|
|
176
|
+
return undefined;
|
|
177
|
+
return (product) => {
|
|
178
|
+
const objectId = product === null || product === void 0 ? void 0 : product.objectID;
|
|
179
|
+
const productId = getIdFromGid(product === null || product === void 0 ? void 0 : product.id);
|
|
180
|
+
const key = objectId || productId;
|
|
181
|
+
if (!key || seenRef.current.has(key))
|
|
182
|
+
return;
|
|
183
|
+
seenRef.current.add(key);
|
|
184
|
+
emitProductImpression({
|
|
185
|
+
client: searchClient,
|
|
186
|
+
surface,
|
|
187
|
+
product: { productId, objectId },
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
}, [searchClient, emitProductImpression, surface, enabled]);
|
|
191
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-search-analytics-open-product.test.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-search-analytics-open-product.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { renderHook } from "@testing-library/react";
|
|
11
|
+
import { useSearchAnalyticsOpenProduct, useSearchAnalyticsAddToCart, useSearchAnalyticsImpression, } from "./use-search-analytics-open-product";
|
|
12
|
+
const products = [
|
|
13
|
+
{ id: "gid://shopify/Product/1", objectID: "obj-1" },
|
|
14
|
+
{ id: "gid://shopify/Product/2", objectID: "obj-2" },
|
|
15
|
+
{ id: "gid://shopify/Product/3", objectID: "obj-3" },
|
|
16
|
+
];
|
|
17
|
+
describe("useSearchAnalyticsOpenProduct", () => {
|
|
18
|
+
it("always opens the product (haptic + product/open) even when not search-backed", () => {
|
|
19
|
+
const action = jest.fn();
|
|
20
|
+
const emitProductClick = jest.fn();
|
|
21
|
+
const { result } = renderHook(() => useSearchAnalyticsOpenProduct({
|
|
22
|
+
products,
|
|
23
|
+
searchClient: undefined,
|
|
24
|
+
emitProductClick,
|
|
25
|
+
action,
|
|
26
|
+
}));
|
|
27
|
+
result.current({ productId: "2", variantId: "v2" });
|
|
28
|
+
expect(action).toHaveBeenCalledWith("trigger/haptic");
|
|
29
|
+
expect(action).toHaveBeenCalledWith("product/open", {
|
|
30
|
+
productId: "2",
|
|
31
|
+
variantId: "v2",
|
|
32
|
+
});
|
|
33
|
+
// No search to attribute to -> no click emitted.
|
|
34
|
+
expect(emitProductClick).not.toHaveBeenCalled();
|
|
35
|
+
});
|
|
36
|
+
it("does not throw and opens the product when no emitter is injected", () => {
|
|
37
|
+
const action = jest.fn();
|
|
38
|
+
const { result } = renderHook(() => useSearchAnalyticsOpenProduct({
|
|
39
|
+
products,
|
|
40
|
+
searchClient: {},
|
|
41
|
+
emitProductClick: undefined,
|
|
42
|
+
action,
|
|
43
|
+
}));
|
|
44
|
+
expect(() => result.current({ productId: "1" })).not.toThrow();
|
|
45
|
+
expect(action).toHaveBeenCalledWith("product/open", { productId: "1" });
|
|
46
|
+
});
|
|
47
|
+
it("emits a click with the hit objectID and 1-based position for a search-backed grid", () => {
|
|
48
|
+
const action = jest.fn();
|
|
49
|
+
const emitProductClick = jest.fn();
|
|
50
|
+
const searchClient = { provider: "algolia" };
|
|
51
|
+
const { result } = renderHook(() => useSearchAnalyticsOpenProduct({
|
|
52
|
+
products,
|
|
53
|
+
searchClient,
|
|
54
|
+
emitProductClick,
|
|
55
|
+
action,
|
|
56
|
+
surface: "collection",
|
|
57
|
+
}));
|
|
58
|
+
result.current({ productId: "2", variantId: "v2" });
|
|
59
|
+
expect(emitProductClick).toHaveBeenCalledTimes(1);
|
|
60
|
+
expect(emitProductClick).toHaveBeenCalledWith({
|
|
61
|
+
client: searchClient,
|
|
62
|
+
surface: "collection",
|
|
63
|
+
product: {
|
|
64
|
+
productId: "2",
|
|
65
|
+
variantId: "v2",
|
|
66
|
+
objectId: "obj-2",
|
|
67
|
+
position: 2, // 1-based index of the second product
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
it("emits with undefined objectId/position when the clicked product isn't in the list", () => {
|
|
72
|
+
const action = jest.fn();
|
|
73
|
+
const emitProductClick = jest.fn();
|
|
74
|
+
const { result } = renderHook(() => useSearchAnalyticsOpenProduct({
|
|
75
|
+
products,
|
|
76
|
+
searchClient: { provider: "algolia" },
|
|
77
|
+
emitProductClick,
|
|
78
|
+
action,
|
|
79
|
+
}));
|
|
80
|
+
result.current({ productId: "999" });
|
|
81
|
+
expect(emitProductClick).toHaveBeenCalledWith(expect.objectContaining({
|
|
82
|
+
product: expect.objectContaining({
|
|
83
|
+
objectId: undefined,
|
|
84
|
+
position: undefined,
|
|
85
|
+
}),
|
|
86
|
+
}));
|
|
87
|
+
});
|
|
88
|
+
it("keeps a stable callback identity across re-renders (preserves memoized cards)", () => {
|
|
89
|
+
const action = jest.fn();
|
|
90
|
+
const emitProductClick = jest.fn();
|
|
91
|
+
const searchClient = { provider: "algolia" };
|
|
92
|
+
const { result, rerender } = renderHook(({ list }) => useSearchAnalyticsOpenProduct({
|
|
93
|
+
products: list,
|
|
94
|
+
searchClient,
|
|
95
|
+
emitProductClick,
|
|
96
|
+
action,
|
|
97
|
+
}), { initialProps: { list: products } });
|
|
98
|
+
const first = result.current;
|
|
99
|
+
// New products array reference (e.g. next infinite-scroll page) must NOT
|
|
100
|
+
// rebuild the callback, or every ProductCard would re-render.
|
|
101
|
+
rerender({
|
|
102
|
+
list: [...products, { id: "gid://shopify/Product/4", objectID: "obj-4" }],
|
|
103
|
+
});
|
|
104
|
+
expect(result.current).toBe(first);
|
|
105
|
+
// ...but the stable callback still reads the latest list on click.
|
|
106
|
+
result.current({ productId: "4" });
|
|
107
|
+
expect(emitProductClick).toHaveBeenCalledWith(expect.objectContaining({
|
|
108
|
+
product: expect.objectContaining({ objectId: "obj-4", position: 4 }),
|
|
109
|
+
}));
|
|
110
|
+
});
|
|
111
|
+
it("stays stable across an unstable `action` and still fires the latest one", () => {
|
|
112
|
+
const emitProductClick = jest.fn();
|
|
113
|
+
const searchClient = { provider: "algolia" };
|
|
114
|
+
const firstAction = jest.fn();
|
|
115
|
+
const { result, rerender } = renderHook(({ action }) => useSearchAnalyticsOpenProduct({
|
|
116
|
+
products,
|
|
117
|
+
searchClient,
|
|
118
|
+
emitProductClick,
|
|
119
|
+
action,
|
|
120
|
+
}), { initialProps: { action: firstAction } });
|
|
121
|
+
const first = result.current;
|
|
122
|
+
// A new `action` identity (e.g. useTapcart() returning a fresh fn) must NOT
|
|
123
|
+
// rebuild the callback — it's held in a ref.
|
|
124
|
+
const secondAction = jest.fn();
|
|
125
|
+
rerender({ action: secondAction });
|
|
126
|
+
expect(result.current).toBe(first);
|
|
127
|
+
// The stable callback still invokes the *latest* action, not the stale one.
|
|
128
|
+
result.current({ productId: "1" });
|
|
129
|
+
expect(secondAction).toHaveBeenCalledWith("product/open", {
|
|
130
|
+
productId: "1",
|
|
131
|
+
});
|
|
132
|
+
expect(firstAction).not.toHaveBeenCalled();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
const cartProducts = [
|
|
136
|
+
{
|
|
137
|
+
id: "gid://shopify/Product/1",
|
|
138
|
+
objectID: "obj-1",
|
|
139
|
+
variants: [{ id: "gid://shopify/ProductVariant/11" }],
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: "gid://shopify/Product/2",
|
|
143
|
+
objectID: "obj-2",
|
|
144
|
+
variants: [
|
|
145
|
+
{ id: "gid://shopify/ProductVariant/22" },
|
|
146
|
+
{ id: "gid://shopify/ProductVariant/23" },
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
// ProductCard's add-to-cart payload: line items keyed by numeric variant id.
|
|
151
|
+
const addProps = (variantId) => ({
|
|
152
|
+
lineItems: [{ variantId, quantity: 1 }],
|
|
153
|
+
cartAttributes: [],
|
|
154
|
+
});
|
|
155
|
+
describe("useSearchAnalyticsAddToCart", () => {
|
|
156
|
+
it("always adds the item (haptic + cart/add) even when not search-backed", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
157
|
+
const action = jest.fn();
|
|
158
|
+
const emitProductConversion = jest.fn();
|
|
159
|
+
const { result } = renderHook(() => useSearchAnalyticsAddToCart({
|
|
160
|
+
products: cartProducts,
|
|
161
|
+
searchClient: undefined,
|
|
162
|
+
emitProductConversion,
|
|
163
|
+
action,
|
|
164
|
+
}));
|
|
165
|
+
yield result.current(addProps("22"));
|
|
166
|
+
expect(action).toHaveBeenCalledWith("trigger/haptic");
|
|
167
|
+
expect(action).toHaveBeenCalledWith("cart/add", addProps("22"));
|
|
168
|
+
expect(emitProductConversion).not.toHaveBeenCalled();
|
|
169
|
+
}));
|
|
170
|
+
it("emits an add_to_cart conversion (after the add succeeds) with the matched product's objectID + 1-based position", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
171
|
+
const action = jest.fn();
|
|
172
|
+
const emitProductConversion = jest.fn();
|
|
173
|
+
const searchClient = { provider: "algolia" };
|
|
174
|
+
const { result } = renderHook(() => useSearchAnalyticsAddToCart({
|
|
175
|
+
products: cartProducts,
|
|
176
|
+
searchClient,
|
|
177
|
+
emitProductConversion,
|
|
178
|
+
action,
|
|
179
|
+
}));
|
|
180
|
+
// Add the second variant of the second product.
|
|
181
|
+
yield result.current(addProps("23"));
|
|
182
|
+
expect(action).toHaveBeenCalledWith("cart/add", addProps("23"));
|
|
183
|
+
expect(emitProductConversion).toHaveBeenCalledTimes(1);
|
|
184
|
+
expect(emitProductConversion).toHaveBeenCalledWith({
|
|
185
|
+
client: searchClient,
|
|
186
|
+
surface: "collection",
|
|
187
|
+
product: {
|
|
188
|
+
productId: "2",
|
|
189
|
+
variantId: "23",
|
|
190
|
+
objectId: "obj-2",
|
|
191
|
+
position: 2,
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
}));
|
|
195
|
+
it("does NOT emit the conversion when the add fails (cart/add rejects)", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
196
|
+
const action = jest.fn((name) => name === "cart/add" ? Promise.reject(new Error("add failed")) : undefined);
|
|
197
|
+
const emitProductConversion = jest.fn();
|
|
198
|
+
const { result } = renderHook(() => useSearchAnalyticsAddToCart({
|
|
199
|
+
products: cartProducts,
|
|
200
|
+
searchClient: { provider: "algolia" },
|
|
201
|
+
emitProductConversion,
|
|
202
|
+
action,
|
|
203
|
+
}));
|
|
204
|
+
yield result.current(addProps("23"));
|
|
205
|
+
expect(action).toHaveBeenCalledWith("cart/add", addProps("23"));
|
|
206
|
+
expect(emitProductConversion).not.toHaveBeenCalled();
|
|
207
|
+
}));
|
|
208
|
+
it("does not emit when the added variant isn't in the current results", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
209
|
+
const action = jest.fn();
|
|
210
|
+
const emitProductConversion = jest.fn();
|
|
211
|
+
const { result } = renderHook(() => useSearchAnalyticsAddToCart({
|
|
212
|
+
products: cartProducts,
|
|
213
|
+
searchClient: { provider: "algolia" },
|
|
214
|
+
emitProductConversion,
|
|
215
|
+
action,
|
|
216
|
+
}));
|
|
217
|
+
yield result.current(addProps("999"));
|
|
218
|
+
expect(action).toHaveBeenCalledWith("cart/add", addProps("999"));
|
|
219
|
+
expect(emitProductConversion).not.toHaveBeenCalled();
|
|
220
|
+
}));
|
|
221
|
+
it("does not throw and still adds when no emitter is injected", () => {
|
|
222
|
+
const action = jest.fn();
|
|
223
|
+
const { result } = renderHook(() => useSearchAnalyticsAddToCart({
|
|
224
|
+
products: cartProducts,
|
|
225
|
+
searchClient: { provider: "algolia" },
|
|
226
|
+
emitProductConversion: undefined,
|
|
227
|
+
action,
|
|
228
|
+
}));
|
|
229
|
+
expect(() => result.current(addProps("11"))).not.toThrow();
|
|
230
|
+
expect(action).toHaveBeenCalledWith("cart/add", addProps("11"));
|
|
231
|
+
});
|
|
232
|
+
it("keeps a stable callback identity across a new products array", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
233
|
+
const action = jest.fn();
|
|
234
|
+
const emitProductConversion = jest.fn();
|
|
235
|
+
const searchClient = { provider: "algolia" };
|
|
236
|
+
const { result, rerender } = renderHook(({ list }) => useSearchAnalyticsAddToCart({
|
|
237
|
+
products: list,
|
|
238
|
+
searchClient,
|
|
239
|
+
emitProductConversion,
|
|
240
|
+
action,
|
|
241
|
+
}), { initialProps: { list: cartProducts } });
|
|
242
|
+
const first = result.current;
|
|
243
|
+
rerender({
|
|
244
|
+
list: [
|
|
245
|
+
...cartProducts,
|
|
246
|
+
{
|
|
247
|
+
id: "gid://shopify/Product/3",
|
|
248
|
+
objectID: "obj-3",
|
|
249
|
+
variants: [{ id: "gid://shopify/ProductVariant/33" }],
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
});
|
|
253
|
+
expect(result.current).toBe(first);
|
|
254
|
+
// Stable callback still reads the latest list.
|
|
255
|
+
yield result.current(addProps("33"));
|
|
256
|
+
expect(emitProductConversion).toHaveBeenCalledWith(expect.objectContaining({
|
|
257
|
+
product: expect.objectContaining({ objectId: "obj-3", position: 3 }),
|
|
258
|
+
}));
|
|
259
|
+
}));
|
|
260
|
+
});
|
|
261
|
+
describe("useSearchAnalyticsImpression", () => {
|
|
262
|
+
it("returns undefined when not search-backed (ProductCard skips the observer)", () => {
|
|
263
|
+
const { result } = renderHook(() => useSearchAnalyticsImpression({
|
|
264
|
+
searchClient: undefined,
|
|
265
|
+
emitProductImpression: jest.fn(),
|
|
266
|
+
}));
|
|
267
|
+
expect(result.current).toBeUndefined();
|
|
268
|
+
});
|
|
269
|
+
it("returns undefined when no emitter is injected", () => {
|
|
270
|
+
const { result } = renderHook(() => useSearchAnalyticsImpression({
|
|
271
|
+
searchClient: {},
|
|
272
|
+
emitProductImpression: undefined,
|
|
273
|
+
}));
|
|
274
|
+
expect(result.current).toBeUndefined();
|
|
275
|
+
});
|
|
276
|
+
it("returns undefined when disabled, even with a search client and a callable no-op emitter (flag OFF skips the observer)", () => {
|
|
277
|
+
// The real NOOP_EMITTER exposes callable no-ops, not undefined, so a truthy
|
|
278
|
+
// emitter must not be enough to set up the observer — the flag must gate it.
|
|
279
|
+
const { result } = renderHook(() => useSearchAnalyticsImpression({
|
|
280
|
+
searchClient: { provider: "algolia" },
|
|
281
|
+
emitProductImpression: () => { },
|
|
282
|
+
enabled: false,
|
|
283
|
+
}));
|
|
284
|
+
expect(result.current).toBeUndefined();
|
|
285
|
+
});
|
|
286
|
+
it("emits one impression with objectId + productId when a card becomes visible", () => {
|
|
287
|
+
var _a;
|
|
288
|
+
const emitProductImpression = jest.fn();
|
|
289
|
+
const searchClient = { provider: "algolia" };
|
|
290
|
+
const { result } = renderHook(() => useSearchAnalyticsImpression({
|
|
291
|
+
searchClient,
|
|
292
|
+
emitProductImpression,
|
|
293
|
+
surface: "collection",
|
|
294
|
+
}));
|
|
295
|
+
(_a = result.current) === null || _a === void 0 ? void 0 : _a.call(result, { id: "gid://shopify/Product/2", objectID: "obj-2" });
|
|
296
|
+
expect(emitProductImpression).toHaveBeenCalledTimes(1);
|
|
297
|
+
expect(emitProductImpression).toHaveBeenCalledWith({
|
|
298
|
+
client: searchClient,
|
|
299
|
+
surface: "collection",
|
|
300
|
+
product: { productId: "2", objectId: "obj-2" },
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
it("dedupes repeat impressions of the same product", () => {
|
|
304
|
+
var _a, _b;
|
|
305
|
+
const emitProductImpression = jest.fn();
|
|
306
|
+
const { result } = renderHook(() => useSearchAnalyticsImpression({
|
|
307
|
+
searchClient: { provider: "algolia" },
|
|
308
|
+
emitProductImpression,
|
|
309
|
+
}));
|
|
310
|
+
const product = { id: "gid://shopify/Product/2", objectID: "obj-2" };
|
|
311
|
+
(_a = result.current) === null || _a === void 0 ? void 0 : _a.call(result, product);
|
|
312
|
+
(_b = result.current) === null || _b === void 0 ? void 0 : _b.call(result, product);
|
|
313
|
+
expect(emitProductImpression).toHaveBeenCalledTimes(1);
|
|
314
|
+
});
|
|
315
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export * from "./components/hooks/use-infinite-scroll";
|
|
|
10
10
|
export * from "./components/hooks/swr-retry";
|
|
11
11
|
export * from "./components/hooks/use-infinite-wishlist";
|
|
12
12
|
export * from "./components/hooks/use-recommendations";
|
|
13
|
+
export * from "./components/hooks/use-search-analytics-open-product";
|
|
13
14
|
export * from "./components/hooks/use-products";
|
|
14
15
|
export * from "./components/hooks/use-personalized-cluster-feed";
|
|
15
16
|
export * from "./components/hooks/use-order-details";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
|