@decocms/blocks 7.21.1 → 7.22.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/blocks",
3
- "version": "7.21.1",
3
+ "version": "7.22.1",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=24"
@@ -308,6 +308,37 @@ describe("commerce loader auto-injects URL search params as props", () => {
308
308
  warnSpy.mockRestore();
309
309
  }
310
310
  });
311
+
312
+ it("never auto-injects `page` — loaders that read __pageUrl apply their own index-base conversion (#391)", async () => {
313
+ const calls: Array<Record<string, unknown>> = [];
314
+ registerCommerceLoader(KEY, async (props: Record<string, unknown>) => {
315
+ calls.push({ ...props });
316
+ return null;
317
+ });
318
+
319
+ await resolveValue({ __resolveType: KEY }, undefined, {
320
+ url: "https://store.com/?page=3",
321
+ path: "/",
322
+ });
323
+
324
+ expect(calls[0]?.page).toBeUndefined();
325
+ });
326
+
327
+ it("coerces `count`/`pageOffset` to numbers instead of injecting raw strings", async () => {
328
+ const calls: Array<Record<string, unknown>> = [];
329
+ registerCommerceLoader(KEY, async (props: Record<string, unknown>) => {
330
+ calls.push({ ...props });
331
+ return null;
332
+ });
333
+
334
+ await resolveValue({ __resolveType: KEY }, undefined, {
335
+ url: "https://store.com/?count=24&pageOffset=2",
336
+ path: "/",
337
+ });
338
+
339
+ expect(calls[0]?.count).toBe(24);
340
+ expect(calls[0]?.pageOffset).toBe(2);
341
+ });
311
342
  });
312
343
 
313
344
  describe("commerce loader resolves legacy .ts-suffixed resolveType", () => {
@@ -880,7 +880,20 @@ async function internalResolve(value: unknown, rctx: ResolveContext): Promise<un
880
880
  if (URL.canParse(rctx.matcherCtx.url)) {
881
881
  const url = new URL(rctx.matcherCtx.url);
882
882
  for (const [k, v] of url.searchParams.entries()) {
883
- if (resolvedProps[k] === undefined) resolvedProps[k] = v;
883
+ if (resolvedProps[k] !== undefined) continue;
884
+ // `page` is skipped on purpose (#391): loaders that read `page`
885
+ // from `__pageUrl` themselves (e.g. VTEX's PLP loader) apply their
886
+ // own 1-indexed-URL -> 0-indexed-internal conversion. Auto-injecting
887
+ // the raw 1-indexed URL string here as `props.page` bypasses that
888
+ // conversion and produces an off-by-one, on top of arriving as an
889
+ // un-coerced string. Numeric conventional names get coerced so a
890
+ // loader declaring `count?: number` doesn't receive a raw string.
891
+ if (k === "page") continue;
892
+ if ((k === "count" || k === "pageOffset") && Number.isFinite(Number(v))) {
893
+ resolvedProps[k] = Number(v);
894
+ continue;
895
+ }
896
+ resolvedProps[k] = v;
884
897
  }
885
898
  } else {
886
899
  // Loud warning instead of silent swallow: matcherCtx.url should