@drip-apex/sdk 0.1.0 → 0.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/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 +19262 -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 +19246 -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 +16621 -13539
- 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 +27 -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 +2070 -0
- package/src/consent-evaluation-cookiescript.test.ts +50 -0
- package/src/consent-evaluation.ts +138 -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/commerce-price.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { Assignment, CommercePriceSettings } from "./types";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
currencyExponent,
|
|
4
|
+
formatCentsLike,
|
|
5
|
+
parseDisplayedCents,
|
|
6
|
+
} from "./money";
|
|
3
7
|
|
|
4
8
|
type PriceAssignmentAuthorization = (assignment: Assignment) => boolean;
|
|
5
9
|
|
|
@@ -23,8 +27,6 @@ type ShopifyWindow = Window & {
|
|
|
23
27
|
};
|
|
24
28
|
};
|
|
25
29
|
|
|
26
|
-
const ZERO_DECIMAL =
|
|
27
|
-
/^(BIF|CLP|DJF|GNF|ISK|JPY|KMF|KRW|PYG|RWF|UGX|VND|VUV|XAF|XOF|XPF)$/;
|
|
28
30
|
let observer: MutationObserver | null = null;
|
|
29
31
|
let rootObserver: MutationObserver | null = null;
|
|
30
32
|
let variantForm: HTMLFormElement | null = null;
|
|
@@ -147,6 +149,13 @@ function activeExperiments(
|
|
|
147
149
|
)
|
|
148
150
|
return [];
|
|
149
151
|
const currency = w.Shopify.currency?.active;
|
|
152
|
+
if (typeof currency !== "string") return [];
|
|
153
|
+
let minorUnits: number;
|
|
154
|
+
try {
|
|
155
|
+
minorUnits = currencyExponent(currency);
|
|
156
|
+
} catch {
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
150
159
|
const pageVariantIds = new Set(
|
|
151
160
|
meta.product.variants
|
|
152
161
|
.filter((variant) => variant.id != null)
|
|
@@ -176,7 +185,7 @@ function activeExperiments(
|
|
|
176
185
|
adjustments &&
|
|
177
186
|
targetsCurrentProduct
|
|
178
187
|
) {
|
|
179
|
-
active.push({ experiment, assignment, currency });
|
|
188
|
+
active.push({ experiment, assignment, currency, minorUnits });
|
|
180
189
|
}
|
|
181
190
|
}
|
|
182
191
|
return active;
|
|
@@ -275,6 +284,50 @@ function treatmentUnits(value: string, minorUnits: number): number | null {
|
|
|
275
284
|
return Number.isSafeInteger(units) ? units : null;
|
|
276
285
|
}
|
|
277
286
|
|
|
287
|
+
function shopifyStoredMinorUnits(
|
|
288
|
+
value: number,
|
|
289
|
+
minorUnits: number,
|
|
290
|
+
shopifyStoredScale: number,
|
|
291
|
+
): number | null {
|
|
292
|
+
if (
|
|
293
|
+
!Number.isFinite(value) ||
|
|
294
|
+
value < 0 ||
|
|
295
|
+
!Number.isSafeInteger(minorUnits) ||
|
|
296
|
+
minorUnits < 0 ||
|
|
297
|
+
!Number.isSafeInteger(shopifyStoredScale) ||
|
|
298
|
+
shopifyStoredScale <= 0
|
|
299
|
+
) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
const match = /^(\d+)(?:\.(\d+))?(?:e([+-]?\d+))?$/i.exec(value.toString());
|
|
303
|
+
if (!match) return null;
|
|
304
|
+
const fraction = match[2] ?? "";
|
|
305
|
+
const exponent = Number(match[3] ?? 0);
|
|
306
|
+
if (!Number.isSafeInteger(exponent)) return null;
|
|
307
|
+
const digits = `${match[1]}${fraction}`;
|
|
308
|
+
const decimalScale = fraction.length - exponent;
|
|
309
|
+
const scaleDelta = minorUnits - decimalScale;
|
|
310
|
+
let scaledDigits: string;
|
|
311
|
+
if (scaleDelta >= 0) {
|
|
312
|
+
scaledDigits = digits + "0".repeat(scaleDelta);
|
|
313
|
+
} else {
|
|
314
|
+
const discardedLength = -scaleDelta;
|
|
315
|
+
if (discardedLength > digits.length) {
|
|
316
|
+
if (/[^0]/.test(digits)) return null;
|
|
317
|
+
scaledDigits = "0";
|
|
318
|
+
} else {
|
|
319
|
+
const discarded = digits.slice(digits.length - discardedLength);
|
|
320
|
+
if (/[^0]/.test(discarded)) return null;
|
|
321
|
+
scaledDigits = digits.slice(0, digits.length - discardedLength) || "0";
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const scaled = Number(scaledDigits);
|
|
325
|
+
if (!Number.isSafeInteger(scaled) || scaled % shopifyStoredScale !== 0) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
return scaled / shopifyStoredScale;
|
|
329
|
+
}
|
|
330
|
+
|
|
278
331
|
function replaceNumericText(
|
|
279
332
|
value: string,
|
|
280
333
|
targets: Map<number, number>,
|
|
@@ -443,15 +496,14 @@ function swapPass(): void {
|
|
|
443
496
|
const variants = (window as ShopifyWindow).ShopifyAnalytics?.meta?.product
|
|
444
497
|
?.variants;
|
|
445
498
|
if (!active.length || !Array.isArray(variants)) return;
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
//
|
|
449
|
-
|
|
450
|
-
const shopifyStoredScale = minorUnits ? 1 : 100;
|
|
499
|
+
const minorUnits = active[0]!.minorUnits;
|
|
500
|
+
// ShopifyAnalytics uses Shopify's fixed two-decimal stored scale. This
|
|
501
|
+
// scale is independent of the display currency's ISO-4217 exponent.
|
|
502
|
+
const shopifyStoredScale = 100;
|
|
451
503
|
const published = new Map<string, number>();
|
|
452
504
|
for (const variant of variants) {
|
|
453
505
|
const price = Number(variant.price);
|
|
454
|
-
if (variant.id != null && Number.
|
|
506
|
+
if (variant.id != null && Number.isFinite(price) && price >= 0)
|
|
455
507
|
published.set(String(variant.id), price);
|
|
456
508
|
}
|
|
457
509
|
const selectedByVariant = new Map<string, string>();
|
|
@@ -514,16 +566,17 @@ function swapPass(): void {
|
|
|
514
566
|
fixedPricePerUnit,
|
|
515
567
|
minorUnits,
|
|
516
568
|
);
|
|
569
|
+
const sourceDisplayAmount = source == null
|
|
570
|
+
? null
|
|
571
|
+
: shopifyStoredMinorUnits(source, minorUnits, shopifyStoredScale);
|
|
517
572
|
// The display plane must mirror the Cart Transform's decrease-only
|
|
518
573
|
// invariant. Invalid, equal, or higher treatment prices fail closed.
|
|
519
574
|
if (
|
|
520
|
-
|
|
575
|
+
sourceDisplayAmount == null ||
|
|
521
576
|
treatmentDisplayAmount == null ||
|
|
522
|
-
treatmentDisplayAmount
|
|
577
|
+
treatmentDisplayAmount >= sourceDisplayAmount
|
|
523
578
|
)
|
|
524
579
|
continue;
|
|
525
|
-
if (source % shopifyStoredScale) continue;
|
|
526
|
-
const sourceDisplayAmount = source / shopifyStoredScale;
|
|
527
580
|
const existing = targets.get(sourceDisplayAmount);
|
|
528
581
|
targets.set(
|
|
529
582
|
sourceDisplayAmount,
|
|
@@ -539,10 +592,9 @@ function swapPass(): void {
|
|
|
539
592
|
if (compare.mode !== "set") continue;
|
|
540
593
|
const source = published.get(id);
|
|
541
594
|
const compareAmount = treatmentUnits(compare.amount, minorUnits);
|
|
542
|
-
const sourceAmount =
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
: null;
|
|
595
|
+
const sourceAmount = source == null
|
|
596
|
+
? null
|
|
597
|
+
: shopifyStoredMinorUnits(source, minorUnits, shopifyStoredScale);
|
|
546
598
|
const displayedAmount =
|
|
547
599
|
sourceAmount == null ? null : targets.get(sourceAmount) || sourceAmount;
|
|
548
600
|
if (
|
|
@@ -610,9 +662,12 @@ function swapPass(): void {
|
|
|
610
662
|
const source = selectedVariantId
|
|
611
663
|
? published.get(selectedVariantId)
|
|
612
664
|
: undefined;
|
|
665
|
+
const sourceDisplayAmount = source == null
|
|
666
|
+
? null
|
|
667
|
+
: shopifyStoredMinorUnits(source, minorUnits, shopifyStoredScale);
|
|
613
668
|
// The current price is read from Shopify and is never replaced for a
|
|
614
669
|
// compare-at-only arm. Only add merchant-authored reference framing.
|
|
615
|
-
if (amount != null &&
|
|
670
|
+
if (amount != null && sourceDisplayAmount != null) {
|
|
616
671
|
const eligible = containers.filter(
|
|
617
672
|
(container) =>
|
|
618
673
|
!isElementLinkedToDifferentProduct(container, productHandle),
|
|
@@ -636,7 +691,6 @@ function swapPass(): void {
|
|
|
636
691
|
container.parentElement?.insertBefore(node, container);
|
|
637
692
|
compareAtNodes.push(node);
|
|
638
693
|
}
|
|
639
|
-
const sourceDisplayAmount = source / shopifyStoredScale;
|
|
640
694
|
const currentText = container.textContent ?? "";
|
|
641
695
|
const parsedCurrent = parseDisplayedCents(currentText, minorUnits);
|
|
642
696
|
// A node whose text still holds several amounts is not safe to
|
|
@@ -500,6 +500,59 @@ describe("commerce shipping messaging", () => {
|
|
|
500
500
|
).toBe("fixed");
|
|
501
501
|
});
|
|
502
502
|
|
|
503
|
+
it("cancels queued placement checks and cannot re-arm them after teardown", async () => {
|
|
504
|
+
vi.useFakeTimers();
|
|
505
|
+
applyCommerceShipping(settings("banner"), assignments);
|
|
506
|
+
await vi.runAllTimersAsync();
|
|
507
|
+
expect(document.querySelector("[data-apex-shipping]")).not.toBeNull();
|
|
508
|
+
|
|
509
|
+
document.body.append(document.createElement("div"));
|
|
510
|
+
await Promise.resolve();
|
|
511
|
+
expect(vi.getTimerCount()).toBeGreaterThan(0);
|
|
512
|
+
|
|
513
|
+
cleanupCommerceShipping();
|
|
514
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
515
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
516
|
+
expect(document.querySelector("[data-apex-shipping]")).toBeNull();
|
|
517
|
+
|
|
518
|
+
document.body.append(document.createElement("div"));
|
|
519
|
+
await Promise.resolve();
|
|
520
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
it("ignores a placement-observer callback delivered after teardown", async () => {
|
|
524
|
+
vi.useFakeTimers();
|
|
525
|
+
const callbacks: MutationCallback[] = [];
|
|
526
|
+
vi.stubGlobal(
|
|
527
|
+
"MutationObserver",
|
|
528
|
+
class {
|
|
529
|
+
constructor(callback: MutationCallback) {
|
|
530
|
+
callbacks.push(callback);
|
|
531
|
+
}
|
|
532
|
+
observe() {}
|
|
533
|
+
disconnect() {}
|
|
534
|
+
takeRecords() {
|
|
535
|
+
return [];
|
|
536
|
+
}
|
|
537
|
+
},
|
|
538
|
+
);
|
|
539
|
+
applyCommerceShipping(settings("banner"), assignments);
|
|
540
|
+
await vi.runAllTimersAsync();
|
|
541
|
+
expect(document.querySelector("[data-apex-shipping]")).not.toBeNull();
|
|
542
|
+
expect(callbacks.length).toBeGreaterThan(0);
|
|
543
|
+
|
|
544
|
+
cleanupCommerceShipping();
|
|
545
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
546
|
+
// Simulate a mutation record queued before disconnect() but delivered
|
|
547
|
+
// after teardown: the stale callback must not re-arm placement work.
|
|
548
|
+
for (const callback of callbacks) {
|
|
549
|
+
callback([], {} as MutationObserver);
|
|
550
|
+
}
|
|
551
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
552
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
553
|
+
expect(document.querySelector("[data-apex-shipping]")).toBeNull();
|
|
554
|
+
});
|
|
555
|
+
|
|
503
556
|
it("re-reads after cart mutations without replacing their response", async () => {
|
|
504
557
|
applyCommerceShipping(settings(), assignments);
|
|
505
558
|
await flush();
|
package/src/commerce-shipping.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
findCartDrawerContainer,
|
|
5
5
|
installCartDrawerOpenListener,
|
|
6
6
|
} from "./surface-signals";
|
|
7
|
+
import { currencyExponent } from "./money";
|
|
7
8
|
|
|
8
9
|
type ShopifyWindow = Window & {
|
|
9
10
|
Shopify?: {
|
|
@@ -108,17 +109,7 @@ export function isShippingCartEligibilityConfirmed(
|
|
|
108
109
|
|
|
109
110
|
function currencyMinorUnits(currency: string): number | null {
|
|
110
111
|
try {
|
|
111
|
-
|
|
112
|
-
style: "currency",
|
|
113
|
-
currency,
|
|
114
|
-
}).resolvedOptions().maximumFractionDigits;
|
|
115
|
-
return typeof units === "number" &&
|
|
116
|
-
Number.isInteger(units) &&
|
|
117
|
-
units >= 0 &&
|
|
118
|
-
// ISO 4217 tops out at exponent 4 (CLF, UYW).
|
|
119
|
-
units <= 4
|
|
120
|
-
? units
|
|
121
|
-
: null;
|
|
112
|
+
return currencyExponent(currency);
|
|
122
113
|
} catch {
|
|
123
114
|
return null;
|
|
124
115
|
}
|
|
@@ -386,6 +377,9 @@ async function refreshCart(
|
|
|
386
377
|
|
|
387
378
|
function installPlacementObserver(active: ActiveShipping): void {
|
|
388
379
|
placementObserver = new MutationObserver(() => {
|
|
380
|
+
// A mutation record may already be queued when teardown disconnects the
|
|
381
|
+
// observer. Do not let that stale callback re-arm placement work.
|
|
382
|
+
if (current !== active) return;
|
|
389
383
|
if (placementCheckTimer !== null) return;
|
|
390
384
|
placementCheckTimer = window.setTimeout(() => {
|
|
391
385
|
placementCheckTimer = null;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function decodeCompactStore(raw: string | null, width: 2 | 3): Record<string, unknown[]> {
|
|
2
|
+
if (!raw) return {};
|
|
3
|
+
try {
|
|
4
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
5
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
6
|
+
const out: Record<string, unknown[]> = {};
|
|
7
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
8
|
+
if (!Array.isArray(value) || value.length < width || typeof value[0] !== "string" || typeof value[1] !== "number" || (width === 3 && typeof value[2] !== "number")) continue;
|
|
9
|
+
out[key] = value;
|
|
10
|
+
}
|
|
11
|
+
return out;
|
|
12
|
+
} catch {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
}
|