@decocms/apps-vtex 8.1.0-next.0 → 8.1.0-next.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.
Files changed (139) hide show
  1. package/package.json +7 -52
  2. package/src/index.ts +10 -28
  3. package/src/utils/constants.ts +2 -56
  4. package/src/utils/cookieSanitizer.ts +59 -59
  5. package/src/utils/operationRouter.ts +75 -65
  6. package/src/utils/types.ts +709 -1557
  7. package/src/vtexClient.ts +354 -343
  8. package/src/README.md +0 -6
  9. package/src/__conformance__/upstream.test.ts +0 -158
  10. package/src/__tests__/client-segment-cookie.test.ts +0 -255
  11. package/src/__tests__/client-set-cookie-forward.test.ts +0 -257
  12. package/src/__tests__/schemas.test.ts +0 -71
  13. package/src/actions/address.ts +0 -250
  14. package/src/actions/analytics/sendEvent.ts +0 -86
  15. package/src/actions/auth.ts +0 -333
  16. package/src/actions/checkout.ts +0 -659
  17. package/src/actions/index.ts +0 -11
  18. package/src/actions/masterData.ts +0 -168
  19. package/src/actions/misc.ts +0 -188
  20. package/src/actions/newsletter.ts +0 -105
  21. package/src/actions/orders.ts +0 -34
  22. package/src/actions/profile.ts +0 -201
  23. package/src/actions/session.ts +0 -85
  24. package/src/actions/trigger.ts +0 -43
  25. package/src/actions/wishlist.ts +0 -114
  26. package/src/client.ts +0 -724
  27. package/src/commerceLoaders.ts +0 -267
  28. package/src/hooks/__tests__/createCart.render.test.ts +0 -114
  29. package/src/hooks/__tests__/createCart.test.ts +0 -92
  30. package/src/hooks/__tests__/createUseCart.test.ts +0 -184
  31. package/src/hooks/__tests__/createUseUser.test.ts +0 -48
  32. package/src/hooks/__tests__/createUseWishlist.test.ts +0 -87
  33. package/src/hooks/cartQuery.ts +0 -204
  34. package/src/hooks/createCart.ts +0 -427
  35. package/src/hooks/createUseCart.ts +0 -360
  36. package/src/hooks/createUseUser.ts +0 -153
  37. package/src/hooks/createUseWishlist.ts +0 -242
  38. package/src/hooks/index.ts +0 -25
  39. package/src/hooks/useAutocomplete.ts +0 -83
  40. package/src/hooks/useCart.ts +0 -243
  41. package/src/hooks/useUser.ts +0 -78
  42. package/src/hooks/useWishlist.ts +0 -119
  43. package/src/invoke.ts +0 -235
  44. package/src/loaders/ProductDetailsPage.ts +0 -1
  45. package/src/loaders/ProductList.ts +0 -1
  46. package/src/loaders/ProductListingPage.ts +0 -1
  47. package/src/loaders/__tests__/legacyProductList.test.ts +0 -157
  48. package/src/loaders/address.ts +0 -115
  49. package/src/loaders/autocomplete.ts +0 -56
  50. package/src/loaders/brands.ts +0 -44
  51. package/src/loaders/cart/attachments.ts +0 -53
  52. package/src/loaders/cart/full.ts +0 -67
  53. package/src/loaders/cart/gifts.ts +0 -43
  54. package/src/loaders/cart/orderFormId.ts +0 -31
  55. package/src/loaders/cart/shipping.test.ts +0 -125
  56. package/src/loaders/cart/shipping.ts +0 -136
  57. package/src/loaders/cart/summary.ts +0 -36
  58. package/src/loaders/cart.ts +0 -52
  59. package/src/loaders/catalog.ts +0 -163
  60. package/src/loaders/collections.ts +0 -55
  61. package/src/loaders/index.ts +0 -20
  62. package/src/loaders/intelligentSearch/__tests__/productListingPage.test.ts +0 -51
  63. package/src/loaders/intelligentSearch/productDetailsPage.ts +0 -111
  64. package/src/loaders/intelligentSearch/productList.ts +0 -170
  65. package/src/loaders/intelligentSearch/productListingPage.ts +0 -525
  66. package/src/loaders/intelligentSearch/suggestions.ts +0 -44
  67. package/src/loaders/legacy/productDetailsPage.ts +0 -1
  68. package/src/loaders/legacy/productList.ts +0 -1
  69. package/src/loaders/legacy/relatedProductsLoader.ts +0 -79
  70. package/src/loaders/legacy.ts +0 -710
  71. package/src/loaders/logistics.ts +0 -111
  72. package/src/loaders/minicart.ts +0 -78
  73. package/src/loaders/navbar.ts +0 -26
  74. package/src/loaders/orders.ts +0 -92
  75. package/src/loaders/pageType.ts +0 -68
  76. package/src/loaders/paths/PDPDefaultPath.ts +0 -51
  77. package/src/loaders/payment.ts +0 -97
  78. package/src/loaders/productListFull.ts +0 -157
  79. package/src/loaders/profile.ts +0 -138
  80. package/src/loaders/promotion.ts +0 -30
  81. package/src/loaders/search.ts +0 -124
  82. package/src/loaders/session.ts +0 -83
  83. package/src/loaders/user.ts +0 -87
  84. package/src/loaders/wishlist.ts +0 -89
  85. package/src/loaders/wishlistProducts.ts +0 -69
  86. package/src/loaders/workflow/products.ts +0 -62
  87. package/src/loaders/workflow.ts +0 -316
  88. package/src/logo.png +0 -0
  89. package/src/manifest.gen.ts +0 -89
  90. package/src/middleware.cacheHeaders.test.ts +0 -44
  91. package/src/middleware.ts +0 -240
  92. package/src/mod.ts +0 -173
  93. package/src/registry.ts +0 -9
  94. package/src/schemas.gen.ts +0 -3191
  95. package/src/schemas.ts +0 -30
  96. package/src/types.ts +0 -248
  97. package/src/utils/__tests__/cartProjection.test.ts +0 -118
  98. package/src/utils/__tests__/cookieSanitizer.test.ts +0 -162
  99. package/src/utils/__tests__/fetch.test.ts +0 -80
  100. package/src/utils/__tests__/fetchCache.test.ts +0 -155
  101. package/src/utils/__tests__/instrumentedFetch.test.ts +0 -158
  102. package/src/utils/__tests__/intelligentSearch.test.ts +0 -88
  103. package/src/utils/__tests__/minicart.test.ts +0 -184
  104. package/src/utils/__tests__/operationRouter.test.ts +0 -227
  105. package/src/utils/__tests__/resilience.test.ts +0 -205
  106. package/src/utils/__tests__/resourceRange.test.ts +0 -32
  107. package/src/utils/__tests__/simulationCache.test.ts +0 -40
  108. package/src/utils/__tests__/sitemap.test.ts +0 -185
  109. package/src/utils/__tests__/slugify.test.ts +0 -31
  110. package/src/utils/__tests__/storefrontBaseUrl.test.ts +0 -61
  111. package/src/utils/__tests__/transform.test.ts +0 -1111
  112. package/src/utils/accountLoaders.ts +0 -203
  113. package/src/utils/authHelpers.ts +0 -85
  114. package/src/utils/batch.ts +0 -18
  115. package/src/utils/buildOfferShelf.test.ts +0 -59
  116. package/src/utils/cartProjection.ts +0 -96
  117. package/src/utils/cookies.ts +0 -167
  118. package/src/utils/enrichment.ts +0 -556
  119. package/src/utils/fetch.ts +0 -111
  120. package/src/utils/fetchCache.ts +0 -55
  121. package/src/utils/index.ts +0 -19
  122. package/src/utils/instrumentedFetch.ts +0 -98
  123. package/src/utils/intelligentSearch.ts +0 -96
  124. package/src/utils/legacy.ts +0 -139
  125. package/src/utils/minicart.ts +0 -139
  126. package/src/utils/pickAndOmit.ts +0 -25
  127. package/src/utils/proxy.ts +0 -495
  128. package/src/utils/resilience.ts +0 -352
  129. package/src/utils/resourceRange.ts +0 -10
  130. package/src/utils/segment.ts +0 -152
  131. package/src/utils/similars.ts +0 -35
  132. package/src/utils/simulationCache.ts +0 -73
  133. package/src/utils/sitemap.ts +0 -283
  134. package/src/utils/slugCache.ts +0 -28
  135. package/src/utils/slugify.ts +0 -13
  136. package/src/utils/transform.ts +0 -1691
  137. package/src/utils/vtexId.ts +0 -151
  138. package/src/vtexClient.test.ts +0 -204
  139. package/tsconfig.json +0 -7
@@ -1,267 +0,0 @@
1
- /**
2
- * Standard VTEX commerce loader map factory for CMS block resolution.
3
- *
4
- * Wraps all VTEX loaders with createCachedLoader, applies universal
5
- * workarounds (slug fallback, IS sort sanitization, map=productClusterIds),
6
- * and registers both `.ts` and `.ts`-less aliases.
7
- *
8
- * Sites call `createVtexCommerceLoaders()` instead of manually wiring ~30
9
- * individual loader entries. Site-specific loaders are merged via `extra`.
10
- */
11
-
12
- import { createCachedLoader } from "@decocms/blocks/sdk/cachedLoader";
13
- import type { CacheProfileName } from "@decocms/blocks/sdk/cacheHeaders";
14
- import { getCategoryTree } from "./loaders/catalog";
15
- import vtexProductDetailsPage from "./loaders/intelligentSearch/productDetailsPage";
16
- import vtexProductListShelf from "./loaders/intelligentSearch/productList";
17
- import vtexProductListingPage from "./loaders/intelligentSearch/productListingPage";
18
- import vtexSuggestions from "./loaders/intelligentSearch/suggestions";
19
- import vtexRelatedProducts from "./loaders/legacy/relatedProductsLoader";
20
- import vtexProductList from "./loaders/productListFull";
21
- import vtexWorkflowProducts from "./loaders/workflow/products";
22
- import { registerVtexSchemas } from "./schemas";
23
- import { VALID_IS_SORTS } from "./utils/intelligentSearch";
24
-
25
- export type CommerceLoaderFn = (props: any) => Promise<any>;
26
-
27
- export interface VtexCommerceLoadersOptions {
28
- /** Override cache profiles per loader type. */
29
- cacheProfiles?: {
30
- listing?: CacheProfileName;
31
- product?: CacheProfileName;
32
- search?: CacheProfileName;
33
- static?: CacheProfileName;
34
- };
35
- /** Additional loaders to merge into the map (site-specific). */
36
- extra?: Record<string, CommerceLoaderFn>;
37
- }
38
-
39
- /**
40
- * Bridge __pagePath → slug when CMS doesn't set slug explicitly.
41
- * VTEX PDP pages receive __pagePath (e.g. "/produto-slug/p") but the
42
- * inline loader only reads the `slug` field.
43
- */
44
- function pdpWithSlugFallback(props: any): Promise<any> {
45
- if ((!props.slug || props.slug.length === 0) && props.__pagePath) {
46
- props = { ...props, slug: props.__pagePath };
47
- }
48
- return vtexProductDetailsPage(props);
49
- }
50
-
51
- /**
52
- * Extract collection name from PLP product data.
53
- * Products carry cluster info in additionalProperty with name="cluster".
54
- */
55
- function extractCollectionName(result: any, collectionId: string): string | null {
56
- if (!result?.products?.length) return null;
57
- for (const product of result.products) {
58
- const props = product.additionalProperty || product.isVariantOf?.additionalProperty || [];
59
- for (const prop of props) {
60
- if (prop.name === "cluster" && prop.propertyID === collectionId) {
61
- return prop.value || null;
62
- }
63
- }
64
- }
65
- return null;
66
- }
67
-
68
- /**
69
- * Returns the standard VTEX commerce loader map for CMS resolution.
70
- *
71
- * Includes all Intelligent Search, legacy, category tree, and navbar loaders
72
- * with SWR caching. Also registers `.ts`-less aliases for invoke compatibility.
73
- *
74
- * @example
75
- * ```ts
76
- * import { createVtexCommerceLoaders } from "@decocms/apps/vtex/commerceLoaders";
77
- *
78
- * const COMMERCE_LOADERS = {
79
- * ...createVtexCommerceLoaders(),
80
- * // site-specific only:
81
- * "site/loaders/myCustomLoader": async (props) => { ... },
82
- * };
83
- * registerCommerceLoaders(COMMERCE_LOADERS);
84
- * ```
85
- */
86
- export function createVtexCommerceLoaders(
87
- options?: VtexCommerceLoadersOptions,
88
- ): Record<string, CommerceLoaderFn> {
89
- // Real props schemas for the admin meta. The stub registration that
90
- // registerCommerceLoaders() later performs for this returned map never
91
- // overwrites these (real-over-stub precedence in the schema registry).
92
- registerVtexSchemas();
93
-
94
- const profiles = {
95
- listing: options?.cacheProfiles?.listing ?? "listing",
96
- product: options?.cacheProfiles?.product ?? "product",
97
- search: options?.cacheProfiles?.search ?? "search",
98
- static: options?.cacheProfiles?.static ?? "static",
99
- };
100
-
101
- const _cachedProductList = createCachedLoader(
102
- "vtex/productList",
103
- vtexProductList,
104
- profiles.listing,
105
- );
106
- const cachedProductListShelf = createCachedLoader(
107
- "vtex/productListShelf",
108
- vtexProductListShelf,
109
- profiles.listing,
110
- );
111
- const cachedPDP = createCachedLoader(
112
- "vtex/productDetailsPage",
113
- pdpWithSlugFallback,
114
- profiles.product,
115
- );
116
- const _cachedPLP = createCachedLoader(
117
- "vtex/productListingPage",
118
- vtexProductListingPage,
119
- profiles.listing,
120
- );
121
- const cachedSuggestions = createCachedLoader(
122
- "vtex/suggestions",
123
- vtexSuggestions,
124
- profiles.search,
125
- );
126
- const cachedRelatedProducts = createCachedLoader(
127
- "vtex/relatedProducts",
128
- vtexRelatedProducts,
129
- profiles.product,
130
- );
131
- const cachedWorkflowProducts = createCachedLoader(
132
- "vtex/workflowProducts",
133
- vtexWorkflowProducts,
134
- profiles.listing,
135
- );
136
-
137
- /**
138
- * PLP wrapper: handles `map=productClusterIds` legacy URLs and sanitizes
139
- * IS sort parameters that would cause VTEX API 400 errors.
140
- */
141
- const cachedPLP: CommerceLoaderFn = async (props) => {
142
- if (props.__pageUrl && !props.selectedFacets?.length) {
143
- try {
144
- const pageUrl = new URL(props.__pageUrl, "https://localhost");
145
- const mapParam = pageUrl.searchParams.get("map");
146
- if (mapParam && props.__pagePath) {
147
- const segments = props.__pagePath.split("/").filter(Boolean);
148
- const mapValues = mapParam.split(",");
149
- const facets: Array<{ key: string; value: string }> = [];
150
- for (let i = 0; i < Math.min(segments.length, mapValues.length); i++) {
151
- const key = mapValues[i].trim();
152
- const value = decodeURIComponent(segments[i]);
153
- if (key && value) facets.push({ key, value });
154
- }
155
- if (facets.length) {
156
- const rawSort = pageUrl.searchParams.get("sort") ?? "";
157
- const cleanSort = VALID_IS_SORTS.has(rawSort) ? rawSort : "";
158
-
159
- if (rawSort !== cleanSort) {
160
- if (cleanSort) {
161
- pageUrl.searchParams.set("sort", cleanSort);
162
- } else {
163
- pageUrl.searchParams.delete("sort");
164
- }
165
- }
166
-
167
- const result = await _cachedPLP({
168
- ...props,
169
- selectedFacets: facets,
170
- sort: cleanSort || undefined,
171
- __pageUrl: pageUrl.toString(),
172
- });
173
-
174
- const clusterFacet = facets.find((f) => f.key === "productClusterIds");
175
- if (result && clusterFacet) {
176
- const collectionName = extractCollectionName(result, clusterFacet.value);
177
- if (collectionName) {
178
- result.breadcrumb = {
179
- "@type": "BreadcrumbList",
180
- itemListElement: [
181
- {
182
- "@type": "ListItem",
183
- name: collectionName,
184
- item: props.__pagePath || "/",
185
- position: 1,
186
- },
187
- ],
188
- numberOfItems: 1,
189
- };
190
- result.seo = { ...result.seo, title: collectionName };
191
- }
192
- }
193
- return result;
194
- }
195
- }
196
- } catch (e) {
197
- console.error("[PLP] Error parsing map param:", e);
198
- }
199
- }
200
- return _cachedPLP(props);
201
- };
202
-
203
- /**
204
- * Related products wrapper: extracts slug from __pagePath when the CMS
205
- * requestToParam stub returns null (standard in TanStack sites).
206
- */
207
- const relatedWithSlugFallback: CommerceLoaderFn = (props) => {
208
- if (!props.slug && props.__pagePath) {
209
- const path = props.__pagePath.replace(/\/p$/, "").replace(/^\//, "");
210
- props = { ...props, slug: path };
211
- }
212
- return cachedRelatedProducts(props);
213
- };
214
-
215
- const loaders: Record<string, CommerceLoaderFn> = {
216
- // Intelligent Search loaders
217
- "vtex/loaders/intelligentSearch/productListingPage.ts": cachedPLP,
218
- "vtex/loaders/intelligentSearch/productList.ts": cachedProductListShelf,
219
- "vtex/loaders/intelligentSearch/productDetailsPage.ts": cachedPDP,
220
- "vtex/loaders/intelligentSearch/suggestions.ts": cachedSuggestions,
221
- // Legacy loaders (map to same cached functions)
222
- "vtex/loaders/legacy/productDetailsPage.ts": cachedPDP,
223
- "vtex/loaders/legacy/productList.ts": cachedProductListShelf,
224
- "vtex/loaders/legacy/relatedProductsLoader.ts": relatedWithSlugFallback,
225
- // Workflow
226
- "vtex/loaders/workflow/products.ts": cachedWorkflowProducts,
227
- // Top-level aliases (used by some CMS block configs)
228
- "vtex/loaders/ProductList.ts": cachedProductListShelf,
229
- "vtex/loaders/ProductDetailsPage.ts": cachedPDP,
230
- "vtex/loaders/ProductListingPage.ts": cachedPLP,
231
- // Category tree
232
- "vtex/loaders/categories/tree": (props: any) => getCategoryTree(props?.categoryLevels ?? 3),
233
- // Commerce passthrough loaders
234
- "commerce/loaders/navbar.ts": async (props: any) => props.items ?? [],
235
- "commerce/loaders/product/extensions/detailsPage.ts": async (props: any) => {
236
- const data = props.data;
237
- if (data?.product) return data;
238
- return cachedPDP({ __pagePath: props.__pagePath });
239
- },
240
- // requestToParam stub — unresolvable in TanStack, pdpWithSlugFallback bridges it
241
- "website/functions/requestToParam.ts": async () => null,
242
- };
243
-
244
- // Register .ts-less aliases for invoke compatibility
245
- const withAliases: Record<string, CommerceLoaderFn> = { ...loaders };
246
- for (const key of Object.keys(loaders)) {
247
- if (key.endsWith(".ts")) {
248
- withAliases[key.slice(0, -3)] = loaders[key];
249
- }
250
- }
251
-
252
- if (options?.extra) {
253
- Object.assign(withAliases, options.extra);
254
- }
255
-
256
- return withAliases;
257
- }
258
-
259
- /**
260
- * Exposes the cached PDP loader for site-specific section loaders that need
261
- * to call it directly (e.g. ProductDescription fallback).
262
- *
263
- * Returns a new instance each call — sites should cache the reference.
264
- */
265
- export function createCachedPDPLoader(profile: CacheProfileName = "product"): CommerceLoaderFn {
266
- return createCachedLoader("vtex/productDetailsPage", pdpWithSlugFallback, profile);
267
- }
@@ -1,114 +0,0 @@
1
- // @vitest-environment jsdom
2
- /**
3
- * Render-based tests for the `createCart` factory (Cart v2).
4
- *
5
- * The factory-shape tests in `createCart.test.ts` run in node and never render.
6
- * These exercise the actual hook behaviour that needs a React renderer:
7
- * `useAddToCart().add()` returning the server's projected payload (so a caller
8
- * can drive a toast without a second fetch) and the optimistic badge bump.
9
- *
10
- * No `@testing-library/react` in the tree — we hand-roll a tiny render harness
11
- * on `react-dom/client` (jsdom env, declared in the docblock above).
12
- */
13
-
14
- import type { CartSummaryWithItems } from "@decocms/apps-commerce/types";
15
- import { act, createElement } from "react";
16
- import { createRoot } from "react-dom/client";
17
- import { describe, expect, it } from "vitest";
18
- import { type CreateCartInvoke, createCart } from "../createCart";
19
-
20
- // React's act() requires this flag when driving updates manually.
21
- (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
22
-
23
- function renderHook<T>(useHook: () => T) {
24
- const ref = { current: undefined as unknown as T };
25
- function Probe() {
26
- ref.current = useHook();
27
- return null;
28
- }
29
- const container = document.createElement("div");
30
- const root = createRoot(container);
31
- act(() => {
32
- root.render(createElement(Probe));
33
- });
34
- return { ref, unmount: () => act(() => root.unmount()) };
35
- }
36
-
37
- /** Fake invoke whose addItemsToCartV2 returns a `summary+items` projection. */
38
- function makeInvoke(): CreateCartInvoke {
39
- const projected: CartSummaryWithItems = {
40
- orderFormId: "of-1",
41
- totalItems: 1,
42
- total: 100,
43
- items: [
44
- {
45
- item_id: "sku-1",
46
- item_name: "Camiseta",
47
- item_variant: "P Azul",
48
- image: "https://img.example/x.jpg",
49
- price: 100,
50
- quantity: 1,
51
- },
52
- ],
53
- };
54
- return {
55
- vtex: {
56
- actions: {
57
- getOrCreateCartV2: async () => ({ orderFormId: "of-1", totalItems: 0, total: 0 }),
58
- addItemsToCartV2: async () => projected as never,
59
- updateCartItemsV2: async () => ({ ok: true }) as never,
60
- addCouponToCartV2: async () => ({ ok: true }) as never,
61
- },
62
- loaders: {
63
- cart: {
64
- summary: async () => ({ orderFormId: "of-1", totalItems: 0, total: 0 }),
65
- full: async () => ({ original: null }) as never,
66
- shipping: async () => ({ postalCode: "00000-000", options: [] }),
67
- gifts: async () => ({ orderFormId: "of-1", selectableGifts: [], ratesAndBenefits: null }),
68
- attachments: async () => ({
69
- orderFormId: "of-1",
70
- itemIndex: 0,
71
- attachments: [],
72
- attachmentOfferings: [],
73
- }),
74
- },
75
- },
76
- },
77
- };
78
- }
79
-
80
- describe("createCart — useAddToCart.add()", () => {
81
- it("returns the projected payload for a toast without a second fetch", async () => {
82
- const cart = createCart({ invoke: makeInvoke() });
83
- const { ref, unmount } = renderHook(() => cart.useAddToCart());
84
-
85
- let result: unknown;
86
- await act(async () => {
87
- result = await ref.current.add({ id: "sku-1", seller: "1" });
88
- });
89
-
90
- expect(result).toMatchObject({
91
- totalItems: 1,
92
- total: 100,
93
- items: [{ item_name: "Camiseta", item_variant: "P Azul", price: 100 }],
94
- });
95
- unmount();
96
- });
97
-
98
- it("reconciles the summary badge from the projected response", async () => {
99
- const cart = createCart({ invoke: makeInvoke() });
100
- const add = renderHook(() => cart.useAddToCart());
101
- const badge = renderHook(() => cart.useCartSummary());
102
-
103
- expect(badge.ref.current.totalItems).toBe(0);
104
-
105
- await act(async () => {
106
- await add.ref.current.add({ id: "sku-1", seller: "1" });
107
- });
108
-
109
- // Reconciled from the server projection (1), not just the optimistic bump.
110
- expect(badge.ref.current.totalItems).toBe(1);
111
- add.unmount();
112
- badge.unmount();
113
- });
114
- });
@@ -1,92 +0,0 @@
1
- /**
2
- * Tests for the `createCart` factory (Cart v2).
3
- *
4
- * Like `createUseCart`, hook render semantics need a React renderer that
5
- * apps-start does not pull in, so we cover the factory's shape and instance
6
- * isolation. The optimistic + reconciliation behaviour is exercised by the
7
- * site-level smoke test.
8
- */
9
-
10
- import { describe, expect, it, vi } from "vitest";
11
- import { type CreateCartInvoke, createCart } from "../createCart";
12
-
13
- function makeInvoke(): CreateCartInvoke {
14
- const summary = async () => ({ orderFormId: "of-1", totalItems: 0, total: 0 });
15
- const anyNoop = async () => ({ ok: true }) as never;
16
- return {
17
- vtex: {
18
- actions: {
19
- getOrCreateCartV2: async () => ({ orderFormId: "of-1", totalItems: 0, total: 0 }),
20
- addItemsToCartV2: anyNoop,
21
- updateCartItemsV2: anyNoop,
22
- addCouponToCartV2: anyNoop,
23
- },
24
- loaders: {
25
- cart: {
26
- summary,
27
- full: async () => ({
28
- original: null,
29
- storefront: {
30
- items: [],
31
- total: 0,
32
- subtotal: 0,
33
- discounts: 0,
34
- locale: "pt-BR",
35
- currency: "BRL",
36
- freeShippingTarget: 0,
37
- checkoutHref: "/checkout",
38
- },
39
- }),
40
- shipping: async () => ({ postalCode: "00000-000", options: [] }),
41
- gifts: async () => ({ orderFormId: "of-1", selectableGifts: [], ratesAndBenefits: null }),
42
- attachments: async () => ({
43
- orderFormId: "of-1",
44
- itemIndex: 0,
45
- attachments: [],
46
- attachmentOfferings: [],
47
- }),
48
- },
49
- },
50
- },
51
- };
52
- }
53
-
54
- describe("createCart — factory shape", () => {
55
- it("returns the full granular hook set", () => {
56
- const cart = createCart({ invoke: makeInvoke() });
57
- expect(typeof cart.useCart).toBe("function");
58
- expect(typeof cart.useCartSummary).toBe("function");
59
- expect(typeof cart.useAddToCart).toBe("function");
60
- expect(typeof cart.useShipping).toBe("function");
61
- expect(typeof cart.useGifts).toBe("function");
62
- expect(typeof cart.useAttachments).toBe("function");
63
- expect(typeof cart.resetCart).toBe("function");
64
- });
65
-
66
- it("two factory calls produce independent hook identities", () => {
67
- const a = createCart({ invoke: makeInvoke() });
68
- const b = createCart({ invoke: makeInvoke() });
69
- expect(a.useCart).not.toBe(b.useCart);
70
- expect(a.useAddToCart).not.toBe(b.useAddToCart);
71
- expect(a.resetCart).not.toBe(b.resetCart);
72
- });
73
-
74
- it("accepts custom cookie name / max-age without throwing", () => {
75
- expect(() =>
76
- createCart({
77
- invoke: makeInvoke(),
78
- orderFormCookieName: "custom_of",
79
- orderFormCookieMaxAge: 3600,
80
- }),
81
- ).not.toThrow();
82
- });
83
-
84
- it("does not call any invoke method at construction time (lazy)", () => {
85
- const invoke = makeInvoke();
86
- const getOrCreate = vi.spyOn(invoke.vtex.actions, "getOrCreateCartV2");
87
- const summary = vi.spyOn(invoke.vtex.loaders.cart, "summary");
88
- createCart({ invoke });
89
- expect(getOrCreate).not.toHaveBeenCalled();
90
- expect(summary).not.toHaveBeenCalled();
91
- });
92
- });
@@ -1,184 +0,0 @@
1
- /**
2
- * Tests for the `createUseCart` factory.
3
- *
4
- * The hook itself depends on React (useState/useEffect), and apps-start
5
- * does not pull in @testing-library/react. So we test the parts of the
6
- * factory that don't require a React renderer: shape, isolation, and the
7
- * pure `itemToAnalyticsItem` helper. Hook semantics are exercised by the
8
- * site-level integration smoke test (the template has shipped to two
9
- * production sites).
10
- */
11
-
12
- import { describe, expect, it } from "vitest";
13
- import { type CreateUseCartInvoke, createUseCart } from "../createUseCart";
14
-
15
- function makeInvoke(): CreateUseCartInvoke {
16
- const noop = async () => ({ orderFormId: "of-1", items: [] }) as never;
17
- return {
18
- vtex: {
19
- actions: {
20
- getOrCreateCart: noop,
21
- addItemsToCart: noop,
22
- updateCartItems: noop,
23
- addCouponToCart: noop,
24
- updateOrderFormAttachment: noop,
25
- simulateCart: noop,
26
- },
27
- },
28
- };
29
- }
30
-
31
- describe("createUseCart — factory shape", () => {
32
- it("returns useCart, resetCart, itemToAnalyticsItem", () => {
33
- const cart = createUseCart({ invoke: makeInvoke() });
34
- expect(typeof cart.useCart).toBe("function");
35
- expect(typeof cart.resetCart).toBe("function");
36
- expect(typeof cart.itemToAnalyticsItem).toBe("function");
37
- });
38
-
39
- it("two factory calls produce independent itemToAnalyticsItem references", () => {
40
- const a = createUseCart({ invoke: makeInvoke() });
41
- const b = createUseCart({ invoke: makeInvoke() });
42
- // Different closures => different function identities.
43
- expect(a.itemToAnalyticsItem).not.toBe(b.itemToAnalyticsItem);
44
- expect(a.useCart).not.toBe(b.useCart);
45
- expect(a.resetCart).not.toBe(b.resetCart);
46
- });
47
-
48
- it("accepts custom orderFormCookieName / maxAge without throwing", () => {
49
- expect(() =>
50
- createUseCart({
51
- invoke: makeInvoke(),
52
- orderFormCookieName: "custom_of_id",
53
- orderFormCookieMaxAge: 3600,
54
- }),
55
- ).not.toThrow();
56
- });
57
- });
58
-
59
- describe("createUseCart — itemToAnalyticsItem", () => {
60
- const { itemToAnalyticsItem } = createUseCart({ invoke: makeInvoke() });
61
-
62
- it("maps the canonical fields", () => {
63
- const out = itemToAnalyticsItem(
64
- {
65
- id: "sku-1",
66
- productId: "prod-1",
67
- name: "Widget",
68
- skuName: "Widget Blue",
69
- sellingPrice: 1990,
70
- price: 2490,
71
- listPrice: 2490,
72
- quantity: 2,
73
- seller: "1",
74
- additionalInfo: { brandName: "Acme" },
75
- } as never,
76
- 0,
77
- );
78
- expect(out).toEqual({
79
- item_id: "prod-1",
80
- item_group_id: "prod-1",
81
- item_name: "Widget",
82
- item_variant: "Widget Blue",
83
- item_brand: "Acme",
84
- price: 19.9,
85
- discount: 5,
86
- quantity: 2,
87
- coupon: undefined,
88
- affiliation: "1",
89
- index: 0,
90
- });
91
- });
92
-
93
- it("falls back to skuName when name is missing", () => {
94
- const out = itemToAnalyticsItem(
95
- {
96
- productId: "p",
97
- skuName: "Fallback",
98
- sellingPrice: 100,
99
- listPrice: 100,
100
- quantity: 1,
101
- seller: "1",
102
- } as never,
103
- 3,
104
- );
105
- expect(out.item_name).toBe("Fallback");
106
- expect(out.index).toBe(3);
107
- });
108
-
109
- it("uses price when sellingPrice is missing", () => {
110
- const out = itemToAnalyticsItem(
111
- {
112
- productId: "p",
113
- name: "n",
114
- price: 1000,
115
- listPrice: 1000,
116
- quantity: 1,
117
- seller: "1",
118
- } as never,
119
- 0,
120
- );
121
- expect(out.price).toBe(10);
122
- });
123
-
124
- it("computes zero discount when listPrice equals sellingPrice", () => {
125
- const out = itemToAnalyticsItem(
126
- {
127
- productId: "p",
128
- name: "n",
129
- sellingPrice: 500,
130
- listPrice: 500,
131
- quantity: 1,
132
- seller: "1",
133
- } as never,
134
- 0,
135
- );
136
- expect(out.discount).toBe(0);
137
- });
138
-
139
- it("rounds discount to 2 decimal places", () => {
140
- const out = itemToAnalyticsItem(
141
- {
142
- productId: "p",
143
- name: "n",
144
- sellingPrice: 1000,
145
- listPrice: 1333,
146
- quantity: 1,
147
- seller: "1",
148
- } as never,
149
- 0,
150
- );
151
- expect(out.discount).toBe(3.33);
152
- });
153
-
154
- it("preserves coupon when supplied", () => {
155
- const out = itemToAnalyticsItem(
156
- {
157
- productId: "p",
158
- name: "n",
159
- sellingPrice: 100,
160
- listPrice: 100,
161
- quantity: 1,
162
- seller: "1",
163
- coupon: "SAVE10",
164
- } as never,
165
- 0,
166
- );
167
- expect(out.coupon).toBe("SAVE10");
168
- });
169
-
170
- it("uses empty string brand when additionalInfo is missing", () => {
171
- const out = itemToAnalyticsItem(
172
- {
173
- productId: "p",
174
- name: "n",
175
- sellingPrice: 100,
176
- listPrice: 100,
177
- quantity: 1,
178
- seller: "1",
179
- } as never,
180
- 0,
181
- );
182
- expect(out.item_brand).toBe("");
183
- });
184
- });
@@ -1,48 +0,0 @@
1
- /**
2
- * Tests for the `createUseUser` factory.
3
- *
4
- * Same approach as `createUseCart.test.ts`: apps-start does not pull
5
- * @testing-library/react, so we test factory shape and isolation. Hook
6
- * semantics are covered by site-level smoke tests.
7
- */
8
-
9
- import { describe, expect, it } from "vitest";
10
- import { type CreateUseUserInvoke, createUseUser } from "../createUseUser";
11
-
12
- function makeInvoke(): CreateUseUserInvoke {
13
- return {
14
- vtex: {
15
- loaders: {
16
- user: async () => null,
17
- },
18
- },
19
- };
20
- }
21
-
22
- describe("createUseUser — factory shape", () => {
23
- it("returns useUser, resetUser", () => {
24
- const u = createUseUser({ invoke: makeInvoke() });
25
- expect(typeof u.useUser).toBe("function");
26
- expect(typeof u.resetUser).toBe("function");
27
- });
28
-
29
- it("two factory calls produce independent function references", () => {
30
- const a = createUseUser({ invoke: makeInvoke() });
31
- const b = createUseUser({ invoke: makeInvoke() });
32
- expect(a.useUser).not.toBe(b.useUser);
33
- expect(a.resetUser).not.toBe(b.resetUser);
34
- });
35
-
36
- it("does not throw at construction time even with a failing invoke", () => {
37
- const invoke: CreateUseUserInvoke = {
38
- vtex: {
39
- loaders: {
40
- user: async () => {
41
- throw new Error("boom");
42
- },
43
- },
44
- },
45
- };
46
- expect(() => createUseUser({ invoke })).not.toThrow();
47
- });
48
- });