@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.
- package/package.json +7 -52
- package/src/index.ts +12 -15
- package/src/utils/constants.ts +2 -56
- package/src/utils/cookieSanitizer.ts +59 -59
- package/src/utils/operationRouter.ts +76 -72
- package/src/utils/types.ts +709 -1557
- package/src/vtexClient.ts +444 -0
- package/src/README.md +0 -6
- package/src/__tests__/client-segment-cookie.test.ts +0 -255
- package/src/__tests__/client-set-cookie-forward.test.ts +0 -257
- package/src/__tests__/schemas.test.ts +0 -71
- package/src/actions/address.ts +0 -250
- package/src/actions/analytics/sendEvent.ts +0 -86
- package/src/actions/auth.ts +0 -333
- package/src/actions/checkout.ts +0 -659
- package/src/actions/index.ts +0 -11
- package/src/actions/masterData.ts +0 -168
- package/src/actions/misc.ts +0 -188
- package/src/actions/newsletter.ts +0 -105
- package/src/actions/orders.ts +0 -34
- package/src/actions/profile.ts +0 -201
- package/src/actions/session.ts +0 -85
- package/src/actions/trigger.ts +0 -43
- package/src/actions/wishlist.ts +0 -114
- package/src/client.ts +0 -667
- package/src/commerceLoaders.ts +0 -267
- package/src/hooks/__tests__/createCart.render.test.ts +0 -114
- package/src/hooks/__tests__/createCart.test.ts +0 -92
- package/src/hooks/__tests__/createUseCart.test.ts +0 -184
- package/src/hooks/__tests__/createUseUser.test.ts +0 -48
- package/src/hooks/__tests__/createUseWishlist.test.ts +0 -87
- package/src/hooks/cartQuery.ts +0 -204
- package/src/hooks/createCart.ts +0 -427
- package/src/hooks/createUseCart.ts +0 -360
- package/src/hooks/createUseUser.ts +0 -153
- package/src/hooks/createUseWishlist.ts +0 -242
- package/src/hooks/index.ts +0 -25
- package/src/hooks/useAutocomplete.ts +0 -83
- package/src/hooks/useCart.ts +0 -243
- package/src/hooks/useUser.ts +0 -78
- package/src/hooks/useWishlist.ts +0 -119
- package/src/invoke.ts +0 -235
- package/src/loaders/ProductDetailsPage.ts +0 -1
- package/src/loaders/ProductList.ts +0 -1
- package/src/loaders/ProductListingPage.ts +0 -1
- package/src/loaders/address.ts +0 -115
- package/src/loaders/autocomplete.ts +0 -58
- package/src/loaders/brands.ts +0 -44
- package/src/loaders/cart/attachments.ts +0 -53
- package/src/loaders/cart/full.ts +0 -67
- package/src/loaders/cart/gifts.ts +0 -43
- package/src/loaders/cart/orderFormId.ts +0 -31
- package/src/loaders/cart/shipping.test.ts +0 -125
- package/src/loaders/cart/shipping.ts +0 -136
- package/src/loaders/cart/summary.ts +0 -36
- package/src/loaders/cart.ts +0 -52
- package/src/loaders/catalog.ts +0 -163
- package/src/loaders/collections.ts +0 -55
- package/src/loaders/index.ts +0 -20
- package/src/loaders/intelligentSearch/__tests__/productListingPage.test.ts +0 -51
- package/src/loaders/intelligentSearch/productDetailsPage.ts +0 -113
- package/src/loaders/intelligentSearch/productList.ts +0 -154
- package/src/loaders/intelligentSearch/productListingPage.ts +0 -526
- package/src/loaders/intelligentSearch/suggestions.ts +0 -46
- package/src/loaders/legacy/productDetailsPage.ts +0 -1
- package/src/loaders/legacy/productList.ts +0 -1
- package/src/loaders/legacy/relatedProductsLoader.ts +0 -81
- package/src/loaders/legacy.ts +0 -635
- package/src/loaders/logistics.ts +0 -111
- package/src/loaders/minicart.ts +0 -78
- package/src/loaders/navbar.ts +0 -26
- package/src/loaders/orders.ts +0 -92
- package/src/loaders/pageType.ts +0 -68
- package/src/loaders/paths/PDPDefaultPath.ts +0 -51
- package/src/loaders/payment.ts +0 -97
- package/src/loaders/productListFull.ts +0 -159
- package/src/loaders/profile.ts +0 -138
- package/src/loaders/promotion.ts +0 -30
- package/src/loaders/search.ts +0 -124
- package/src/loaders/session.ts +0 -83
- package/src/loaders/user.ts +0 -87
- package/src/loaders/wishlist.ts +0 -89
- package/src/loaders/wishlistProducts.ts +0 -69
- package/src/loaders/workflow/products.ts +0 -64
- package/src/loaders/workflow.ts +0 -316
- package/src/logo.png +0 -0
- package/src/manifest.gen.ts +0 -89
- package/src/middleware.ts +0 -240
- package/src/mod.ts +0 -157
- package/src/registry.ts +0 -9
- package/src/schemas.gen.ts +0 -3191
- package/src/schemas.ts +0 -30
- package/src/types.ts +0 -248
- package/src/utils/__tests__/cartProjection.test.ts +0 -118
- package/src/utils/__tests__/cookieSanitizer.test.ts +0 -162
- package/src/utils/__tests__/fetch.test.ts +0 -80
- package/src/utils/__tests__/fetchCache.test.ts +0 -155
- package/src/utils/__tests__/instrumentedFetch.test.ts +0 -158
- package/src/utils/__tests__/intelligentSearch.test.ts +0 -88
- package/src/utils/__tests__/minicart.test.ts +0 -184
- package/src/utils/__tests__/operationRouter.test.ts +0 -227
- package/src/utils/__tests__/resilience.test.ts +0 -205
- package/src/utils/__tests__/resourceRange.test.ts +0 -32
- package/src/utils/__tests__/simulationCache.test.ts +0 -40
- package/src/utils/__tests__/sitemap.test.ts +0 -185
- package/src/utils/__tests__/slugify.test.ts +0 -31
- package/src/utils/__tests__/transform.test.ts +0 -769
- package/src/utils/accountLoaders.ts +0 -203
- package/src/utils/authHelpers.ts +0 -85
- package/src/utils/batch.ts +0 -18
- package/src/utils/cartProjection.ts +0 -96
- package/src/utils/cookies.ts +0 -167
- package/src/utils/enrichment.ts +0 -560
- package/src/utils/fetch.ts +0 -107
- package/src/utils/fetchCache.ts +0 -199
- package/src/utils/index.ts +0 -19
- package/src/utils/instrumentedFetch.ts +0 -97
- package/src/utils/intelligentSearch.ts +0 -96
- package/src/utils/legacy.ts +0 -139
- package/src/utils/minicart.ts +0 -139
- package/src/utils/pickAndOmit.ts +0 -25
- package/src/utils/proxy.ts +0 -435
- package/src/utils/resilience.ts +0 -352
- package/src/utils/resourceRange.ts +0 -10
- package/src/utils/segment.ts +0 -152
- package/src/utils/similars.ts +0 -37
- package/src/utils/simulationCache.ts +0 -73
- package/src/utils/sitemap.ts +0 -282
- package/src/utils/slugCache.ts +0 -28
- package/src/utils/slugify.ts +0 -13
- package/src/utils/transform.ts +0 -1522
- package/src/utils/vtexId.ts +0 -138
- package/tsconfig.json +0 -7
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { clearFetchCache, fetchWithCache, getFetchCacheStats } from "../fetchCache";
|
|
3
|
-
import { VtexCircuitOpenError } from "../resilience";
|
|
4
|
-
|
|
5
|
-
function mockResponse(body: unknown, status = 200): Response {
|
|
6
|
-
return {
|
|
7
|
-
ok: status >= 200 && status < 300,
|
|
8
|
-
status,
|
|
9
|
-
statusText: status === 200 ? "OK" : "Error",
|
|
10
|
-
json: () => Promise.resolve(body),
|
|
11
|
-
} as Response;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
describe("fetchWithCache", () => {
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
clearFetchCache();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("returns fetched data on cache miss", async () => {
|
|
20
|
-
const data = { id: 1, name: "product" };
|
|
21
|
-
const doFetch = vi.fn(() => Promise.resolve(mockResponse(data)));
|
|
22
|
-
|
|
23
|
-
const result = await fetchWithCache("key1", doFetch);
|
|
24
|
-
expect(result).toEqual(data);
|
|
25
|
-
expect(doFetch).toHaveBeenCalledOnce();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("returns cached data on subsequent calls", async () => {
|
|
29
|
-
const data = { id: 1 };
|
|
30
|
-
const doFetch = vi.fn(() => Promise.resolve(mockResponse(data)));
|
|
31
|
-
|
|
32
|
-
await fetchWithCache("key2", doFetch);
|
|
33
|
-
const result = await fetchWithCache("key2", doFetch);
|
|
34
|
-
|
|
35
|
-
expect(result).toEqual(data);
|
|
36
|
-
expect(doFetch).toHaveBeenCalledOnce();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("deduplicates in-flight requests", async () => {
|
|
40
|
-
const data = { id: 1 };
|
|
41
|
-
const doFetch = vi.fn(
|
|
42
|
-
() => new Promise<Response>((resolve) => setTimeout(() => resolve(mockResponse(data)), 10)),
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
const [r1, r2] = await Promise.all([
|
|
46
|
-
fetchWithCache("key3", doFetch),
|
|
47
|
-
fetchWithCache("key3", doFetch),
|
|
48
|
-
]);
|
|
49
|
-
|
|
50
|
-
expect(r1).toEqual(data);
|
|
51
|
-
expect(r2).toEqual(data);
|
|
52
|
-
expect(doFetch).toHaveBeenCalledOnce();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("throws on 5xx responses", async () => {
|
|
56
|
-
const doFetch = vi.fn(() => Promise.resolve(mockResponse(null, 500)));
|
|
57
|
-
|
|
58
|
-
await expect(fetchWithCache("key4", doFetch)).rejects.toThrow("500");
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("does not retry a circuit-open error (fast-fail)", async () => {
|
|
62
|
-
const doFetch = vi.fn(() => Promise.reject(new VtexCircuitOpenError("host")));
|
|
63
|
-
|
|
64
|
-
await expect(fetchWithCache("circuit-key", doFetch)).rejects.toBeInstanceOf(
|
|
65
|
-
VtexCircuitOpenError,
|
|
66
|
-
);
|
|
67
|
-
// The breaker already decided to shed load — must not hammer with retries.
|
|
68
|
-
expect(doFetch).toHaveBeenCalledOnce();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("serves last-good stale data when a background refresh fails", async () => {
|
|
72
|
-
vi.useFakeTimers();
|
|
73
|
-
try {
|
|
74
|
-
let call = 0;
|
|
75
|
-
const doFetch = vi.fn(() => {
|
|
76
|
-
call++;
|
|
77
|
-
if (call === 1) return Promise.resolve(mockResponse({ v: "good" }));
|
|
78
|
-
return Promise.reject(new VtexCircuitOpenError("host"));
|
|
79
|
-
});
|
|
80
|
-
// Prime a good entry.
|
|
81
|
-
const first = await fetchWithCache<{ v: string }>("sie-key", doFetch);
|
|
82
|
-
expect(first).toEqual({ v: "good" });
|
|
83
|
-
|
|
84
|
-
// Advance past the 2xx freshness TTL (180s) but within the 24h SIE window.
|
|
85
|
-
await vi.advanceTimersByTimeAsync(200_000);
|
|
86
|
-
|
|
87
|
-
// Stale read: serves last-good immediately, refresh happens in background.
|
|
88
|
-
const second = await fetchWithCache<{ v: string }>("sie-key", doFetch);
|
|
89
|
-
expect(second).toEqual({ v: "good" });
|
|
90
|
-
} finally {
|
|
91
|
-
vi.useRealTimers();
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("does not retry — retries are owned by the resilience layer", async () => {
|
|
96
|
-
// A 5xx used to be retried 3× here, which also re-entered the circuit
|
|
97
|
-
// breaker 3× per request. fetchCache now makes a single attempt.
|
|
98
|
-
const doFetch = vi.fn(() => Promise.resolve(mockResponse(null, 500)));
|
|
99
|
-
await expect(fetchWithCache("single-attempt", doFetch)).rejects.toThrow("500");
|
|
100
|
-
expect(doFetch).toHaveBeenCalledOnce();
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it("returns null for 404 responses", async () => {
|
|
104
|
-
const doFetch = vi.fn(() => Promise.resolve(mockResponse(null, 404)));
|
|
105
|
-
|
|
106
|
-
const result = await fetchWithCache("key5", doFetch);
|
|
107
|
-
expect(result).toBeNull();
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("tracks cache stats", async () => {
|
|
111
|
-
const doFetch = () => Promise.resolve(mockResponse({ ok: true }));
|
|
112
|
-
|
|
113
|
-
expect(getFetchCacheStats()).toEqual({ entries: 0, inflight: 0 });
|
|
114
|
-
|
|
115
|
-
await fetchWithCache("stats1", doFetch);
|
|
116
|
-
await fetchWithCache("stats2", doFetch);
|
|
117
|
-
|
|
118
|
-
expect(getFetchCacheStats()).toEqual({ entries: 2, inflight: 0 });
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it("clears cache", async () => {
|
|
122
|
-
const doFetch = () => Promise.resolve(mockResponse({ ok: true }));
|
|
123
|
-
await fetchWithCache("clear1", doFetch);
|
|
124
|
-
|
|
125
|
-
clearFetchCache();
|
|
126
|
-
expect(getFetchCacheStats()).toEqual({ entries: 0, inflight: 0 });
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it("evicts the inflight slot when the fetch never settles", async () => {
|
|
130
|
-
vi.useFakeTimers();
|
|
131
|
-
try {
|
|
132
|
-
// `doFetch` returns a Promise that never resolves — simulates a hung
|
|
133
|
-
// VTEX subrequest (TCP open, no FIN, no response). Without the
|
|
134
|
-
// timeout guard, the inflight Map entry would leak forever and
|
|
135
|
-
// subsequent callers would `await` a zombie Promise — the prod
|
|
136
|
-
// memory-leak this fix addresses.
|
|
137
|
-
const doFetch = vi.fn(() => new Promise<Response>(() => {}));
|
|
138
|
-
|
|
139
|
-
const pending = fetchWithCache("hung-key", doFetch);
|
|
140
|
-
// Swallow the eventual rejection so the unhandled rejection doesn't
|
|
141
|
-
// fail the test runner.
|
|
142
|
-
pending.catch(() => {});
|
|
143
|
-
|
|
144
|
-
expect(getFetchCacheStats().inflight).toBe(1);
|
|
145
|
-
|
|
146
|
-
// Fast-forward past the 15s inflight backstop timeout.
|
|
147
|
-
await vi.advanceTimersByTimeAsync(16_000);
|
|
148
|
-
|
|
149
|
-
await expect(pending).rejects.toThrow(/timed out/);
|
|
150
|
-
expect(getFetchCacheStats().inflight).toBe(0);
|
|
151
|
-
} finally {
|
|
152
|
-
vi.useRealTimers();
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
});
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Smoke tests for the pre-wired VTEX fetch factory.
|
|
3
|
-
*
|
|
4
|
-
* The deep coverage of `createInstrumentedFetch` lives in @decocms/start;
|
|
5
|
-
* here we only verify the apps-start wiring decisions:
|
|
6
|
-
*
|
|
7
|
-
* - The URL router is plumbed through so unannotated callsites get
|
|
8
|
-
* semantic span operations + histogram labels.
|
|
9
|
-
* - The canonical `http.client.request.duration` histogram is recorded
|
|
10
|
-
* with the right labels on every call (via the framework's
|
|
11
|
-
* `recordCommerceMetric` helper).
|
|
12
|
-
* - `disableHistogram: true` opts out cleanly.
|
|
13
|
-
* - A caller's explicit `init.operation` wins over the URL router
|
|
14
|
-
* (delegating to the framework, but worth asserting at this seam).
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { configureMeter, type MeterAdapter } from "@decocms/blocks/sdk/observability";
|
|
18
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
19
|
-
import { createVtexFetch } from "../instrumentedFetch";
|
|
20
|
-
|
|
21
|
-
type Labels = Record<string, string | number | boolean>;
|
|
22
|
-
type HistogramCall = {
|
|
23
|
-
name: string;
|
|
24
|
-
value: number;
|
|
25
|
-
attrs: Labels;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
function captureHistogram(): { calls: HistogramCall[]; meter: MeterAdapter } {
|
|
29
|
-
const calls: HistogramCall[] = [];
|
|
30
|
-
const meter: MeterAdapter = {
|
|
31
|
-
counterInc: vi.fn(),
|
|
32
|
-
gaugeSet: vi.fn(),
|
|
33
|
-
histogramRecord: (name, value, attrs) => {
|
|
34
|
-
calls.push({ name, value, attrs: attrs ?? {} });
|
|
35
|
-
},
|
|
36
|
-
};
|
|
37
|
-
return { calls, meter };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function mockOkResponse(status = 200): Response {
|
|
41
|
-
return new Response(JSON.stringify({}), { status });
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
describe("createVtexFetch", () => {
|
|
45
|
-
afterEach(() => {
|
|
46
|
-
vi.restoreAllMocks();
|
|
47
|
-
configureMeter({
|
|
48
|
-
counterInc: () => {},
|
|
49
|
-
gaugeSet: () => {},
|
|
50
|
-
histogramRecord: () => {},
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("records http.client.request.duration with provider/operation/status labels on success", async () => {
|
|
55
|
-
const { calls, meter } = captureHistogram();
|
|
56
|
-
configureMeter(meter);
|
|
57
|
-
|
|
58
|
-
const baseFetch = vi.fn(async () => mockOkResponse(200));
|
|
59
|
-
const fetchFn = createVtexFetch({ baseFetch: baseFetch as typeof fetch });
|
|
60
|
-
|
|
61
|
-
await fetchFn("https://store.vtexcommercestable.com.br/api/sessions");
|
|
62
|
-
|
|
63
|
-
expect(calls).toHaveLength(1);
|
|
64
|
-
expect(calls[0].name).toBe("http.client.request.duration");
|
|
65
|
-
expect(calls[0].attrs).toMatchObject({
|
|
66
|
-
provider: "vtex",
|
|
67
|
-
operation: "sessions.get",
|
|
68
|
-
status_class: "2xx",
|
|
69
|
-
cached: false,
|
|
70
|
-
});
|
|
71
|
-
expect(calls[0].value).toBeGreaterThanOrEqual(0);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("uses the URL router for unannotated calls", async () => {
|
|
75
|
-
const { calls, meter } = captureHistogram();
|
|
76
|
-
configureMeter(meter);
|
|
77
|
-
|
|
78
|
-
const baseFetch = vi.fn(async () => mockOkResponse(200));
|
|
79
|
-
const fetchFn = createVtexFetch({ baseFetch: baseFetch as typeof fetch });
|
|
80
|
-
|
|
81
|
-
await fetchFn(
|
|
82
|
-
"https://store.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/foo",
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
expect(calls[0].attrs.operation).toBe("intelligent-search.product_search");
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("honors init.operation over the URL router", async () => {
|
|
89
|
-
const { calls, meter } = captureHistogram();
|
|
90
|
-
configureMeter(meter);
|
|
91
|
-
|
|
92
|
-
const baseFetch = vi.fn(async () => mockOkResponse(200));
|
|
93
|
-
const fetchFn = createVtexFetch({ baseFetch: baseFetch as typeof fetch });
|
|
94
|
-
|
|
95
|
-
await fetchFn("https://store.vtexcommercestable.com.br/api/sessions", {
|
|
96
|
-
operation: "explicit.custom_op",
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
expect(calls[0].attrs.operation).toBe("explicit.custom_op");
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("records cached=true when the response carries x-cache: HIT", async () => {
|
|
103
|
-
const { calls, meter } = captureHistogram();
|
|
104
|
-
configureMeter(meter);
|
|
105
|
-
|
|
106
|
-
const baseFetch = vi.fn(
|
|
107
|
-
async () => new Response("{}", { status: 200, headers: { "x-cache": "HIT" } }),
|
|
108
|
-
);
|
|
109
|
-
const fetchFn = createVtexFetch({ baseFetch: baseFetch as typeof fetch });
|
|
110
|
-
|
|
111
|
-
await fetchFn("https://store.vtexcommercestable.com.br/api/sessions");
|
|
112
|
-
|
|
113
|
-
expect(calls[0].attrs.cached).toBe(true);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it("emits status_class derived from the actual response status", async () => {
|
|
117
|
-
const { calls, meter } = captureHistogram();
|
|
118
|
-
configureMeter(meter);
|
|
119
|
-
|
|
120
|
-
const baseFetch = vi.fn(async () => mockOkResponse(503));
|
|
121
|
-
const fetchFn = createVtexFetch({ baseFetch: baseFetch as typeof fetch });
|
|
122
|
-
|
|
123
|
-
await fetchFn("https://store.vtexcommercestable.com.br/api/sessions");
|
|
124
|
-
|
|
125
|
-
expect(calls[0].attrs.status_class).toBe("5xx");
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("skips histogram emission when disableHistogram is true", async () => {
|
|
129
|
-
const { calls, meter } = captureHistogram();
|
|
130
|
-
configureMeter(meter);
|
|
131
|
-
|
|
132
|
-
const baseFetch = vi.fn(async () => mockOkResponse(200));
|
|
133
|
-
const fetchFn = createVtexFetch({
|
|
134
|
-
baseFetch: baseFetch as typeof fetch,
|
|
135
|
-
disableHistogram: true,
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
await fetchFn("https://store.vtexcommercestable.com.br/api/sessions");
|
|
139
|
-
|
|
140
|
-
expect(calls).toHaveLength(0);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("does not surface the operation field to the underlying fetch", async () => {
|
|
144
|
-
const { meter } = captureHistogram();
|
|
145
|
-
configureMeter(meter);
|
|
146
|
-
|
|
147
|
-
const baseFetch = vi.fn(async (_input: unknown, _init?: RequestInit) => mockOkResponse(200));
|
|
148
|
-
const fetchFn = createVtexFetch({ baseFetch: baseFetch as unknown as typeof fetch });
|
|
149
|
-
|
|
150
|
-
await fetchFn("https://store.vtexcommercestable.com.br/api/sessions", {
|
|
151
|
-
operation: "explicit.op",
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
expect(baseFetch).toHaveBeenCalledOnce();
|
|
155
|
-
const init = baseFetch.mock.calls[0]?.[1] as Record<string, unknown> | undefined;
|
|
156
|
-
expect(init?.operation).toBeUndefined();
|
|
157
|
-
});
|
|
158
|
-
});
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { isFilterParam, toPath, withDefaultFacets, withDefaultParams } from "../intelligentSearch";
|
|
3
|
-
|
|
4
|
-
describe("withDefaultFacets", () => {
|
|
5
|
-
it("returns a copy of the facets array", () => {
|
|
6
|
-
const facets = [{ key: "category", value: "shoes" }];
|
|
7
|
-
const result = withDefaultFacets(facets);
|
|
8
|
-
expect(result).toEqual(facets);
|
|
9
|
-
expect(result).not.toBe(facets);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it("returns empty array for empty input", () => {
|
|
13
|
-
expect(withDefaultFacets([])).toEqual([]);
|
|
14
|
-
});
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
describe("toPath", () => {
|
|
18
|
-
it("builds path from facets", () => {
|
|
19
|
-
const facets = [
|
|
20
|
-
{ key: "category", value: "shoes" },
|
|
21
|
-
{ key: "brand", value: "nike" },
|
|
22
|
-
];
|
|
23
|
-
expect(toPath(facets)).toBe("category/shoes/brand/nike");
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("handles facets with empty key", () => {
|
|
27
|
-
const facets = [{ key: "", value: "shoes" }];
|
|
28
|
-
expect(toPath(facets)).toBe("shoes");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("returns empty string for empty facets", () => {
|
|
32
|
-
expect(toPath([])).toBe("");
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
describe("withDefaultParams", () => {
|
|
37
|
-
it("fills in defaults", () => {
|
|
38
|
-
const result = withDefaultParams({});
|
|
39
|
-
expect(result).toEqual({
|
|
40
|
-
page: 1,
|
|
41
|
-
count: 12,
|
|
42
|
-
query: "",
|
|
43
|
-
sort: "",
|
|
44
|
-
fuzzy: "auto",
|
|
45
|
-
locale: "pt-BR",
|
|
46
|
-
hideUnavailableItems: false,
|
|
47
|
-
simulationBehavior: "default",
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("increments page by 1", () => {
|
|
52
|
-
expect(withDefaultParams({ page: 0 }).page).toBe(1);
|
|
53
|
-
expect(withDefaultParams({ page: 2 }).page).toBe(3);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("preserves provided values", () => {
|
|
57
|
-
const result = withDefaultParams({
|
|
58
|
-
query: "shoes",
|
|
59
|
-
count: 24,
|
|
60
|
-
sort: "price:asc",
|
|
61
|
-
fuzzy: "0",
|
|
62
|
-
hideUnavailableItems: true,
|
|
63
|
-
});
|
|
64
|
-
expect(result.query).toBe("shoes");
|
|
65
|
-
expect(result.count).toBe(24);
|
|
66
|
-
expect(result.sort).toBe("price:asc");
|
|
67
|
-
expect(result.fuzzy).toBe("0");
|
|
68
|
-
expect(result.hideUnavailableItems).toBe(true);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("omits fuzzy when empty string", () => {
|
|
72
|
-
const result = withDefaultParams({ fuzzy: "" });
|
|
73
|
-
expect(result).not.toHaveProperty("fuzzy");
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
describe("isFilterParam", () => {
|
|
78
|
-
it("returns true for filter params", () => {
|
|
79
|
-
expect(isFilterParam("filter.category")).toBe(true);
|
|
80
|
-
expect(isFilterParam("filter.brand")).toBe(true);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("returns false for non-filter params", () => {
|
|
84
|
-
expect(isFilterParam("page")).toBe(false);
|
|
85
|
-
expect(isFilterParam("query")).toBe(false);
|
|
86
|
-
expect(isFilterParam("filterNot")).toBe(false);
|
|
87
|
-
});
|
|
88
|
-
});
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import type { OrderForm, OrderFormItem, Totalizer } from "../../types";
|
|
3
|
-
import { vtexOrderFormToMinicart } from "../minicart";
|
|
4
|
-
|
|
5
|
-
const baseStorePreferences = {
|
|
6
|
-
countryCode: "BRA",
|
|
7
|
-
currencyCode: "BRL",
|
|
8
|
-
currencyLocale: 1046,
|
|
9
|
-
currencySymbol: "R$",
|
|
10
|
-
saveUserData: true,
|
|
11
|
-
timeZone: "E. South America Standard Time",
|
|
12
|
-
currencyFormatInfo: {
|
|
13
|
-
currencyDecimalDigits: 2,
|
|
14
|
-
currencyDecimalSeparator: ",",
|
|
15
|
-
currencyGroupSeparator: ".",
|
|
16
|
-
currencyGroupSize: 3,
|
|
17
|
-
startsWithCurrencySymbol: true,
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
function makeOrderForm(overrides: Partial<OrderForm> = {}): OrderForm {
|
|
22
|
-
return {
|
|
23
|
-
orderFormId: "of-1",
|
|
24
|
-
salesChannel: "1",
|
|
25
|
-
loggedIn: false,
|
|
26
|
-
isCheckedIn: false,
|
|
27
|
-
allowManualPrice: false,
|
|
28
|
-
canEditData: false,
|
|
29
|
-
ignoreProfileData: false,
|
|
30
|
-
value: 0,
|
|
31
|
-
messages: [],
|
|
32
|
-
items: [],
|
|
33
|
-
totalizers: [],
|
|
34
|
-
shippingData: null,
|
|
35
|
-
clientProfileData: null,
|
|
36
|
-
paymentData: null,
|
|
37
|
-
marketingData: null,
|
|
38
|
-
sellers: [],
|
|
39
|
-
clientPreferencesData: { locale: "pt-BR" },
|
|
40
|
-
storePreferencesData: baseStorePreferences,
|
|
41
|
-
...overrides,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function makeItem(overrides: Partial<OrderFormItem> = {}): OrderFormItem {
|
|
46
|
-
return {
|
|
47
|
-
uniqueId: "uid-1",
|
|
48
|
-
id: "sku-1",
|
|
49
|
-
productId: "p-1",
|
|
50
|
-
name: "T-Shirt",
|
|
51
|
-
skuName: "T-Shirt M",
|
|
52
|
-
price: 9990,
|
|
53
|
-
listPrice: 12990,
|
|
54
|
-
sellingPrice: 9990,
|
|
55
|
-
quantity: 2,
|
|
56
|
-
seller: "1",
|
|
57
|
-
imageUrl: "http://example.com/img.jpg",
|
|
58
|
-
detailUrl: "/p/t-shirt",
|
|
59
|
-
additionalInfo: { brandName: "Brand", brandId: "b-1" },
|
|
60
|
-
productCategoryIds: "/123/",
|
|
61
|
-
productCategories: { "123": "Clothing" },
|
|
62
|
-
availability: "available",
|
|
63
|
-
measurementUnit: "un",
|
|
64
|
-
unitMultiplier: 1,
|
|
65
|
-
...overrides,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function tot(id: string, value: number, name = id): Totalizer {
|
|
70
|
-
return { id, name, value };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
describe("vtexOrderFormToMinicart", () => {
|
|
74
|
-
it("returns an empty cart shape for an empty OrderForm", () => {
|
|
75
|
-
const minicart = vtexOrderFormToMinicart(makeOrderForm());
|
|
76
|
-
expect(minicart.storefront.items).toEqual([]);
|
|
77
|
-
expect(minicart.storefront.subtotal).toBe(0);
|
|
78
|
-
expect(minicart.storefront.discounts).toBe(0);
|
|
79
|
-
expect(minicart.storefront.total).toBe(0);
|
|
80
|
-
expect(minicart.storefront.shipping).toBeUndefined();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("converts cents to major units across totalizers", () => {
|
|
84
|
-
const orderForm = makeOrderForm({
|
|
85
|
-
value: 19980,
|
|
86
|
-
totalizers: [tot("Items", 25980), tot("Discounts", -6000), tot("Shipping", 1500)],
|
|
87
|
-
items: [makeItem()],
|
|
88
|
-
});
|
|
89
|
-
const minicart = vtexOrderFormToMinicart(orderForm);
|
|
90
|
-
expect(minicart.storefront.subtotal).toBeCloseTo(259.8, 2);
|
|
91
|
-
expect(minicart.storefront.discounts).toBeCloseTo(60, 2);
|
|
92
|
-
expect(minicart.storefront.shipping).toBeCloseTo(15, 2);
|
|
93
|
-
expect(minicart.storefront.total).toBeCloseTo(199.8, 2);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it("Discounts are always non-negative even when VTEX returns a negative totalizer", () => {
|
|
97
|
-
const orderForm = makeOrderForm({
|
|
98
|
-
value: 9990,
|
|
99
|
-
totalizers: [tot("Items", 9990), tot("Discounts", -1000)],
|
|
100
|
-
items: [makeItem()],
|
|
101
|
-
});
|
|
102
|
-
const minicart = vtexOrderFormToMinicart(orderForm);
|
|
103
|
-
expect(minicart.storefront.discounts).toBe(10);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("omits shipping when no Shipping totalizer is present", () => {
|
|
107
|
-
const orderForm = makeOrderForm({
|
|
108
|
-
value: 9990,
|
|
109
|
-
totalizers: [tot("Items", 9990)],
|
|
110
|
-
items: [makeItem()],
|
|
111
|
-
});
|
|
112
|
-
const minicart = vtexOrderFormToMinicart(orderForm);
|
|
113
|
-
expect(minicart.storefront.shipping).toBeUndefined();
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it("maps OrderFormItem to MinicartItem with major-unit prices and analytics fields", () => {
|
|
117
|
-
const orderForm = makeOrderForm({
|
|
118
|
-
value: 19980,
|
|
119
|
-
totalizers: [tot("Items", 19980)],
|
|
120
|
-
items: [makeItem()],
|
|
121
|
-
});
|
|
122
|
-
const minicart = vtexOrderFormToMinicart(orderForm);
|
|
123
|
-
const item = minicart.storefront.items[0];
|
|
124
|
-
expect(item.item_id).toBe("sku-1");
|
|
125
|
-
expect(item.item_group_id).toBe("p-1");
|
|
126
|
-
expect(item.item_name).toBe("T-Shirt");
|
|
127
|
-
expect(item.item_brand).toBe("Brand");
|
|
128
|
-
expect(item.item_url).toBe("/p/t-shirt");
|
|
129
|
-
expect(item.price).toBeCloseTo(99.9, 2);
|
|
130
|
-
expect(item.listPrice).toBeCloseTo(129.9, 2);
|
|
131
|
-
expect(item.discount).toBeCloseTo(30, 2);
|
|
132
|
-
expect(item.quantity).toBe(2);
|
|
133
|
-
expect(item.seller).toBe("1");
|
|
134
|
-
expect(item.affiliation).toBe("1");
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it("forces https on item images", () => {
|
|
138
|
-
const orderForm = makeOrderForm({
|
|
139
|
-
items: [makeItem({ imageUrl: "http://cdn.example.com/img.jpg" })],
|
|
140
|
-
});
|
|
141
|
-
const minicart = vtexOrderFormToMinicart(orderForm);
|
|
142
|
-
expect(minicart.storefront.items[0].image).toBe("https://cdn.example.com/img.jpg");
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it("propagates the marketing coupon onto every item and the storefront root", () => {
|
|
146
|
-
const orderForm = makeOrderForm({
|
|
147
|
-
items: [makeItem(), makeItem({ uniqueId: "uid-2", id: "sku-2" })],
|
|
148
|
-
marketingData: { coupon: "SAVE10" },
|
|
149
|
-
});
|
|
150
|
-
const minicart = vtexOrderFormToMinicart(orderForm);
|
|
151
|
-
expect(minicart.storefront.coupon).toBe("SAVE10");
|
|
152
|
-
expect(minicart.storefront.items[0].coupon).toBe("SAVE10");
|
|
153
|
-
expect(minicart.storefront.items[1].coupon).toBe("SAVE10");
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it("uses opts overrides for free-shipping target, locale, checkout href and coupon toggle", () => {
|
|
157
|
-
const minicart = vtexOrderFormToMinicart(makeOrderForm(), {
|
|
158
|
-
freeShippingTarget: 250,
|
|
159
|
-
locale: "en-US",
|
|
160
|
-
checkoutHref: "/cart/go",
|
|
161
|
-
enableCoupon: false,
|
|
162
|
-
});
|
|
163
|
-
expect(minicart.storefront.freeShippingTarget).toBe(250);
|
|
164
|
-
expect(minicart.storefront.locale).toBe("en-US");
|
|
165
|
-
expect(minicart.storefront.checkoutHref).toBe("/cart/go");
|
|
166
|
-
expect(minicart.storefront.enableCoupon).toBe(false);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it("infers pt-BR locale from BRA countryCode when no override is given", () => {
|
|
170
|
-
const orderForm = makeOrderForm({
|
|
171
|
-
clientPreferencesData: { locale: "" },
|
|
172
|
-
storePreferencesData: { ...baseStorePreferences, countryCode: "BRA" },
|
|
173
|
-
});
|
|
174
|
-
const minicart = vtexOrderFormToMinicart(orderForm);
|
|
175
|
-
expect(minicart.storefront.locale).toBe("pt-BR");
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it("preserves the raw OrderForm under .original for site escape hatches", () => {
|
|
179
|
-
const orderForm = makeOrderForm({ orderFormId: "of-original" });
|
|
180
|
-
const minicart = vtexOrderFormToMinicart(orderForm);
|
|
181
|
-
expect(minicart.original).toBe(orderForm);
|
|
182
|
-
expect(minicart.original.orderFormId).toBe("of-original");
|
|
183
|
-
});
|
|
184
|
-
});
|