@decocms/apps-vtex 7.60.0 → 7.62.0
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/apps-vtex",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.62.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Deco commerce app: VTEX integration",
|
|
6
6
|
"repository": {
|
|
@@ -51,10 +51,10 @@
|
|
|
51
51
|
"lint:unused": "knip"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@decocms/blocks": "7.
|
|
55
|
-
"@decocms/apps-commerce": "7.
|
|
56
|
-
"@decocms/apps-website": "7.
|
|
57
|
-
"@decocms/tanstack": "7.
|
|
54
|
+
"@decocms/blocks": "7.62.0",
|
|
55
|
+
"@decocms/apps-commerce": "7.62.0",
|
|
56
|
+
"@decocms/apps-website": "7.62.0",
|
|
57
|
+
"@decocms/tanstack": "7.62.0"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"@tanstack/react-query": ">=5.0.0",
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
// `legacyProductList` is the SHELF loader. It accepted no payload-shaping
|
|
4
|
+
// options, so every shelf paid the full product for every entry — measured on a
|
|
5
|
+
// real home shelf (`count: 28`): 1095 KB, 39.1 KB per product, of which
|
|
6
|
+
// `isVariantOf` 19.1 KB and the 48-rung `offers.priceSpecification` 12.8 KB.
|
|
7
|
+
// `legacyProductListingPage` already took them; this file pins the pass-through,
|
|
8
|
+
// because declaring the options WITHOUT forwarding them is exactly the bug the
|
|
9
|
+
// PLP loader had.
|
|
10
|
+
|
|
11
|
+
vi.mock("../../client", () => ({
|
|
12
|
+
getVtexConfig: () => ({ account: "test", salesChannel: "1" }),
|
|
13
|
+
vtexFetch: vi.fn(),
|
|
14
|
+
vtexFetchResponse: vi.fn(),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
import { vtexFetch } from "../../client";
|
|
18
|
+
import { legacyProductList } from "../legacy";
|
|
19
|
+
|
|
20
|
+
const sellers = (price: number) => [
|
|
21
|
+
{
|
|
22
|
+
sellerId: "1",
|
|
23
|
+
sellerName: "Seller One",
|
|
24
|
+
sellerDefault: true,
|
|
25
|
+
commertialOffer: {
|
|
26
|
+
AvailableQuantity: 5,
|
|
27
|
+
Price: price,
|
|
28
|
+
ListPrice: price + 30,
|
|
29
|
+
PriceWithoutDiscount: price + 30,
|
|
30
|
+
spotPrice: price,
|
|
31
|
+
PriceValidUntil: "2025-12-31",
|
|
32
|
+
// A real ladder: every method the store accepts, every installment count.
|
|
33
|
+
Installments: ["Visa", "Master", "Amex", "Boleto"].flatMap((method) =>
|
|
34
|
+
Array.from({ length: 4 }, (_, i) => ({
|
|
35
|
+
Value: price / (i + 1),
|
|
36
|
+
NumberOfInstallments: i + 1,
|
|
37
|
+
Name: `${method} ${i + 1}x`,
|
|
38
|
+
InterestRate: 0,
|
|
39
|
+
TotalValuePlusInterestRate: price,
|
|
40
|
+
PaymentSystemName: method,
|
|
41
|
+
})),
|
|
42
|
+
),
|
|
43
|
+
GiftSkuIds: [],
|
|
44
|
+
teasers: [],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const sku = (itemId: string, price: number) => ({
|
|
50
|
+
itemId,
|
|
51
|
+
name: `SKU ${itemId}`,
|
|
52
|
+
nameComplete: `SKU ${itemId}`,
|
|
53
|
+
complementName: "",
|
|
54
|
+
ean: "1234567890123",
|
|
55
|
+
referenceId: [{ Key: "RefId", Value: `REF-${itemId}` }],
|
|
56
|
+
images: Array.from({ length: 4 }, (_, i) => ({
|
|
57
|
+
imageId: `${itemId}-${i}`,
|
|
58
|
+
imageUrl: `https://img.com/${itemId}-${i}.jpg`,
|
|
59
|
+
imageText: `img${i}`,
|
|
60
|
+
imageLabel: `label${i}`,
|
|
61
|
+
})),
|
|
62
|
+
sellers: sellers(price),
|
|
63
|
+
Videos: [],
|
|
64
|
+
estimatedDateArrival: null,
|
|
65
|
+
measurementUnit: "un",
|
|
66
|
+
unitMultiplier: 1,
|
|
67
|
+
variations: [],
|
|
68
|
+
attachments: [],
|
|
69
|
+
isKit: false,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const legacyProduct = () => ({
|
|
73
|
+
productId: "PROD1",
|
|
74
|
+
productName: "Test Product",
|
|
75
|
+
brand: "TestBrand",
|
|
76
|
+
brandId: 1,
|
|
77
|
+
brandImageUrl: null,
|
|
78
|
+
linkText: "test-product",
|
|
79
|
+
productReference: "REF1",
|
|
80
|
+
categoryId: "1",
|
|
81
|
+
productTitle: "Test Product",
|
|
82
|
+
metaTagDescription: "meta",
|
|
83
|
+
clusterHighlights: {},
|
|
84
|
+
productClusters: {},
|
|
85
|
+
searchableClusters: {},
|
|
86
|
+
categories: ["/Electronics/"],
|
|
87
|
+
categoriesIds: ["/1/"],
|
|
88
|
+
link: "https://test/test-product/p",
|
|
89
|
+
description: "x".repeat(2000),
|
|
90
|
+
items: [sku("SKU1", 90), sku("SKU2", 60), sku("SKU3", 120)],
|
|
91
|
+
allSpecifications: [],
|
|
92
|
+
allSpecificationsGroups: [],
|
|
93
|
+
skuSpecifications: [],
|
|
94
|
+
releaseDate: "2024-01-01",
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const opts = { query: { count: 1 } as any, baseUrl: "https://example.com" };
|
|
98
|
+
const ladderOf = (p: any) => p?.offers?.offers?.[0]?.priceSpecification ?? [];
|
|
99
|
+
|
|
100
|
+
describe("legacyProductList — payload-shaping options reach toProduct", () => {
|
|
101
|
+
beforeEach(() => {
|
|
102
|
+
(vtexFetch as any).mockReset();
|
|
103
|
+
(vtexFetch as any).mockResolvedValue([legacyProduct()]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("no options: full product, full ladder, every variant, every image", async () => {
|
|
107
|
+
const [p] = (await legacyProductList(opts)) as any[];
|
|
108
|
+
expect(ladderOf(p).length).toBeGreaterThan(10);
|
|
109
|
+
expect(p.isVariantOf.hasVariant).toHaveLength(3);
|
|
110
|
+
expect(ladderOf(p.isVariantOf.hasVariant[0]).length).toBeGreaterThan(10);
|
|
111
|
+
expect(p.image).toHaveLength(4);
|
|
112
|
+
expect(p.description).toBeTruthy();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("leanVariants empties the ladder on every variant", async () => {
|
|
116
|
+
const [p] = (await legacyProductList({ ...opts, leanVariants: true })) as any[];
|
|
117
|
+
for (const v of p.isVariantOf.hasVariant) expect(ladderOf(v)).toEqual([]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("displayedVariantId keeps the ladder on the one variant the card renders", async () => {
|
|
121
|
+
const [p] = (await legacyProductList({
|
|
122
|
+
...opts,
|
|
123
|
+
leanVariants: true,
|
|
124
|
+
displayedVariantId: (items: any[]) => items[1].itemId,
|
|
125
|
+
})) as any[];
|
|
126
|
+
const kept = p.isVariantOf.hasVariant.find((v: any) => v.sku === "SKU2");
|
|
127
|
+
expect(ladderOf(kept).length).toBeGreaterThan(0);
|
|
128
|
+
// ...and it is the LEAN shape, not a second full product.
|
|
129
|
+
expect(kept.description).toBeUndefined();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("priceSpecifications rewrites the root ladder with the caller's rule", async () => {
|
|
133
|
+
const [p] = (await legacyProductList({
|
|
134
|
+
...opts,
|
|
135
|
+
priceSpecifications: (specs) => specs.filter((s) => !s.priceComponentType),
|
|
136
|
+
})) as any[];
|
|
137
|
+
expect(ladderOf(p).every((s: any) => !s.priceComponentType)).toBe(true);
|
|
138
|
+
expect(ladderOf(p).length).toBeLessThan(10);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("maxImages caps image[] by position", async () => {
|
|
142
|
+
const [p] = (await legacyProductList({ ...opts, maxImages: 2 })) as any[];
|
|
143
|
+
expect(p.image).toHaveLength(2);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("every option absent is byte-for-byte the previous output", async () => {
|
|
147
|
+
const [before] = (await legacyProductList(opts)) as any[];
|
|
148
|
+
const [after] = (await legacyProductList({
|
|
149
|
+
...opts,
|
|
150
|
+
leanVariants: undefined,
|
|
151
|
+
displayedVariantId: undefined,
|
|
152
|
+
maxImages: undefined,
|
|
153
|
+
priceSpecifications: undefined,
|
|
154
|
+
})) as any[];
|
|
155
|
+
expect(JSON.stringify(after)).toBe(JSON.stringify(before));
|
|
156
|
+
});
|
|
157
|
+
});
|
package/src/loaders/legacy.ts
CHANGED
|
@@ -159,6 +159,23 @@ export interface LegacyProductListOptions {
|
|
|
159
159
|
query: LegacyProductListQuery;
|
|
160
160
|
baseUrl: string;
|
|
161
161
|
priceCurrency?: string;
|
|
162
|
+
/**
|
|
163
|
+
* Payload-shaping options, forwarded verbatim to `toProduct`. Same set the
|
|
164
|
+
* PLP loader already accepts, and for the same reason: a shelf renders a
|
|
165
|
+
* CARD, but this loader built the full product for every entry. Measured on
|
|
166
|
+
* a real home shelf (`count: 28`), 1095 KB — 39.1 KB per product, of which
|
|
167
|
+
* `isVariantOf` 19.1 KB and the 48-rung `offers.priceSpecification` 12.8 KB.
|
|
168
|
+
*
|
|
169
|
+
* All default to undefined, which keeps the previous output byte for byte.
|
|
170
|
+
* See the matching fields on {@link ProductOptions}.
|
|
171
|
+
*/
|
|
172
|
+
leanVariants?: ProductOptions["leanVariants"];
|
|
173
|
+
displayedVariantId?: ProductOptions["displayedVariantId"];
|
|
174
|
+
variantPropertyNames?: ProductOptions["variantPropertyNames"];
|
|
175
|
+
variantIncludeImage?: ProductOptions["variantIncludeImage"];
|
|
176
|
+
variantIncludeInventory?: ProductOptions["variantIncludeInventory"];
|
|
177
|
+
maxImages?: ProductOptions["maxImages"];
|
|
178
|
+
priceSpecifications?: ProductOptions["priceSpecifications"];
|
|
162
179
|
}
|
|
163
180
|
|
|
164
181
|
function isCollectionQuery(
|
|
@@ -234,7 +251,18 @@ function queryToSearchParams(
|
|
|
234
251
|
* Ported from: vtex/loaders/legacy/productList.ts
|
|
235
252
|
*/
|
|
236
253
|
export async function legacyProductList(opts: LegacyProductListOptions): Promise<Product[] | null> {
|
|
237
|
-
const {
|
|
254
|
+
const {
|
|
255
|
+
query,
|
|
256
|
+
baseUrl,
|
|
257
|
+
priceCurrency = "BRL",
|
|
258
|
+
leanVariants,
|
|
259
|
+
displayedVariantId,
|
|
260
|
+
variantPropertyNames,
|
|
261
|
+
variantIncludeImage,
|
|
262
|
+
variantIncludeInventory,
|
|
263
|
+
maxImages,
|
|
264
|
+
priceSpecifications,
|
|
265
|
+
} = opts;
|
|
238
266
|
const searchArgs = queryToSearchParams(query);
|
|
239
267
|
const qs = buildSearchParams(searchArgs);
|
|
240
268
|
|
|
@@ -255,7 +283,17 @@ export async function legacyProductList(opts: LegacyProductListOptions): Promise
|
|
|
255
283
|
};
|
|
256
284
|
|
|
257
285
|
let products = vtexProducts.map((p) =>
|
|
258
|
-
toProduct(p, preferredSKU(p.items), 0, {
|
|
286
|
+
toProduct(p, preferredSKU(p.items), 0, {
|
|
287
|
+
baseUrl,
|
|
288
|
+
priceCurrency,
|
|
289
|
+
leanVariants,
|
|
290
|
+
displayedVariantId,
|
|
291
|
+
variantPropertyNames,
|
|
292
|
+
variantIncludeImage,
|
|
293
|
+
variantIncludeInventory,
|
|
294
|
+
maxImages,
|
|
295
|
+
priceSpecifications,
|
|
296
|
+
}),
|
|
259
297
|
);
|
|
260
298
|
|
|
261
299
|
if (isSkuIdsQuery(query)) {
|