@decocms/apps-vtex 8.0.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 (133) hide show
  1. package/package.json +7 -52
  2. package/src/index.ts +12 -15
  3. package/src/utils/constants.ts +2 -56
  4. package/src/utils/cookieSanitizer.ts +59 -59
  5. package/src/utils/operationRouter.ts +76 -72
  6. package/src/utils/types.ts +709 -1557
  7. package/src/vtexClient.ts +444 -0
  8. package/src/README.md +0 -6
  9. package/src/__tests__/client-segment-cookie.test.ts +0 -255
  10. package/src/__tests__/client-set-cookie-forward.test.ts +0 -257
  11. package/src/__tests__/schemas.test.ts +0 -71
  12. package/src/actions/address.ts +0 -250
  13. package/src/actions/analytics/sendEvent.ts +0 -86
  14. package/src/actions/auth.ts +0 -333
  15. package/src/actions/checkout.ts +0 -659
  16. package/src/actions/index.ts +0 -11
  17. package/src/actions/masterData.ts +0 -168
  18. package/src/actions/misc.ts +0 -188
  19. package/src/actions/newsletter.ts +0 -105
  20. package/src/actions/orders.ts +0 -34
  21. package/src/actions/profile.ts +0 -201
  22. package/src/actions/session.ts +0 -85
  23. package/src/actions/trigger.ts +0 -43
  24. package/src/actions/wishlist.ts +0 -114
  25. package/src/client.ts +0 -667
  26. package/src/commerceLoaders.ts +0 -267
  27. package/src/hooks/__tests__/createCart.render.test.ts +0 -114
  28. package/src/hooks/__tests__/createCart.test.ts +0 -92
  29. package/src/hooks/__tests__/createUseCart.test.ts +0 -184
  30. package/src/hooks/__tests__/createUseUser.test.ts +0 -48
  31. package/src/hooks/__tests__/createUseWishlist.test.ts +0 -87
  32. package/src/hooks/cartQuery.ts +0 -204
  33. package/src/hooks/createCart.ts +0 -427
  34. package/src/hooks/createUseCart.ts +0 -360
  35. package/src/hooks/createUseUser.ts +0 -153
  36. package/src/hooks/createUseWishlist.ts +0 -242
  37. package/src/hooks/index.ts +0 -25
  38. package/src/hooks/useAutocomplete.ts +0 -83
  39. package/src/hooks/useCart.ts +0 -243
  40. package/src/hooks/useUser.ts +0 -78
  41. package/src/hooks/useWishlist.ts +0 -119
  42. package/src/invoke.ts +0 -235
  43. package/src/loaders/ProductDetailsPage.ts +0 -1
  44. package/src/loaders/ProductList.ts +0 -1
  45. package/src/loaders/ProductListingPage.ts +0 -1
  46. package/src/loaders/address.ts +0 -115
  47. package/src/loaders/autocomplete.ts +0 -58
  48. package/src/loaders/brands.ts +0 -44
  49. package/src/loaders/cart/attachments.ts +0 -53
  50. package/src/loaders/cart/full.ts +0 -67
  51. package/src/loaders/cart/gifts.ts +0 -43
  52. package/src/loaders/cart/orderFormId.ts +0 -31
  53. package/src/loaders/cart/shipping.test.ts +0 -125
  54. package/src/loaders/cart/shipping.ts +0 -136
  55. package/src/loaders/cart/summary.ts +0 -36
  56. package/src/loaders/cart.ts +0 -52
  57. package/src/loaders/catalog.ts +0 -163
  58. package/src/loaders/collections.ts +0 -55
  59. package/src/loaders/index.ts +0 -20
  60. package/src/loaders/intelligentSearch/__tests__/productListingPage.test.ts +0 -51
  61. package/src/loaders/intelligentSearch/productDetailsPage.ts +0 -113
  62. package/src/loaders/intelligentSearch/productList.ts +0 -154
  63. package/src/loaders/intelligentSearch/productListingPage.ts +0 -526
  64. package/src/loaders/intelligentSearch/suggestions.ts +0 -46
  65. package/src/loaders/legacy/productDetailsPage.ts +0 -1
  66. package/src/loaders/legacy/productList.ts +0 -1
  67. package/src/loaders/legacy/relatedProductsLoader.ts +0 -81
  68. package/src/loaders/legacy.ts +0 -635
  69. package/src/loaders/logistics.ts +0 -111
  70. package/src/loaders/minicart.ts +0 -78
  71. package/src/loaders/navbar.ts +0 -26
  72. package/src/loaders/orders.ts +0 -92
  73. package/src/loaders/pageType.ts +0 -68
  74. package/src/loaders/paths/PDPDefaultPath.ts +0 -51
  75. package/src/loaders/payment.ts +0 -97
  76. package/src/loaders/productListFull.ts +0 -159
  77. package/src/loaders/profile.ts +0 -138
  78. package/src/loaders/promotion.ts +0 -30
  79. package/src/loaders/search.ts +0 -124
  80. package/src/loaders/session.ts +0 -83
  81. package/src/loaders/user.ts +0 -87
  82. package/src/loaders/wishlist.ts +0 -89
  83. package/src/loaders/wishlistProducts.ts +0 -69
  84. package/src/loaders/workflow/products.ts +0 -64
  85. package/src/loaders/workflow.ts +0 -316
  86. package/src/logo.png +0 -0
  87. package/src/manifest.gen.ts +0 -89
  88. package/src/middleware.ts +0 -240
  89. package/src/mod.ts +0 -157
  90. package/src/registry.ts +0 -9
  91. package/src/schemas.gen.ts +0 -3191
  92. package/src/schemas.ts +0 -30
  93. package/src/types.ts +0 -248
  94. package/src/utils/__tests__/cartProjection.test.ts +0 -118
  95. package/src/utils/__tests__/cookieSanitizer.test.ts +0 -162
  96. package/src/utils/__tests__/fetch.test.ts +0 -80
  97. package/src/utils/__tests__/fetchCache.test.ts +0 -155
  98. package/src/utils/__tests__/instrumentedFetch.test.ts +0 -158
  99. package/src/utils/__tests__/intelligentSearch.test.ts +0 -88
  100. package/src/utils/__tests__/minicart.test.ts +0 -184
  101. package/src/utils/__tests__/operationRouter.test.ts +0 -227
  102. package/src/utils/__tests__/resilience.test.ts +0 -205
  103. package/src/utils/__tests__/resourceRange.test.ts +0 -32
  104. package/src/utils/__tests__/simulationCache.test.ts +0 -40
  105. package/src/utils/__tests__/sitemap.test.ts +0 -185
  106. package/src/utils/__tests__/slugify.test.ts +0 -31
  107. package/src/utils/__tests__/transform.test.ts +0 -769
  108. package/src/utils/accountLoaders.ts +0 -203
  109. package/src/utils/authHelpers.ts +0 -85
  110. package/src/utils/batch.ts +0 -18
  111. package/src/utils/cartProjection.ts +0 -96
  112. package/src/utils/cookies.ts +0 -167
  113. package/src/utils/enrichment.ts +0 -560
  114. package/src/utils/fetch.ts +0 -107
  115. package/src/utils/fetchCache.ts +0 -199
  116. package/src/utils/index.ts +0 -19
  117. package/src/utils/instrumentedFetch.ts +0 -97
  118. package/src/utils/intelligentSearch.ts +0 -96
  119. package/src/utils/legacy.ts +0 -139
  120. package/src/utils/minicart.ts +0 -139
  121. package/src/utils/pickAndOmit.ts +0 -25
  122. package/src/utils/proxy.ts +0 -435
  123. package/src/utils/resilience.ts +0 -352
  124. package/src/utils/resourceRange.ts +0 -10
  125. package/src/utils/segment.ts +0 -152
  126. package/src/utils/similars.ts +0 -37
  127. package/src/utils/simulationCache.ts +0 -73
  128. package/src/utils/sitemap.ts +0 -282
  129. package/src/utils/slugCache.ts +0 -28
  130. package/src/utils/slugify.ts +0 -13
  131. package/src/utils/transform.ts +0 -1522
  132. package/src/utils/vtexId.ts +0 -138
  133. 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
- });