@decocms/blocks 7.1.2 → 7.2.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 +10 -1
- package/src/flags/audience.ts +56 -0
- package/src/flags/everyone.ts +29 -0
- package/src/flags/flag.ts +15 -0
- package/src/flags/flags.test.ts +148 -0
- package/src/flags/multivariate/image.ts +11 -0
- package/src/flags/multivariate/message.ts +11 -0
- package/src/flags/multivariate/page.ts +16 -0
- package/src/flags/multivariate/section.ts +16 -0
- package/src/flags/multivariate.ts +1 -0
- package/src/flags/types.ts +59 -0
- package/src/flags/utils/multivariate.ts +20 -0
- package/src/hooks/Image.test.ts +100 -0
- package/src/hooks/Image.tsx +250 -0
- package/src/hooks/JsonLd.tsx +353 -0
- package/src/hooks/Picture.tsx +104 -0
- package/src/hooks/index.ts +26 -0
- package/src/matchers/builtins.test.ts +68 -1
- package/src/matchers/builtins.ts +24 -2
package/src/hooks/index.ts
CHANGED
|
@@ -2,3 +2,29 @@ export { isBelowFold, LazySection, type LazySectionProps } from "./LazySection";
|
|
|
2
2
|
export { LiveControls } from "./LiveControls";
|
|
3
3
|
export { SectionErrorBoundary } from "./SectionErrorFallback";
|
|
4
4
|
export { default as RenderSection } from "./RenderSection";
|
|
5
|
+
|
|
6
|
+
// Commerce UI primitives (moved from apps-start's commerce/components/).
|
|
7
|
+
export {
|
|
8
|
+
default as Image,
|
|
9
|
+
registerImageCdnDomain,
|
|
10
|
+
getImageCdnDomain,
|
|
11
|
+
getOptimizedMediaUrl,
|
|
12
|
+
getSrcSet,
|
|
13
|
+
FACTORS,
|
|
14
|
+
type ImageProps,
|
|
15
|
+
type FitOptions,
|
|
16
|
+
} from "./Image";
|
|
17
|
+
export { Picture, Source, type PictureProps, type SourceProps } from "./Picture";
|
|
18
|
+
export {
|
|
19
|
+
ProductJsonLd,
|
|
20
|
+
PLPJsonLd,
|
|
21
|
+
BreadcrumbJsonLd,
|
|
22
|
+
seoMetaTags,
|
|
23
|
+
type ProductJsonLdProps,
|
|
24
|
+
type PLPJsonLdProps,
|
|
25
|
+
type BreadcrumbJsonLdProps,
|
|
26
|
+
type SeoMetaProps,
|
|
27
|
+
type JsonLdProduct,
|
|
28
|
+
type JsonLdProductListingPage,
|
|
29
|
+
type JsonLdBreadcrumbList,
|
|
30
|
+
} from "./JsonLd";
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it } from "vitest";
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
import type { MatcherContext } from "../cms/resolve";
|
|
3
3
|
import { evaluateMatcher } from "../cms/resolve";
|
|
4
4
|
import { registerBuiltinMatchers } from "./builtins";
|
|
5
5
|
|
|
6
6
|
const LOCATION_KEY = "website/matchers/location.ts";
|
|
7
|
+
const DATE_KEY = "website/matchers/date.ts";
|
|
7
8
|
|
|
8
9
|
beforeEach(() => {
|
|
9
10
|
registerBuiltinMatchers();
|
|
@@ -249,3 +250,69 @@ describe("locationMatcher — data source fallbacks", () => {
|
|
|
249
250
|
expect(match({ includeLocations: [{ regionCode: "RJ" }] }, ctx)).toBe(false);
|
|
250
251
|
});
|
|
251
252
|
});
|
|
253
|
+
|
|
254
|
+
function matchDate(rule: Record<string, unknown>): boolean {
|
|
255
|
+
return evaluateMatcher({ ...rule, __resolveType: DATE_KEY }, {});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
describe("dateMatcher — parity with deco-cx/apps website/matchers/date.ts", () => {
|
|
259
|
+
beforeEach(() => {
|
|
260
|
+
vi.useFakeTimers();
|
|
261
|
+
vi.setSystemTime(new Date("2026-07-07T12:00:00Z"));
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
afterEach(() => {
|
|
265
|
+
vi.useRealTimers();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("matches when neither start nor end is given (vacuously true)", () => {
|
|
269
|
+
expect(matchDate({})).toBe(true);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("matches when now is after start and no end is given", () => {
|
|
273
|
+
expect(matchDate({ start: "2026-07-01T00:00:00Z" })).toBe(true);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("does not match when now is before start", () => {
|
|
277
|
+
expect(matchDate({ start: "2026-08-01T00:00:00Z" })).toBe(false);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("matches when now is before end and no start is given", () => {
|
|
281
|
+
expect(matchDate({ end: "2026-08-01T00:00:00Z" })).toBe(true);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("does not match when now is after end", () => {
|
|
285
|
+
expect(matchDate({ end: "2026-06-01T00:00:00Z" })).toBe(false);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("matches when now is strictly between start and end", () => {
|
|
289
|
+
expect(
|
|
290
|
+
matchDate({ start: "2026-07-01T00:00:00Z", end: "2026-07-31T00:00:00Z" }),
|
|
291
|
+
).toBe(true);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("does not match outside a [start, end] window in either direction", () => {
|
|
295
|
+
expect(
|
|
296
|
+
matchDate({ start: "2026-08-01T00:00:00Z", end: "2026-09-01T00:00:00Z" }),
|
|
297
|
+
).toBe(false);
|
|
298
|
+
expect(
|
|
299
|
+
matchDate({ start: "2026-01-01T00:00:00Z", end: "2026-02-01T00:00:00Z" }),
|
|
300
|
+
).toBe(false);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("does not match at the exact start boundary instant (strict >, not >=)", () => {
|
|
304
|
+
expect(matchDate({ start: "2026-07-07T12:00:00Z" })).toBe(false);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("does not match at the exact end boundary instant (strict <, not <=)", () => {
|
|
308
|
+
expect(matchDate({ end: "2026-07-07T12:00:00Z" })).toBe(false);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("treats an invalid start date as non-matching (NaN comparison is always false)", () => {
|
|
312
|
+
expect(matchDate({ start: "not-a-date" })).toBe(false);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("treats an invalid end date as non-matching", () => {
|
|
316
|
+
expect(matchDate({ end: "not-a-date" })).toBe(false);
|
|
317
|
+
});
|
|
318
|
+
});
|
package/src/matchers/builtins.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* These augment the matchers already handled inline in resolve.ts
|
|
5
5
|
* (always, never, device, random, utm) with the additional matchers
|
|
6
|
-
* that deco supported: cookie, cron, host, pathname, queryString,
|
|
6
|
+
* that deco supported: cookie, cron, date, host, pathname, queryString,
|
|
7
7
|
* location, userAgent, environment, multi, negate.
|
|
8
8
|
*
|
|
9
9
|
* Register these at startup:
|
|
@@ -61,6 +61,28 @@ function cronMatcher(rule: Record<string, unknown>, _ctx: MatcherContext): boole
|
|
|
61
61
|
return true;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
// -------------------------------------------------------------------------
|
|
65
|
+
// Date matcher — parity with deco-cx/apps website/matchers/date.ts
|
|
66
|
+
//
|
|
67
|
+
// Unlike cronMatcher's inclusive [start, end] window (>=, <=), date.ts uses
|
|
68
|
+
// strict inequalities: it matches only strictly *between* start and end, so
|
|
69
|
+
// a request landing on the exact boundary instant does not match. Invalid
|
|
70
|
+
// date strings produce NaN comparisons, which are always false — same
|
|
71
|
+
// "reject silently" behavior as the original (no explicit isNaN check
|
|
72
|
+
// needed to match upstream semantics).
|
|
73
|
+
// -------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
function dateMatcher(rule: Record<string, unknown>, _ctx: MatcherContext): boolean {
|
|
76
|
+
const start = rule.start as string | undefined;
|
|
77
|
+
const end = rule.end as string | undefined;
|
|
78
|
+
|
|
79
|
+
const now = new Date();
|
|
80
|
+
const afterStart = start ? now > new Date(start) : true;
|
|
81
|
+
const beforeEnd = end ? now < new Date(end) : true;
|
|
82
|
+
|
|
83
|
+
return afterStart && beforeEnd;
|
|
84
|
+
}
|
|
85
|
+
|
|
64
86
|
// -------------------------------------------------------------------------
|
|
65
87
|
// Host matcher
|
|
66
88
|
// -------------------------------------------------------------------------
|
|
@@ -425,7 +447,7 @@ function negateMatcher(rule: Record<string, unknown>, ctx: MatcherContext): bool
|
|
|
425
447
|
export function registerBuiltinMatchers(): void {
|
|
426
448
|
registerMatcher("website/matchers/cookie.ts", cookieMatcher);
|
|
427
449
|
registerMatcher("website/matchers/cron.ts", cronMatcher);
|
|
428
|
-
registerMatcher("website/matchers/date.ts",
|
|
450
|
+
registerMatcher("website/matchers/date.ts", dateMatcher);
|
|
429
451
|
registerMatcher("website/matchers/host.ts", hostMatcher);
|
|
430
452
|
registerMatcher("website/matchers/pathname.ts", pathnameMatcher);
|
|
431
453
|
registerMatcher("website/matchers/queryString.ts", queryStringMatcher);
|