@drip-apex/sdk 0.1.0 → 0.3.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/README.md +5 -0
- package/dist/bundle-report.json +584 -226
- package/dist/define.cjs +123 -0
- package/dist/define.cjs.map +7 -0
- package/dist/define.mjs +102 -0
- package/dist/define.mjs.map +7 -0
- package/dist/drip.cjs +19283 -0
- package/dist/drip.cjs.map +7 -0
- package/dist/drip.debug.js +1 -1
- package/dist/drip.debug.js.map +1 -1
- package/dist/drip.heatmap.js +1 -1
- package/dist/drip.heatmap.js.map +1 -1
- package/dist/drip.js +1 -1
- package/dist/drip.js.map +1 -1
- package/dist/drip.live.js +1 -1
- package/dist/drip.live.js.map +1 -1
- package/dist/drip.mjs +19267 -0
- package/dist/drip.mjs.map +7 -0
- package/dist/drip.replay-recorder.js +2 -0
- package/dist/drip.replay-recorder.js.map +1 -0
- package/dist/editor.js +147 -142
- package/dist/editor.js.map +4 -4
- package/dist/flags.cjs +1788 -0
- package/dist/flags.cjs.map +7 -0
- package/dist/flags.mjs +1765 -0
- package/dist/flags.mjs.map +7 -0
- package/dist/index.mjs +12 -0
- package/dist/index.mjs.map +2 -2
- package/dist/metafiles/define.cjs.meta.json +28 -0
- package/dist/metafiles/define.mjs.meta.json +32 -0
- package/dist/metafiles/drip.cjs.meta.json +3193 -0
- package/dist/metafiles/drip.debug.meta.json +2859 -574
- package/dist/metafiles/drip.heatmap.meta.json +2732 -460
- package/dist/metafiles/drip.live.meta.json +2732 -460
- package/dist/metafiles/drip.meta.json +2859 -574
- package/dist/metafiles/drip.mjs.meta.json +3235 -0
- package/dist/metafiles/drip.replay-recorder.meta.json +82 -0
- package/dist/metafiles/editor.meta.json +67 -27
- package/dist/metafiles/flags.cjs.meta.json +220 -0
- package/dist/metafiles/flags.mjs.meta.json +225 -0
- package/dist/metafiles/index.meta.json +2717 -491
- package/dist/metafiles/index.mjs.meta.json +14 -4
- package/dist/react/index.js +16636 -13533
- package/dist/react/index.js.map +4 -4
- package/dist/sri.json +20 -15
- package/dist/types/activation-runtime.d.ts +91 -0
- package/dist/types/analytics-runtime.d.ts +166 -0
- package/dist/types/antiflicker.d.ts +9 -3
- package/dist/types/assignment-cookie-sync.d.ts +31 -0
- package/dist/types/assignment-runtime.d.ts +257 -0
- package/dist/types/assignment-store.d.ts +4 -8
- package/dist/types/bucket.d.ts +1 -2
- package/dist/types/compact-store.d.ts +1 -0
- package/dist/types/config-runtime.d.ts +313 -0
- package/dist/types/consent-evaluation.d.ts +28 -0
- package/dist/types/css.d.ts +5 -3
- package/dist/types/debug-loader.d.ts +2 -0
- package/dist/types/decide-assignments.d.ts +31 -0
- package/dist/types/delivery-timing.d.ts +8 -0
- package/dist/types/early-interaction-capture.d.ts +79 -0
- package/dist/types/early-reassert.d.ts +32 -0
- package/dist/types/edge-runtime.d.ts +11 -0
- package/dist/types/editor/api.d.ts +3 -0
- package/dist/types/editor/controller.d.ts +3 -1
- package/dist/types/editor/publication-result.d.ts +11 -0
- package/dist/types/editor/types.d.ts +2 -1
- package/dist/types/editor/view-kit.d.ts +1 -1
- package/dist/types/endpoint.d.ts +8 -0
- package/dist/types/exclusion-groups.d.ts +4 -7
- package/dist/types/flags.d.ts +2 -0
- package/dist/types/goal-eligibility.d.ts +4 -0
- package/dist/types/goals.d.ts +10 -1
- package/dist/types/guards.d.ts +7 -7
- package/dist/types/handover.d.ts +12 -1
- package/dist/types/hash.d.ts +8 -4
- package/dist/types/hydration.d.ts +2 -1
- package/dist/types/index.d.ts +24 -128
- package/dist/types/init-test-helpers.d.ts +48 -0
- package/dist/types/inline-bootstrap.d.ts +26 -0
- package/dist/types/js.d.ts +3 -1
- package/dist/types/money.d.ts +12 -0
- package/dist/types/mutate.d.ts +45 -6
- package/dist/types/performance.d.ts +2 -0
- package/dist/types/persistence-decoders.d.ts +12 -0
- package/dist/types/public-api.d.ts +209 -0
- package/dist/types/qa-assistant-state.d.ts +34 -0
- package/dist/types/react/index.d.ts +28 -3
- package/dist/types/reassert-sentinel.d.ts +6 -0
- package/dist/types/redirect.d.ts +48 -0
- package/dist/types/replay-recorder-entry.d.ts +1 -0
- package/dist/types/replay-signal-state.d.ts +6 -0
- package/dist/types/replay-signals.d.ts +13 -0
- package/dist/types/replay.d.ts +110 -0
- package/dist/types/router.d.ts +12 -2
- package/dist/types/runtime-state.d.ts +166 -0
- package/dist/types/sanitize.d.ts +1 -0
- package/dist/types/shadow.d.ts +3 -0
- package/dist/types/shopify-cart.d.ts +25 -1
- package/dist/types/storefront-vitals.d.ts +24 -0
- package/dist/types/targeting-diagnostics.d.ts +1 -0
- package/dist/types/targeting.d.ts +16 -2
- package/dist/types/track.d.ts +15 -1
- package/dist/types/types.d.ts +115 -9
- package/dist/types/wait.d.ts +14 -1
- package/dist/types/weekday-schedule.d.ts +3 -0
- package/package.json +17 -8
- package/src/activation-runtime.ts +2737 -0
- package/src/analytics-runtime.ts +2175 -0
- package/src/antiflicker.test.ts +216 -44
- package/src/antiflicker.ts +103 -28
- package/src/artifact-concurrency.test.ts +1091 -0
- package/src/assignment-cookie-sync.test.ts +202 -0
- package/src/assignment-cookie-sync.ts +189 -0
- package/src/assignment-runtime.ts +3204 -0
- package/src/assignment-store.test.ts +56 -0
- package/src/assignment-store.ts +7 -38
- package/src/bucket.ts +3 -18
- package/src/commerce-analytics-lifecycle.test.ts +75 -15
- package/src/commerce-config-lifecycle.test.ts +81 -1
- package/src/commerce-price-preserve.test.ts +133 -0
- package/src/commerce-price.test.ts +97 -0
- package/src/commerce-price.ts +74 -20
- package/src/commerce-shipping.test.ts +53 -0
- package/src/commerce-shipping.ts +5 -11
- package/src/compact-store.ts +15 -0
- package/src/config-fetch.test.ts +480 -9
- package/src/config-runtime.ts +2071 -0
- package/src/consent-evaluation-borlabs.test.ts +41 -0
- package/src/consent-evaluation-cookiescript.test.ts +50 -0
- package/src/consent-evaluation.ts +151 -0
- package/src/css.test.ts +30 -0
- package/src/css.ts +120 -6
- package/src/cwv.test.ts +60 -77
- package/src/debug-loader.test.ts +214 -0
- package/src/debug-loader.ts +41 -0
- package/src/decide-assignments.test.ts +196 -0
- package/src/decide-assignments.ts +204 -0
- package/src/delivery-timing.test.ts +104 -0
- package/src/delivery-timing.ts +89 -0
- package/src/early-interaction-capture.test.ts +451 -0
- package/src/early-interaction-capture.ts +230 -0
- package/src/early-reassert.test.ts +207 -0
- package/src/early-reassert.ts +152 -0
- package/src/edge-adoption.test.ts +435 -0
- package/src/edge-runtime.ts +30 -0
- package/src/editor/api.test.ts +109 -0
- package/src/editor/api.ts +34 -3
- package/src/editor/controller.test.ts +297 -14
- package/src/editor/controller.ts +69 -5
- package/src/editor/index.test.ts +2 -0
- package/src/editor/index.ts +1 -0
- package/src/editor/publication-result.ts +58 -0
- package/src/editor/types.ts +10 -1
- package/src/editor/view-kit.ts +2 -1
- package/src/editor/view.test.ts +108 -0
- package/src/editor/view.ts +49 -7
- package/src/endpoint.test.ts +15 -0
- package/src/endpoint.ts +10 -0
- package/src/exclusion-groups.ts +8 -18
- package/src/flags.test.ts +88 -1
- package/src/flags.ts +29 -29
- package/src/global-runtime-reconciler.test.ts +271 -0
- package/src/goal-eligibility.test.ts +29 -0
- package/src/goal-eligibility.ts +17 -0
- package/src/goals.test.ts +144 -1
- package/src/goals.ts +97 -18
- package/src/guards.test.ts +99 -1
- package/src/guards.ts +34 -15
- package/src/handover-cache-order.test.ts +6 -3
- package/src/handover.test.ts +154 -8
- package/src/handover.ts +122 -12
- package/src/hash.ts +29 -54
- package/src/holdout.test.ts +26 -0
- package/src/hydration.test.ts +73 -2
- package/src/hydration.ts +18 -9
- package/src/index.ts +130 -7287
- package/src/init-analytics.test.ts +1694 -0
- package/src/init-anti-flicker.test.ts +1211 -0
- package/src/init-cart-attributes.test.ts +2003 -0
- package/src/init-config-resolution.test.ts +999 -0
- package/src/init-consent.test.ts +971 -0
- package/src/init-empty.test.ts +11 -5406
- package/src/init-integrations.test.ts +846 -0
- package/src/init-page-activation.test.ts +5064 -0
- package/src/init-qa-loading.test.ts +740 -0
- package/src/init-redirects.test.ts +2156 -0
- package/src/init-test-helpers.ts +287 -0
- package/src/inline-bootstrap.test.ts +184 -0
- package/src/inline-bootstrap.ts +73 -0
- package/src/js.test.ts +51 -1
- package/src/js.ts +86 -2
- package/src/money.test.ts +55 -1
- package/src/money.ts +104 -2
- package/src/money.web-parity.contract.test.ts +85 -0
- package/src/mutate.test.ts +474 -10
- package/src/mutate.ts +739 -230
- package/src/page-trigger.test.ts +218 -8
- package/src/performance.ts +10 -0
- package/src/persistence-decoders.ts +18 -0
- package/src/public-api.ts +1828 -0
- package/src/qa-assistant-state.test.ts +249 -3
- package/src/qa-assistant-state.ts +146 -6
- package/src/qa-assistant-view.test.ts +220 -2
- package/src/qa-assistant-view.ts +102 -5
- package/src/react/index.test.tsx +37 -0
- package/src/react/index.tsx +19 -3
- package/src/reassert-sentinel.test.ts +155 -1
- package/src/reassert-sentinel.ts +168 -8
- package/src/redirect.test.ts +541 -0
- package/src/redirect.ts +572 -0
- package/src/replay-recorder-entry.ts +115 -0
- package/src/replay-signal-state.ts +100 -0
- package/src/replay-signals.test.ts +249 -0
- package/src/replay-signals.ts +151 -0
- package/src/replay.test.ts +764 -0
- package/src/replay.ts +514 -0
- package/src/router.test.ts +61 -2
- package/src/router.ts +50 -12
- package/src/runtime-state.ts +507 -0
- package/src/runtime.test.ts +174 -7
- package/src/runtime.ts +88 -14
- package/src/sanitize.test.ts +34 -0
- package/src/sanitize.ts +23 -12
- package/src/shadow.test.ts +187 -0
- package/src/shadow.ts +99 -0
- package/src/shopify-cart.test.ts +733 -23
- package/src/shopify-cart.ts +402 -49
- package/src/storefront-vitals.test.ts +217 -0
- package/src/storefront-vitals.ts +206 -0
- package/src/surface-signals.ts +6 -0
- package/src/targeting-conformance.test.ts +9 -0
- package/src/targeting-diagnostics.ts +38 -8
- package/src/targeting-parity-corpus.json +559 -0
- package/src/targeting-parity-corpus.test.ts +18 -0
- package/src/targeting.detailed.test.ts +105 -0
- package/src/targeting.test.ts +211 -0
- package/src/targeting.ts +88 -207
- package/src/track.test.ts +147 -0
- package/src/track.ts +125 -26
- package/src/types.ts +120 -13
- package/src/wait.test.ts +23 -0
- package/src/wait.ts +129 -13
- package/src/weekday-delivery.test.ts +56 -0
- package/src/weekday-schedule-parity-corpus.json +92 -0
- package/src/weekday-schedule.test.ts +69 -0
- package/src/weekday-schedule.ts +20 -0
package/src/types.ts
CHANGED
|
@@ -29,7 +29,7 @@ export interface UrlRule {
|
|
|
29
29
|
|
|
30
30
|
export interface AttributeRule {
|
|
31
31
|
attribute: string;
|
|
32
|
-
operator: "equals" | "contains" | "exists" | "in";
|
|
32
|
+
operator: "equals" | "contains" | "exists" | "in" | "not_in";
|
|
33
33
|
value?: string | string[];
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -94,7 +94,6 @@ export interface PageTargetingRule {
|
|
|
94
94
|
advancedCondition?: {
|
|
95
95
|
code?: string;
|
|
96
96
|
description?: string;
|
|
97
|
-
pollOnRules?: boolean;
|
|
98
97
|
} | null;
|
|
99
98
|
trigger?: {
|
|
100
99
|
mode?:
|
|
@@ -130,6 +129,8 @@ export interface Targeting {
|
|
|
130
129
|
segmentRules?: SegmentTargetingRule[];
|
|
131
130
|
lifecycle?: {
|
|
132
131
|
mode?: "reset" | "deactivate" | "force_reset" | "persistent";
|
|
132
|
+
explicit?: boolean;
|
|
133
|
+
source?: "author" | "page";
|
|
133
134
|
};
|
|
134
135
|
}
|
|
135
136
|
|
|
@@ -144,11 +145,14 @@ export interface CaptureRule {
|
|
|
144
145
|
|
|
145
146
|
export interface Goal {
|
|
146
147
|
id: string;
|
|
148
|
+
name?: string;
|
|
147
149
|
type: "click" | "pageview" | "custom" | "revenue";
|
|
148
150
|
selector?: string;
|
|
149
151
|
/** Case-insensitive text content match. When set, the clicked element's textContent must include this string. */
|
|
150
152
|
textMatch?: string;
|
|
151
153
|
urlPattern?: string;
|
|
154
|
+
/** Optional Page scope. The goal is active only while this Page matches. */
|
|
155
|
+
pageId?: string;
|
|
152
156
|
/** Custom analytics event name that should count as this goal. */
|
|
153
157
|
eventName?: string;
|
|
154
158
|
countOnce?: boolean;
|
|
@@ -189,11 +193,21 @@ export interface SnippetDeliveryManifest {
|
|
|
189
193
|
criticalMutationPlan?: SnippetCriticalMutationPlanItem[];
|
|
190
194
|
}
|
|
191
195
|
|
|
196
|
+
export interface UrlBlock {
|
|
197
|
+
id: string;
|
|
198
|
+
name: string;
|
|
199
|
+
urlRules: UrlRule[];
|
|
200
|
+
mutations: Mutation[];
|
|
201
|
+
css?: string;
|
|
202
|
+
js?: string;
|
|
203
|
+
}
|
|
204
|
+
|
|
192
205
|
export interface Variation {
|
|
193
206
|
id: string;
|
|
194
207
|
name?: string;
|
|
195
208
|
weight: number;
|
|
196
209
|
mutations: Mutation[];
|
|
210
|
+
urlBlocks?: UrlBlock[];
|
|
197
211
|
value?: unknown;
|
|
198
212
|
css?: string;
|
|
199
213
|
js?: string;
|
|
@@ -203,6 +217,10 @@ export interface Variation {
|
|
|
203
217
|
externalJsUrl?: string;
|
|
204
218
|
harnessSession?: HarnessSessionInfo;
|
|
205
219
|
isControl?: boolean;
|
|
220
|
+
/** Destination for redirect experiments; absent on the control variation. */
|
|
221
|
+
redirectUrl?: string;
|
|
222
|
+
/** Persistence provenance used by runtime safety policy. */
|
|
223
|
+
artifactSource?: string;
|
|
206
224
|
}
|
|
207
225
|
|
|
208
226
|
export interface HarnessSessionInfo {
|
|
@@ -218,7 +236,14 @@ export interface Experiment {
|
|
|
218
236
|
kind?: "experiment" | "feature_flag" | "personalization";
|
|
219
237
|
name?: string;
|
|
220
238
|
status?: string;
|
|
239
|
+
runtime_disabled?: boolean | null;
|
|
240
|
+
environment_key?: string | null;
|
|
241
|
+
weekdaySchedule?: import("./weekday-schedule").WeekdaySchedule;
|
|
242
|
+
/** Delivery classification authored by Apex (`mutation` or `redirect`). */
|
|
243
|
+
experimentType?: "mutation" | "redirect";
|
|
221
244
|
targeting?: Targeting;
|
|
245
|
+
/** Page definitions used only to decide whether page-scoped goals listen. */
|
|
246
|
+
goalPageRules?: PageTargetingRule[];
|
|
222
247
|
trafficAllocation?: number;
|
|
223
248
|
assignmentEpoch?: number;
|
|
224
249
|
assignmentEpochStartedAt?: string | null;
|
|
@@ -232,6 +257,10 @@ export interface Experiment {
|
|
|
232
257
|
moves?: boolean;
|
|
233
258
|
sampleRate?: number;
|
|
234
259
|
};
|
|
260
|
+
replayCapture?: {
|
|
261
|
+
enabled?: boolean;
|
|
262
|
+
sampleRate?: number;
|
|
263
|
+
};
|
|
235
264
|
variations: Variation[];
|
|
236
265
|
hashAttribute?: string;
|
|
237
266
|
goals?: Goal[];
|
|
@@ -273,6 +302,10 @@ export interface Assignment {
|
|
|
273
302
|
experimentId: string;
|
|
274
303
|
experimentName?: string;
|
|
275
304
|
variationId: string;
|
|
305
|
+
/** Whether this assignment may render and activate page-scoped behavior here. */
|
|
306
|
+
pageActive: boolean;
|
|
307
|
+
/** Identifies the selected per-URL artifact without retaining its content. */
|
|
308
|
+
activeUrlBlockId?: string;
|
|
276
309
|
index: number;
|
|
277
310
|
mutations: Mutation[];
|
|
278
311
|
value?: unknown;
|
|
@@ -289,6 +322,8 @@ export interface Assignment {
|
|
|
289
322
|
externalJsUrl?: string;
|
|
290
323
|
harnessSession?: HarnessSessionInfo;
|
|
291
324
|
isControl: boolean;
|
|
325
|
+
redirectUrl?: string;
|
|
326
|
+
artifactSource?: string;
|
|
292
327
|
hashValue: string;
|
|
293
328
|
assignmentEpoch?: number;
|
|
294
329
|
}
|
|
@@ -296,16 +331,12 @@ export interface Assignment {
|
|
|
296
331
|
export type ExecutionMode = "auto" | "edge" | "client";
|
|
297
332
|
export type AntiFlickerMode = "scoped" | "full" | "off" | "custom";
|
|
298
333
|
export type TrackingStart = "idle" | "immediate";
|
|
299
|
-
export type CustomJsPolicy = "strict" | "hybrid"
|
|
334
|
+
export type CustomJsPolicy = "strict" | "hybrid";
|
|
300
335
|
export type PlatformHint = "auto" | "shopify" | "spa" | "shopware";
|
|
301
|
-
export type RouterMode = "auto" | "history" | "navigation" | "none";
|
|
336
|
+
export type RouterMode = "auto" | "history" | "navigation" | "polling" | "none";
|
|
302
337
|
export type ActivationMode = "auto" | "observer" | "manual";
|
|
303
338
|
export type PageTriggerMode = "direct" | "url_change" | "dom_change" | "manual";
|
|
304
|
-
export type PerfFeatureFlag =
|
|
305
|
-
| "perf_platform_router_v1"
|
|
306
|
-
| "perf_shopify_pixel_buffer_v1"
|
|
307
|
-
| "perf_spa_incremental_reapply_v1"
|
|
308
|
-
| "perf_empty_project_v1";
|
|
339
|
+
export type PerfFeatureFlag = "perf_empty_project_v1";
|
|
309
340
|
export type PerfFeatureFlags = Partial<Record<PerfFeatureFlag, boolean>>;
|
|
310
341
|
|
|
311
342
|
export interface ProjectPrerequisites {
|
|
@@ -315,7 +346,7 @@ export interface ProjectPrerequisites {
|
|
|
315
346
|
|
|
316
347
|
export interface ConsentModeSettings {
|
|
317
348
|
enabled?: boolean;
|
|
318
|
-
provider?: "custom" | "onetrust" | "usercentrics" | "cookiebot" | "ccm19" | "pandectes";
|
|
349
|
+
provider?: "custom" | "onetrust" | "usercentrics" | "cookiebot" | "ccm19" | "pandectes" | "cookiescript" | "borlabs";
|
|
319
350
|
getter?: string;
|
|
320
351
|
requiredGroup?: string;
|
|
321
352
|
}
|
|
@@ -325,6 +356,7 @@ export interface DebugSettings {
|
|
|
325
356
|
restrictMode?: "off" | "query_param" | "user_ids" | "always";
|
|
326
357
|
queryParam?: string;
|
|
327
358
|
token?: string;
|
|
359
|
+
tokenRequired?: boolean;
|
|
328
360
|
allowedUserIds?: string[];
|
|
329
361
|
persist?: boolean;
|
|
330
362
|
disableCache?: boolean;
|
|
@@ -340,12 +372,15 @@ export interface SpaSettings {
|
|
|
340
372
|
domChangeTrigger?: boolean;
|
|
341
373
|
debounceMs?: number;
|
|
342
374
|
allowIframes?: boolean;
|
|
343
|
-
applyTiming?: "immediate" | "after_hydration";
|
|
375
|
+
applyTiming?: "immediate" | "after_hydration" | "early_reassert";
|
|
344
376
|
hydrationTimeoutMs?: number;
|
|
345
377
|
}
|
|
346
378
|
|
|
347
379
|
export interface PageTriggerSettings {
|
|
348
380
|
defaultMode?: PageTriggerMode;
|
|
381
|
+
urlPollIntervalMs?: number;
|
|
382
|
+
rulePollWindowMs?: number;
|
|
383
|
+
rulePollIntervalMs?: number;
|
|
349
384
|
}
|
|
350
385
|
|
|
351
386
|
export interface ExternalTrackingSettings {
|
|
@@ -527,6 +562,27 @@ export interface ActivationDiagnostics {
|
|
|
527
562
|
experiments: ExperimentActivationDiagnostic[];
|
|
528
563
|
}
|
|
529
564
|
|
|
565
|
+
export type PageDeactivationMode =
|
|
566
|
+
| "reset"
|
|
567
|
+
| "deactivate"
|
|
568
|
+
| "force_reset"
|
|
569
|
+
| "persistent";
|
|
570
|
+
|
|
571
|
+
export interface PageActivationDiagnostic {
|
|
572
|
+
pageId: string;
|
|
573
|
+
pageName?: string;
|
|
574
|
+
active: boolean;
|
|
575
|
+
appliedArtifactCount: number;
|
|
576
|
+
alwaysEmbedInSnippet?: boolean;
|
|
577
|
+
lastTransitionMode?: PageDeactivationMode;
|
|
578
|
+
isolationFallback?: "whole_experiment";
|
|
579
|
+
isolationFallbackExperimentId?: string;
|
|
580
|
+
triggerDetector?: "history" | "polling" | "dom";
|
|
581
|
+
triggerPollAttempts?: number;
|
|
582
|
+
triggerOutcome?: "activated" | "condition_failed" | "window_expired";
|
|
583
|
+
triggerElapsedMs?: number;
|
|
584
|
+
}
|
|
585
|
+
|
|
530
586
|
export interface SdkDiagnostics {
|
|
531
587
|
/** Public diagnostics redact consent and tracking-gate internals unless SDK debug mode is enabled. */
|
|
532
588
|
mode: "public" | "debug";
|
|
@@ -549,6 +605,7 @@ export interface SdkDiagnostics {
|
|
|
549
605
|
personalizationStopAfterMatch?: boolean;
|
|
550
606
|
value?: unknown;
|
|
551
607
|
}>;
|
|
608
|
+
pageActivation: PageActivationDiagnostic[];
|
|
552
609
|
experimentsLoaded: number;
|
|
553
610
|
analyticsStarted: boolean;
|
|
554
611
|
trackerStarted: boolean;
|
|
@@ -625,11 +682,20 @@ export interface SdkDiagnostics {
|
|
|
625
682
|
mappedSignals: string[];
|
|
626
683
|
gtagNormalized?: boolean;
|
|
627
684
|
};
|
|
685
|
+
observerErrors: {
|
|
686
|
+
count: number;
|
|
687
|
+
last?: {
|
|
688
|
+
source: "route" | "data_layer" | "dom";
|
|
689
|
+
message: string;
|
|
690
|
+
at: number;
|
|
691
|
+
};
|
|
692
|
+
};
|
|
628
693
|
lastShopifyAssignmentSync?: {
|
|
629
694
|
at: number;
|
|
630
|
-
status: "pending" | "sent" | "skipped" | "failed";
|
|
695
|
+
status: "pending" | "sent" | "cookie_fallback" | "skipped" | "failed";
|
|
631
696
|
reason?: string;
|
|
632
697
|
assignmentCount: number;
|
|
698
|
+
truncatedAssignmentCount?: number;
|
|
633
699
|
};
|
|
634
700
|
lastShopifyCartSync?: {
|
|
635
701
|
at: number;
|
|
@@ -789,7 +855,27 @@ export interface CommerceOfferExperiment {
|
|
|
789
855
|
>;
|
|
790
856
|
}
|
|
791
857
|
|
|
858
|
+
export interface AggregateModeSettings {
|
|
859
|
+
enabled?: boolean;
|
|
860
|
+
constructions?: {
|
|
861
|
+
loggedInSegment?: boolean;
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
|
|
792
865
|
export interface InitConfig {
|
|
866
|
+
/** QA-only diagnostics for forced experiments omitted from the served config. */
|
|
867
|
+
qa_warnings?: Array<{
|
|
868
|
+
code: "forced_experiment_unavailable" | "edge_device_audience_unsupported";
|
|
869
|
+
experiment_id: string;
|
|
870
|
+
reason?: "archived" | "runtime_disabled" | "not_found" | "environment_mismatch";
|
|
871
|
+
/** Named audience that the edge runtime cannot reproduce. */
|
|
872
|
+
audience_name?: string;
|
|
873
|
+
}>;
|
|
874
|
+
/** Global program holdout. Missing or malformed values fail open. */
|
|
875
|
+
holdout_v1_enabled?: boolean;
|
|
876
|
+
holdout_pct?: number;
|
|
877
|
+
/** ISO timestamp for the reporting epoch that produced the holdout cohort. */
|
|
878
|
+
holdout_config_updated_at?: string | null;
|
|
793
879
|
/** Provide experiments inline OR use shopId to auto-fetch from API. */
|
|
794
880
|
experiments?: Experiment[];
|
|
795
881
|
/** Request-scoped edge geography used for targeting. */
|
|
@@ -806,18 +892,29 @@ export interface InitConfig {
|
|
|
806
892
|
};
|
|
807
893
|
/** Shop ID – when provided (without experiments), the SDK fetches running experiments from the API. */
|
|
808
894
|
shopId?: string;
|
|
895
|
+
/** Canonical storefront origin used to scope host-agnostic redirect source rules. */
|
|
896
|
+
storeOrigin?: string;
|
|
897
|
+
/** Registered public storefront origins accepted for host-agnostic redirect source rules. */
|
|
898
|
+
storeOrigins?: string[];
|
|
809
899
|
/** Base URL for event tracking & config API (e.g. "https://events.drip-apex.com"). */
|
|
810
900
|
endpoint?: string;
|
|
901
|
+
/** Prefer same-origin Shopify App Proxy delivery, with direct-worker fallback. */
|
|
902
|
+
proxyEventDelivery?: { enabled: boolean; path?: string };
|
|
903
|
+
assetFallback?: boolean;
|
|
811
904
|
/** Optional endpoint for signed storefront audit snapshots. Defaults to Apex production. */
|
|
812
905
|
auditEndpoint?: string;
|
|
813
906
|
/** Versioned editor bundle URL emitted by the Apex snippet. */
|
|
814
907
|
editorBundleUrl?: string;
|
|
908
|
+
/** Versioned replay recorder URL emitted by the Apex snippet. */
|
|
909
|
+
replayRecorderUrl?: string;
|
|
815
910
|
ingestShopId?: string;
|
|
816
911
|
ingestSignature?: string;
|
|
817
912
|
attributes?: Record<string, unknown>;
|
|
818
913
|
userId?: string;
|
|
819
914
|
url?: string;
|
|
820
915
|
autoTrack?: boolean;
|
|
916
|
+
/** Per-page storefront Core Web Vitals sample rate. Defaults to 0.1. */
|
|
917
|
+
storefrontVitalsSampleRate?: number;
|
|
821
918
|
/** Persist fetched config in localStorage for faster repeat loads. Default: true. */
|
|
822
919
|
configCache?: boolean;
|
|
823
920
|
/** Fresh cache window in ms for local config cache. Default: 300000 (5m). */
|
|
@@ -890,6 +987,14 @@ export interface InitConfig {
|
|
|
890
987
|
semanticTracking?: SemanticTrackingSettings;
|
|
891
988
|
/** Server-authoritative runtime kill switch state. */
|
|
892
989
|
runtimeControl?: RuntimeControlSettings;
|
|
990
|
+
/** Write assignment sync to a shared-domain cookie when Shopify's analytics bus is unavailable. Default: true. */
|
|
991
|
+
assignmentCookieSync?: boolean;
|
|
992
|
+
/** Legal-gated server-side aggregate-mode constructions. Missing values are off. */
|
|
993
|
+
aggregateMode?: AggregateModeSettings;
|
|
994
|
+
/** Selects whether the SDK or an external integration owns Shopify cart attribute writes. */
|
|
995
|
+
cartAttributeWrite?: "sdk" | "external";
|
|
996
|
+
/** Selects which assignment labels an external cart integration can read. */
|
|
997
|
+
cartAttributeScope?: "consented" | "all";
|
|
893
998
|
/** Snippet compiler delivery state for SDK performance telemetry. */
|
|
894
999
|
deliveryState?:
|
|
895
1000
|
| "no_visual_tests"
|
|
@@ -911,6 +1016,8 @@ export interface InitConfig {
|
|
|
911
1016
|
| "shopify_app_embed"
|
|
912
1017
|
| "legacy_shopify_redirect"
|
|
913
1018
|
| string;
|
|
1019
|
+
/** Exact snippet URL used for a per-visitor debug-build handoff. */
|
|
1020
|
+
snippetUrl?: string;
|
|
914
1021
|
/** Immutable SDK artifact version selected by the Worker. */
|
|
915
1022
|
sdkVersion?: string;
|
|
916
1023
|
/** Resolved SDK policy mode selected by the Worker. */
|
|
@@ -931,6 +1038,7 @@ export interface InitConfig {
|
|
|
931
1038
|
qa?: boolean;
|
|
932
1039
|
harness?: boolean;
|
|
933
1040
|
audit?: boolean;
|
|
1041
|
+
debugLoader?: boolean;
|
|
934
1042
|
};
|
|
935
1043
|
/** Runtime kill-switches for staged rollout features. */
|
|
936
1044
|
perfFlags?: PerfFeatureFlags;
|
|
@@ -963,7 +1071,6 @@ export interface InitConfig {
|
|
|
963
1071
|
* Custom JS execution policy. Default: "hybrid".
|
|
964
1072
|
* - "strict": no custom JS execution in production.
|
|
965
1073
|
* - "hybrid": custom JS allowed with safety guards (sourceURL, error isolation).
|
|
966
|
-
* - "flex": unrestricted custom JS (legacy behavior).
|
|
967
1074
|
*/
|
|
968
1075
|
customJsPolicy?: CustomJsPolicy;
|
|
969
1076
|
|
package/src/wait.test.ts
CHANGED
|
@@ -93,4 +93,27 @@ describe("waitForElement", () => {
|
|
|
93
93
|
vi.advanceTimersByTime(1001);
|
|
94
94
|
expect(onMiss).not.toHaveBeenCalled();
|
|
95
95
|
});
|
|
96
|
+
|
|
97
|
+
it("does not apply a pending-root match after its final owner tears down", async () => {
|
|
98
|
+
const { resetWaitEntriesForOwner, waitForElement } = await import("./wait");
|
|
99
|
+
const callback = vi.fn();
|
|
100
|
+
const node = {
|
|
101
|
+
nodeType: 1,
|
|
102
|
+
matches: vi.fn(() => {
|
|
103
|
+
resetWaitEntriesForOwner("page-a");
|
|
104
|
+
return true;
|
|
105
|
+
}),
|
|
106
|
+
querySelectorAll: vi.fn(() => []),
|
|
107
|
+
} as unknown as Element;
|
|
108
|
+
|
|
109
|
+
waitForElement(".late-node", callback, { ownerKeys: ["page-a"] });
|
|
110
|
+
observerCallback?.(
|
|
111
|
+
[{ type: "childList", addedNodes: [node] } as unknown as MutationRecord],
|
|
112
|
+
{} as MutationObserver,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
await Promise.resolve();
|
|
116
|
+
expect(callback).not.toHaveBeenCalled();
|
|
117
|
+
expect(disconnect).toHaveBeenCalled();
|
|
118
|
+
});
|
|
96
119
|
});
|
package/src/wait.ts
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { Mutation } from "./types";
|
|
2
|
-
import { applyTrackedMutation,
|
|
2
|
+
import { applyTrackedMutation, getMutationKey, rejectTrackedMutation } from "./mutate";
|
|
3
|
+
import type { MutationJournalEntry } from "./mutate";
|
|
4
|
+
import { extractCurrencyTokens, type CurrencyToken } from "./money";
|
|
5
|
+
import { onShadowMutations, queryAllDeep } from "./shadow";
|
|
3
6
|
|
|
4
7
|
type WaitEntry = {
|
|
8
|
+
seq: number;
|
|
9
|
+
artifactId: string;
|
|
10
|
+
epoch: number;
|
|
5
11
|
selector: string;
|
|
6
12
|
callback: (el: Element) => void;
|
|
7
13
|
handled: WeakSet<Element>;
|
|
8
14
|
matched: boolean;
|
|
9
15
|
expiresAt: number;
|
|
10
16
|
onMiss?: () => void;
|
|
17
|
+
ownerKeys: Set<string>;
|
|
18
|
+
requiresOwner: boolean;
|
|
19
|
+
state: "applied" | "invalidated";
|
|
11
20
|
};
|
|
12
21
|
|
|
13
22
|
const entries: WaitEntry[] = [];
|
|
@@ -16,6 +25,8 @@ let pending = false;
|
|
|
16
25
|
const pendingRoots = new Set<ParentNode>();
|
|
17
26
|
const DEFAULT_WAIT_TTL_MS = 30_000;
|
|
18
27
|
let expiryTimer: ReturnType<typeof setTimeout> | null = null;
|
|
28
|
+
let waitSequence = 0;
|
|
29
|
+
let stopShadowListener: (() => void) | null = null;
|
|
19
30
|
|
|
20
31
|
function isElement(node: Node): node is Element {
|
|
21
32
|
return node.nodeType === Node.ELEMENT_NODE;
|
|
@@ -27,6 +38,7 @@ function hasQuerySelectorAll(node: ParentNode): node is ParentNode & { querySele
|
|
|
27
38
|
|
|
28
39
|
function applyToEntry(entry: WaitEntry, element: Element): void {
|
|
29
40
|
if (entry.expiresAt <= Date.now()) return;
|
|
41
|
+
if (entry.requiresOwner && entry.ownerKeys.size === 0) return;
|
|
30
42
|
if (entry.handled.has(element)) return;
|
|
31
43
|
entry.handled.add(element);
|
|
32
44
|
entry.matched = true;
|
|
@@ -69,6 +81,8 @@ function pruneExpiredEntries(): void {
|
|
|
69
81
|
pending = false;
|
|
70
82
|
pendingRoots.clear();
|
|
71
83
|
clearExpiryTimer();
|
|
84
|
+
stopShadowListener?.();
|
|
85
|
+
stopShadowListener = null;
|
|
72
86
|
} else {
|
|
73
87
|
scheduleExpiryPrune();
|
|
74
88
|
}
|
|
@@ -116,7 +130,7 @@ function checkAllEntries(): void {
|
|
|
116
130
|
const selectorMap = buildSelectorMap();
|
|
117
131
|
for (const [selector, selectorEntries] of selectorMap) {
|
|
118
132
|
try {
|
|
119
|
-
const elements =
|
|
133
|
+
const elements = queryAllDeep(selector);
|
|
120
134
|
elements.forEach((el) => {
|
|
121
135
|
selectorEntries.forEach((entry) => applyToEntry(entry, el));
|
|
122
136
|
});
|
|
@@ -177,17 +191,34 @@ function ensureObserver(): void {
|
|
|
177
191
|
childList: true,
|
|
178
192
|
subtree: true,
|
|
179
193
|
});
|
|
194
|
+
stopShadowListener ??= onShadowMutations((records) => {
|
|
195
|
+
for (const record of records) {
|
|
196
|
+
if (record.type === "childList") {
|
|
197
|
+
record.addedNodes.forEach((node) => pendingRoots.add(node as ParentNode));
|
|
198
|
+
} else {
|
|
199
|
+
pendingRoots.add(record.target as ParentNode);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (pendingRoots.size > 0) debouncedCheck();
|
|
203
|
+
});
|
|
180
204
|
}
|
|
181
205
|
|
|
182
206
|
export function waitForElement(
|
|
183
207
|
selector: string,
|
|
184
208
|
callback: (el: Element) => void,
|
|
185
|
-
options: {
|
|
209
|
+
options: {
|
|
210
|
+
ttlMs?: number;
|
|
211
|
+
onMiss?: () => void;
|
|
212
|
+
ownerKeys?: readonly string[];
|
|
213
|
+
ownerKeysSet?: Set<string>;
|
|
214
|
+
artifactId?: string;
|
|
215
|
+
epoch?: number;
|
|
216
|
+
} = {},
|
|
186
217
|
): void {
|
|
187
218
|
const handled = new WeakSet<Element>();
|
|
188
219
|
let matched = false;
|
|
189
220
|
try {
|
|
190
|
-
const existing =
|
|
221
|
+
const existing = queryAllDeep(selector);
|
|
191
222
|
existing.forEach((el) => {
|
|
192
223
|
handled.add(el);
|
|
193
224
|
matched = true;
|
|
@@ -198,7 +229,21 @@ export function waitForElement(
|
|
|
198
229
|
return;
|
|
199
230
|
}
|
|
200
231
|
const ttlMs = Math.max(1_000, Math.min(options.ttlMs ?? DEFAULT_WAIT_TTL_MS, 120_000));
|
|
201
|
-
entries.push({
|
|
232
|
+
entries.push({
|
|
233
|
+
seq: ++waitSequence,
|
|
234
|
+
artifactId: options.artifactId ?? options.ownerKeys?.[0] ?? "wait:" + selector,
|
|
235
|
+
epoch: options.epoch ?? 0,
|
|
236
|
+
selector,
|
|
237
|
+
callback,
|
|
238
|
+
handled,
|
|
239
|
+
matched,
|
|
240
|
+
expiresAt: Date.now() + ttlMs,
|
|
241
|
+
onMiss: options.onMiss,
|
|
242
|
+
ownerKeys: options.ownerKeysSet ?? new Set(options.ownerKeys ?? []),
|
|
243
|
+
requiresOwner:
|
|
244
|
+
options.ownerKeysSet !== undefined || (options.ownerKeys?.length ?? 0) > 0,
|
|
245
|
+
state: "applied",
|
|
246
|
+
});
|
|
202
247
|
scheduleExpiryPrune();
|
|
203
248
|
ensureObserver();
|
|
204
249
|
}
|
|
@@ -206,26 +251,97 @@ export function waitForElement(
|
|
|
206
251
|
export function applyMutationWithWait(
|
|
207
252
|
mutation: Mutation,
|
|
208
253
|
index: number,
|
|
209
|
-
onApplied?: ((mutation: Mutation, element: Element, index: number) => void) | null,
|
|
254
|
+
onApplied?: ((mutation: Mutation, element: Element, index: number, journalSeq?: number) => void) | null,
|
|
210
255
|
onMiss?: ((mutation: Mutation, index: number) => void) | null,
|
|
256
|
+
ownerKeys: readonly string[] = [],
|
|
257
|
+
artifactId?: string,
|
|
258
|
+
epoch = 0,
|
|
259
|
+
pricePreservation?: {
|
|
260
|
+
enabled: boolean;
|
|
261
|
+
onViolation: (input: { selector: string; originalPriceTokens: CurrencyToken[] }) => void;
|
|
262
|
+
},
|
|
211
263
|
): void {
|
|
264
|
+
const mutableOwnerKeys = new Set(ownerKeys);
|
|
212
265
|
const key = getMutationKey(mutation, index);
|
|
266
|
+
let resolved = false;
|
|
213
267
|
waitForElement(mutation.selector, (element) => {
|
|
214
|
-
if (
|
|
215
|
-
|
|
216
|
-
|
|
268
|
+
if (resolved) return;
|
|
269
|
+
const originalPrices = pricePreservation?.enabled &&
|
|
270
|
+
(mutation.action === "html" || mutation.action === "text" || mutation.action === "setText")
|
|
271
|
+
? extractCurrencyTokens(element.textContent ?? "")
|
|
272
|
+
: [];
|
|
273
|
+
const originalHtml = originalPrices.length > 0 ? element.innerHTML : null;
|
|
274
|
+
let journalSeq: number | undefined;
|
|
275
|
+
let journalEntry: MutationJournalEntry | undefined;
|
|
276
|
+
applyTrackedMutation(
|
|
277
|
+
mutation,
|
|
278
|
+
element,
|
|
279
|
+
key,
|
|
280
|
+
Array.from(mutableOwnerKeys),
|
|
281
|
+
artifactId,
|
|
282
|
+
(entry) => { journalSeq = entry.seq; journalEntry = entry; },
|
|
283
|
+
epoch,
|
|
284
|
+
);
|
|
285
|
+
if (originalHtml !== null) {
|
|
286
|
+
const remaining = extractCurrencyTokens(element.textContent ?? "");
|
|
287
|
+
const preserved = originalPrices.every((token) => remaining.some((candidate) =>
|
|
288
|
+
candidate.symbol === token.symbol && candidate.cents === token.cents));
|
|
289
|
+
if (!preserved) {
|
|
290
|
+
rejectTrackedMutation(journalEntry, element, originalHtml);
|
|
291
|
+
pricePreservation?.onViolation({ selector: mutation.selector, originalPriceTokens: originalPrices });
|
|
292
|
+
resolved = true;
|
|
293
|
+
onMiss?.(mutation, index);
|
|
217
294
|
return;
|
|
218
295
|
}
|
|
219
|
-
applyTrackedMutation(mutation, element, key);
|
|
220
|
-
} else {
|
|
221
|
-
applyTrackedMutation(mutation, element, key);
|
|
222
296
|
}
|
|
223
|
-
onApplied?.(mutation, element, index);
|
|
297
|
+
onApplied?.(mutation, element, index, journalSeq);
|
|
224
298
|
}, {
|
|
225
299
|
onMiss: () => onMiss?.(mutation, index),
|
|
300
|
+
ownerKeysSet: mutableOwnerKeys,
|
|
301
|
+
artifactId,
|
|
302
|
+
epoch,
|
|
226
303
|
});
|
|
227
304
|
}
|
|
228
305
|
|
|
306
|
+
export function addWaitEntryOwners(
|
|
307
|
+
existingOwnerKeys: ReadonlySet<string>,
|
|
308
|
+
ownerKeys: readonly string[],
|
|
309
|
+
artifactId?: string,
|
|
310
|
+
): void {
|
|
311
|
+
if (existingOwnerKeys.size === 0 || ownerKeys.length === 0) return;
|
|
312
|
+
for (const entry of entries) {
|
|
313
|
+
if (artifactId !== undefined && entry.artifactId !== artifactId) continue;
|
|
314
|
+
if (
|
|
315
|
+
!Array.from(existingOwnerKeys).some((ownerKey) =>
|
|
316
|
+
entry.ownerKeys.has(ownerKey)
|
|
317
|
+
)
|
|
318
|
+
) continue;
|
|
319
|
+
ownerKeys.forEach((ownerKey) => entry.ownerKeys.add(ownerKey));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function resetWaitEntriesForOwner(
|
|
324
|
+
ownerKey: string,
|
|
325
|
+
artifactId?: string,
|
|
326
|
+
): void {
|
|
327
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
328
|
+
const entry = entries[i];
|
|
329
|
+
if (artifactId !== undefined && entry.artifactId !== artifactId) continue;
|
|
330
|
+
if (!entry.ownerKeys.has(ownerKey)) continue;
|
|
331
|
+
entry.ownerKeys.delete(ownerKey);
|
|
332
|
+
if (entry.ownerKeys.size === 0) entries.splice(i, 1);
|
|
333
|
+
}
|
|
334
|
+
if (entries.length > 0) {
|
|
335
|
+
scheduleExpiryPrune();
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
pending = false;
|
|
339
|
+
pendingRoots.clear();
|
|
340
|
+
observer?.disconnect();
|
|
341
|
+
observer = null;
|
|
342
|
+
clearExpiryTimer();
|
|
343
|
+
}
|
|
344
|
+
|
|
229
345
|
export function resetWaitEntries(): void {
|
|
230
346
|
entries.length = 0;
|
|
231
347
|
pending = false;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { __test } from "./index";
|
|
3
|
+
import type { Experiment } from "./types";
|
|
4
|
+
|
|
5
|
+
// Hermeticity: weekday tests pin the most recent UTC Monday at 12:00 instead
|
|
6
|
+
// of a literal calendar date.
|
|
7
|
+
const RECENT_MONDAY_NOON = (() => {
|
|
8
|
+
const d = new Date();
|
|
9
|
+
d.setUTCDate(d.getUTCDate() - ((d.getUTCDay() + 6) % 7));
|
|
10
|
+
d.setUTCHours(12, 0, 0, 0);
|
|
11
|
+
return d;
|
|
12
|
+
})();
|
|
13
|
+
|
|
14
|
+
const excludedExperiment: Experiment = {
|
|
15
|
+
id: "weekday",
|
|
16
|
+
name: "Weekday test",
|
|
17
|
+
status: "running",
|
|
18
|
+
weekdaySchedule: {
|
|
19
|
+
excludedDays: ["monday"],
|
|
20
|
+
timezone: "UTC",
|
|
21
|
+
},
|
|
22
|
+
trafficAllocation: 1,
|
|
23
|
+
variations: [{
|
|
24
|
+
id: "variation",
|
|
25
|
+
weight: 1,
|
|
26
|
+
mutations: [{ action: "text", selector: "h1", text: "Variant" }],
|
|
27
|
+
}],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
describe("SDK weekday delivery sites", () => {
|
|
31
|
+
afterEach(() => vi.useRealTimers());
|
|
32
|
+
|
|
33
|
+
it("does not collect anti-flicker selectors on an excluded day", () => {
|
|
34
|
+
vi.useFakeTimers();
|
|
35
|
+
vi.setSystemTime(new Date(RECENT_MONDAY_NOON));
|
|
36
|
+
expect(__test.collectScopedSelectors([excludedExperiment])).toEqual([]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("does not build critical-path work on an excluded day", () => {
|
|
40
|
+
vi.useFakeTimers();
|
|
41
|
+
vi.setSystemTime(new Date(RECENT_MONDAY_NOON));
|
|
42
|
+
const result = __test.buildCriticalPathAssignments(
|
|
43
|
+
[excludedExperiment],
|
|
44
|
+
[{
|
|
45
|
+
e: "weekday",
|
|
46
|
+
v: [{ id: "variation", w: 1, m: [["h1", "text", "Variant"]] }],
|
|
47
|
+
}],
|
|
48
|
+
{ url: "https://shop.test/", attributes: {}, userId: "visitor" },
|
|
49
|
+
);
|
|
50
|
+
expect(result.assignments).toEqual([]);
|
|
51
|
+
expect(result.skippedDiagnostics[0]).toMatchObject({
|
|
52
|
+
reason: "excluded_weekday",
|
|
53
|
+
gates: [{ id: "weekday", label: "Weekday schedule", status: "failed" }],
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|