@funnelsgrove/cli 0.1.87 → 0.1.89

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.
Files changed (121) hide show
  1. package/dist/apiClient.d.ts +1 -0
  2. package/dist/apiClient.js +32 -1
  3. package/funnel-contract-compatibility.json +28 -28
  4. package/package.json +3 -3
  5. package/template_docs/.funnelsgrove-docs.json +3 -3
  6. package/template_docs/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
  7. package/template_docs/funnel-docs.config.json +1 -1
  8. package/template_scaffold/.funnelsgrove-docs.json +3 -3
  9. package/template_scaffold/.funnelsgrove-scaffold.json +26 -471
  10. package/template_scaffold/PLAN.md +15 -29
  11. package/template_scaffold/context/build-context.md +3 -3
  12. package/template_scaffold/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
  13. package/template_scaffold/funnel-docs.config.json +1 -1
  14. package/template_scaffold/package-lock.json +9 -9
  15. package/template_scaffold/package.json +2 -2
  16. package/template_scaffold/public/assets/email-capture-preview.svg +5 -0
  17. package/template_scaffold/public/assets/paywall-preview.svg +6 -0
  18. package/template_scaffold/src/app/sdk/page.tsx +14 -26
  19. package/template_scaffold/src/components/FunnelFlow.tsx +8 -1
  20. package/template_scaffold/src/config/billing.plans.ts +71 -21
  21. package/template_scaffold/src/config/funnel.manifest.ts +20 -59
  22. package/template_scaffold/src/runtime/checkout-runtime-config.ts +0 -7
  23. package/template_scaffold/src/runtime/step-registry.ts +0 -28
  24. package/template_scaffold/src/steps/content/step-01.content.ts +4 -9
  25. package/template_scaffold/src/steps/content/step-32-paywall.content.ts +12 -50
  26. package/template_scaffold/src/steps/step-01.tsx +43 -50
  27. package/template_scaffold/src/steps/step-31-email-capture.tsx +18 -1
  28. package/template_scaffold/src/steps/step-32-paywall.tsx +93 -299
  29. package/template_scaffold/src/steps/step-33-subscription-started.tsx +1 -1
  30. package/template_scaffold/src/steps/step-content.registry.ts +1 -26
  31. package/template_scaffold/public/claimbee/bee.png +0 -0
  32. package/template_scaffold/public/claimbee/device-iphone.png +0 -0
  33. package/template_scaffold/public/claimbee/device-macbook.png +0 -0
  34. package/template_scaffold/public/claimbee/device-none.png +0 -0
  35. package/template_scaffold/public/claimbee/device-watch.png +0 -0
  36. package/template_scaffold/public/claimbee/glow.svg +0 -3
  37. package/template_scaffold/public/claimbee/steps-sprite-alt.png +0 -0
  38. package/template_scaffold/public/claimbee/steps-sprite-default.png +0 -0
  39. package/template_scaffold/public/claimbee/testimonial-avatar.png +0 -0
  40. package/template_scaffold/public/claimbee/testimonial-star.svg +0 -3
  41. package/template_scaffold/public/paywall/ai-claim-assistant.png +0 -0
  42. package/template_scaffold/public/paywall/claim-history.png +0 -0
  43. package/template_scaffold/public/paywall/clear-progress.png +0 -0
  44. package/template_scaffold/public/paywall/hero.png +0 -0
  45. package/template_scaffold/public/paywall/money-alerts.png +0 -0
  46. package/template_scaffold/public/paywall/money-back-badge.png +0 -0
  47. package/template_scaffold/public/paywall/paywall-image.png +0 -0
  48. package/template_scaffold/public/paywall/r1.png +0 -0
  49. package/template_scaffold/public/paywall/r2.png +0 -0
  50. package/template_scaffold/public/paywall/r3.png +0 -0
  51. package/template_scaffold/public/paywall/r4.png +0 -0
  52. package/template_scaffold/public/paywall/r5.png +0 -0
  53. package/template_scaffold/public/paywall/r6.png +0 -0
  54. package/template_scaffold/public/paywall/special-offer-gift.png +0 -0
  55. package/template_scaffold/public/please/bee.png +0 -0
  56. package/template_scaffold/public/please/device-iphone.png +0 -0
  57. package/template_scaffold/public/please/device-macbook.png +0 -0
  58. package/template_scaffold/public/please/device-none.png +0 -0
  59. package/template_scaffold/public/please/device-watch.png +0 -0
  60. package/template_scaffold/public/please/glow.svg +0 -3
  61. package/template_scaffold/public/please/steps-sprite-alt.png +0 -0
  62. package/template_scaffold/public/please/steps-sprite-default.png +0 -0
  63. package/template_scaffold/public/please/testimonial-avatar.png +0 -0
  64. package/template_scaffold/public/please/testimonial-star.svg +0 -3
  65. package/template_scaffold/public/upsell/upgrade-hero.png +0 -0
  66. package/template_scaffold/src/assets/claimbee/bee.png +0 -0
  67. package/template_scaffold/src/assets/claimbee/device-iphone.png +0 -0
  68. package/template_scaffold/src/assets/claimbee/device-macbook.png +0 -0
  69. package/template_scaffold/src/assets/claimbee/device-none.png +0 -0
  70. package/template_scaffold/src/assets/claimbee/device-watch.png +0 -0
  71. package/template_scaffold/src/assets/claimbee/glow.svg +0 -3
  72. package/template_scaffold/src/assets/claimbee/steps-sprite-alt.png +0 -0
  73. package/template_scaffold/src/assets/claimbee/steps-sprite-default.png +0 -0
  74. package/template_scaffold/src/assets/claimbee/testimonial-avatar.png +0 -0
  75. package/template_scaffold/src/assets/claimbee/testimonial-star.svg +0 -3
  76. package/template_scaffold/src/components/FunnelStepImage.tsx +0 -28
  77. package/template_scaffold/src/config/offer-sets.generated.ts +0 -4
  78. package/template_scaffold/src/steps/content/manage-subscription.content.ts +0 -62
  79. package/template_scaffold/src/steps/content/paywall.content.ts +0 -191
  80. package/template_scaffold/src/steps/content/step-02.content.ts +0 -77
  81. package/template_scaffold/src/steps/content/step-03.content.ts +0 -40
  82. package/template_scaffold/src/steps/content/step-04.content.ts +0 -49
  83. package/template_scaffold/src/steps/editor/manage-subscription.editor.ts +0 -179
  84. package/template_scaffold/src/steps/editor/paywall.editor.ts +0 -232
  85. package/template_scaffold/src/steps/editor/step-02.editor.ts +0 -41
  86. package/template_scaffold/src/steps/editor/step-03.editor.ts +0 -58
  87. package/template_scaffold/src/steps/editor/step-04.editor.ts +0 -98
  88. package/template_scaffold/src/steps/pricing/paywall-b.pricing.ts +0 -12
  89. package/template_scaffold/src/steps/step-02.tsx +0 -140
  90. package/template_scaffold/src/steps/step-03.tsx +0 -262
  91. package/template_scaffold/src/steps/step-04.tsx +0 -673
  92. package/template_scaffold/src/steps/step-32b-paywall-b.tsx +0 -35
  93. package/template_scaffold/src/steps/step-35-manage-subscription.tsx +0 -46
  94. package/template_scaffold/src/steps/use-claimbee-step-content.ts +0 -15
  95. package/template_scaffold/tests/funnel-agent-docs.test.ts +0 -751
  96. package/template_scaffold/tests/package.test.ts +0 -37
  97. package/template_scaffold/tests/src/app/[stepId]/page.test.tsx +0 -59
  98. package/template_scaffold/tests/src/app/layout.test.ts +0 -26
  99. package/template_scaffold/tests/src/components/FunnelFlow.test.tsx +0 -38
  100. package/template_scaffold/tests/src/components/FunnelStepImage.test.ts +0 -15
  101. package/template_scaffold/tests/src/config/billing.plans.test.ts +0 -143
  102. package/template_scaffold/tests/src/config/funnel.manifest.test.ts +0 -166
  103. package/template_scaffold/tests/src/contract/funnel-project.validation.test.ts +0 -79
  104. package/template_scaffold/tests/src/contract/golden/golden-fixtures.test.ts +0 -190
  105. package/template_scaffold/tests/src/contract/validate-funnel.cli.test.ts +0 -191
  106. package/template_scaffold/tests/src/runtime/checkout-runtime-config.test.ts +0 -60
  107. package/template_scaffold/tests/src/runtime/funnel-runtime.test.ts +0 -25
  108. package/template_scaffold/tests/src/runtime/step-content-context.test.ts +0 -38
  109. package/template_scaffold/tests/src/runtime/use-funnel-flow-controller.integration.test.ts +0 -203
  110. package/template_scaffold/tests/src/runtime/use-funnel-flow-controller.test.ts +0 -76
  111. package/template_scaffold/tests/src/steps/paywall-checkout-options.test.ts +0 -104
  112. package/template_scaffold/tests/src/steps/shared-header-contract.test.ts +0 -29
  113. package/template_scaffold/tests/src/steps/step-01.test.ts +0 -38
  114. package/template_scaffold/tests/src/steps/step-02.test.ts +0 -43
  115. package/template_scaffold/tests/src/steps/step-03.test.ts +0 -15
  116. package/template_scaffold/tests/src/steps/step-04.test.ts +0 -32
  117. package/template_scaffold/tests/src/steps/step-31-email-capture.test.ts +0 -22
  118. package/template_scaffold/tests/src/steps/step-32-paywall.test.ts +0 -773
  119. package/template_scaffold/tests/src/steps/step-33-subscription-started.test.ts +0 -44
  120. package/template_scaffold/tests/src/steps/step-34-upsell-form.test.ts +0 -27
  121. package/template_scaffold/tests/src/steps/step-image-migration.test.ts +0 -20
@@ -1,773 +0,0 @@
1
- import { existsSync, readFileSync } from 'node:fs';
2
- import path from 'node:path';
3
- import { describe, expect, it } from 'vitest';
4
-
5
- describe('ClaimBee paywall source', () => {
6
- it('mounts environment-dependent payment hooks only inside the runtime boundary', () => {
7
- const stepSource = readFileSync(
8
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
9
- 'utf8',
10
- );
11
-
12
- expect(stepSource).toContain('PaymentRuntimeBoundary');
13
- expect(stepSource).toContain('<PaymentRuntimeBoundary');
14
- expect(stepSource).toContain('<StepPaywallRuntime');
15
- expect(stepSource.indexOf('function StepPaywallRuntime')).toBeLessThan(
16
- stepSource.indexOf('useRuntimeMode()'),
17
- );
18
- });
19
-
20
- it('uses the local checkout runtime config when creating subscription-backed custom checkout intents', () => {
21
- const stepSource = readFileSync(
22
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
23
- 'utf8',
24
- );
25
-
26
- expect(stepSource).toContain("from '@/runtime/checkout-runtime-config'");
27
- expect(stepSource).toContain('runtimeConfig: checkoutRuntimeConfig');
28
- expect(stepSource).toContain('useStripeSubscriptionCheckoutSession');
29
- expect(stepSource).not.toContain('Boolean(checkoutRuntimeConfig.funnelSdkPublishableKey)');
30
- expect(stepSource).not.toContain('createStripeSubscriptionCheckout');
31
- expect(stepSource).not.toContain('createStripePaymentIntent');
32
- expect(stepSource).not.toContain('redirectToStripeCheckout');
33
- });
34
-
35
- it('renders the custom checkout trigger and separate claim CTA', () => {
36
- const stepSource = readFileSync(
37
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
38
- 'utf8',
39
- );
40
- const contentSource = readFileSync(
41
- path.resolve(__dirname, '../../../src/steps/content/step-32-paywall.content.ts'),
42
- 'utf8',
43
- );
44
-
45
- expect(stepSource).toContain('SharedCheckoutSpecialOfferDialog');
46
- expect(stepSource).toContain('StripeSubscriptionWalletSurface');
47
- expect(stepSource).toContain('SharedStripeCheckoutV2Dialog');
48
- expect(stepSource).not.toContain('StripeExpressCheckoutElement');
49
- expect(stepSource).not.toContain('createStripeSubscriptionCheckout');
50
- expect(stepSource).not.toContain('redirectToStripeCheckout');
51
- expect(stepSource).not.toContain('createStripePaymentIntent');
52
- expect(stepSource).toContain("from '@/steps/content/step-32-paywall.content'");
53
- expect(stepSource).toContain('useClaimbeeStepContent(stepPaywallId, stepPaywallContent)');
54
- expect(contentSource).toContain("topBarDiscountLabel: '⏰ Discount Expires in'");
55
- expect(contentSource).toContain("topBarCtaLabel: 'GET MY PLAN'");
56
- expect(contentSource).toContain("cardButtonLabel: 'GET MY PLAN'");
57
- expect(contentSource).toContain("title: 'Checkout'");
58
- expect(contentSource).toContain("walletButtonLabel: 'Apple Pay'");
59
- expect(contentSource).toContain("googlePayPlaceholderLabel: 'Subscribe with Google Pay'");
60
- expect(contentSource).toContain("paypalButtonLabel: 'PayPal Buy Now'");
61
- expect(contentSource).toContain("originalPriceLabel: 'Regular Price'");
62
- expect(contentSource).toContain("discountLabelTemplate: '{percent}% OFF'");
63
- expect(contentSource).toContain("promoCodeLabel: 'Applied promo code:'");
64
- expect(contentSource).toContain("specialOffer:");
65
- expect(contentSource).toContain("src: '/paywall/special-offer-gift.png'");
66
- expect(stepSource).toContain('imageSrc={content.checkout.specialOffer.image.src}');
67
- expect(stepSource).toContain("className='paywall-v2-scroll-cta paywall-v2-vibe-cta'");
68
- expect(stepSource).toContain("className='paywall-v2-card-button paywall-v2-vibe-cta'");
69
- expect(stepSource).toContain("applePay: 'paywall-v2-apple-pay-button'");
70
- expect(stepSource).toContain("googlePay: 'paywall-v2-google-pay-button'");
71
- expect(stepSource).not.toContain('Continue with other payment options');
72
- });
73
-
74
- it('keeps the timer and top CTA in a sticky bar with minute and second labels under blue digits', () => {
75
- const stepSource = readFileSync(
76
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
77
- 'utf8',
78
- );
79
- const contentSource = readFileSync(
80
- path.resolve(__dirname, '../../../src/steps/content/step-32-paywall.content.ts'),
81
- 'utf8',
82
- );
83
-
84
- const topBarStylesStart = stepSource.indexOf('.paywall-v2-top-row {');
85
- const topBarStylesEnd = stepSource.indexOf('.paywall-v2-head {');
86
- const topBarStyles = stepSource.slice(topBarStylesStart, topBarStylesEnd);
87
- const compactTopBarStylesStart = stepSource.indexOf('@container (max-width: 429px) {');
88
- const compactTopBarStylesEnd = stepSource.indexOf(' .paywall-v2-plan-row {', compactTopBarStylesStart);
89
- const compactTopBarStyles = stepSource.slice(compactTopBarStylesStart, compactTopBarStylesEnd);
90
-
91
- expect(topBarStylesStart).toBeGreaterThan(-1);
92
- expect(topBarStyles).toContain('position: sticky;');
93
- expect(topBarStyles).toContain('top: 0;');
94
- expect(topBarStyles).toContain('z-index: 20;');
95
- expect(topBarStyles).toContain('min-height: 92px;');
96
- expect(topBarStyles).toContain('margin: 0 -24px 0;');
97
- expect(topBarStyles).toContain('padding: 8px 24px 10px;');
98
- expect(topBarStyles).toContain('background: #ecf7ff;');
99
- expect(topBarStyles).toContain('border-bottom: 1px solid rgb(63 81 181 / 10%);');
100
- expect(topBarStyles).toContain('.paywall-v2-top-label {');
101
- expect(topBarStyles).toContain('font-size: 13px;');
102
- expect(topBarStyles).toContain('.paywall-v2-top-controls {');
103
- expect(topBarStyles).toContain('font-size: 40px;');
104
- expect(topBarStyles).toContain('grid-template-columns: minmax(0, 1fr) 14px minmax(0, 1fr);');
105
- expect(topBarStyles).toContain('color: var(--color-secondary-text);');
106
- expect(topBarStyles).toContain('max-width: 188px;');
107
- expect(topBarStyles).toContain('min-height: 46px;');
108
- expect(topBarStyles).toContain('padding: 0 14px;');
109
- expect(topBarStyles).toContain('font-size: 16px;');
110
- expect(topBarStyles).toContain('background: var(--color-secondary-text);');
111
- expect(topBarStyles).toContain('border-radius: 16px;');
112
- expect(stepSource).toContain('.paywall-v2-vibe-cta:not(:disabled) {');
113
- expect(stepSource).toContain('animation: paywall-v2-cta-beat 2400ms ease-in-out infinite;');
114
- expect(stepSource).toContain('animation: paywall-v2-cta-wave 2400ms ease-out infinite;');
115
- expect(stepSource).toContain('@keyframes paywall-v2-cta-beat {');
116
- expect(stepSource).toContain('@keyframes paywall-v2-cta-wave {');
117
- expect(stepSource).toContain('@media (prefers-reduced-motion: reduce) {');
118
- expect(stepSource).toContain("className='paywall-v2-countdown-units'");
119
- expect(stepSource).toContain("className='paywall-v2-top-label'");
120
- expect(stepSource).toContain("className='paywall-v2-top-controls'");
121
- expect(contentSource).toContain("countdownUnits: ['minutes', 'seconds']");
122
- expect(stepSource).not.toContain('Discount expired');
123
- expect(stepSource).not.toContain("className='paywall-v2-countdown-label'");
124
- expect(topBarStyles).not.toContain('background: rgb(148 165 255 / 20%);');
125
- expect(topBarStyles).not.toContain('backdrop-filter: blur(12px);');
126
- expect(stepSource).toContain('padding: 0 24px 64px;');
127
- expect(stepSource).toContain('.paywall-v2-head {');
128
- expect(stepSource).toContain('margin-top: 16px;');
129
- expect(compactTopBarStylesStart).toBeGreaterThan(-1);
130
- expect(compactTopBarStylesEnd).toBeGreaterThan(compactTopBarStylesStart);
131
- expect(compactTopBarStyles).toContain('min-height: 92px;');
132
- expect(compactTopBarStyles).toContain('padding: 8px 16px 10px;');
133
- expect(compactTopBarStyles).toContain('font-size: 40px;');
134
- expect(compactTopBarStyles).toContain('min-height: 46px;');
135
- expect(compactTopBarStyles).not.toContain('min-height: 108px;');
136
- });
137
-
138
- it('scrolls the header CTA to the selected plan card and opens the custom subscription checkout', () => {
139
- const stepSource = readFileSync(
140
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
141
- 'utf8',
142
- );
143
- const contentSource = readFileSync(
144
- path.resolve(__dirname, '../../../src/steps/content/step-32-paywall.content.ts'),
145
- 'utf8',
146
- );
147
-
148
- expect(stepSource).toContain('useStripeSubscriptionCheckoutSession');
149
- expect(stepSource).toContain('plan: selectedPlan');
150
- expect(stepSource).toContain('couponId: checkoutPromoActive ? selectedDisplayPlan?.couponId ?? null : null');
151
- expect(stepSource).toContain("checkoutSession.prepareCardCheckout({ checkoutStartSource: 'checkout_render' })");
152
- expect(stepSource).toContain("successUrl.searchParams.set('source', 'stripe-elements');");
153
- expect(stepSource).not.toContain('publicAnalyticsSdk.trackCheckoutStarted({');
154
- expect(stepSource).toContain('stepContractVersion: funnelManifest.stepContractVersion');
155
- expect(stepSource).toContain("successUrl.searchParams.set('user_id', user.id);");
156
- expect(stepSource).toContain("onClick={() => void startCheckout()}");
157
- expect(stepSource).toContain("document.getElementById(`paywall-v2-plan-${currentPlanId}`)");
158
- expect(stepSource).toContain("scrollIntoView({ behavior: 'smooth', block: 'center' })");
159
- expect(stepSource).toContain("id={`paywall-v2-plan-${plan.id}`}");
160
- expect(contentSource).toContain("walletPlaceholderLabel: 'Subscribe with Apple Pay'");
161
- expect(stepSource).not.toContain('window.open(');
162
- expect(stepSource).not.toContain("className='paywall-v2-checkout-overlay'");
163
- });
164
-
165
- it('uses subscription-backed Payment Element checkout instead of a one-time PaymentIntent checkout path', () => {
166
- const stepSource = readFileSync(
167
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
168
- 'utf8',
169
- );
170
-
171
- expect(stepSource).toContain('useStripeSubscriptionCheckoutSession');
172
- expect(stepSource).toContain('checkoutSession.activeClientSecret');
173
- expect(stepSource).toContain('<SharedStripeCheckoutV2Dialog');
174
- expect(stepSource).toContain('<StripeSubscriptionWalletSurface');
175
- expect(stepSource).not.toContain('<StripeExpressCheckoutElement');
176
- expect(stepSource).not.toContain('createStripePaymentIntent');
177
- expect(stepSource).not.toContain('createStripeSubscriptionCheckout');
178
- expect(stepSource).not.toContain('redirectToStripeCheckout');
179
- expect(stepSource).not.toContain('/sdk/public/payments/intents');
180
- expect(stepSource).toContain('if (checkoutSession.loading.card) {');
181
- expect(stepSource).toContain("className='paywall-v2-loading-screen'");
182
- expect(stepSource).toContain("aria-label='Loading checkout'");
183
- });
184
-
185
- it('claims paywall completion only after verified checkout success', () => {
186
- const stepSource = readFileSync(
187
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
188
- 'utf8',
189
- );
190
- const completionHandler = stepSource.slice(
191
- stepSource.indexOf('const handleCheckoutCompleted = () => {'),
192
- stepSource.indexOf('const preparePaywallWalletCheckout'),
193
- );
194
- const checkoutStartHandler = stepSource.slice(
195
- stepSource.indexOf('const startCheckout = async () => {'),
196
- stepSource.indexOf('const handleCheckoutCompleted'),
197
- );
198
-
199
- expect(stepSource).toContain('const { user, setUser, attributes, featureFlags, completeStep } = useFunnel();');
200
- expect(completionHandler).toContain('completeStep(analyticsStepId, {');
201
- expect(completionHandler).toContain('planId: selectedDisplayPlan.id');
202
- expect(stepSource).toContain('onSuccess={handleCheckoutCompleted}');
203
- expect(stepSource).not.toContain('onPaymentInfoSubmitted=');
204
- expect(checkoutStartHandler).not.toContain('completeStep(');
205
- });
206
-
207
- it('tracks the staged discount timer, returns first checkout closes to the special offer, and forwards the active coupon into subscription checkout', () => {
208
- const stepSource = readFileSync(
209
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
210
- 'utf8',
211
- );
212
-
213
- expect(stepSource).toContain("from '@/config/billing.plans'");
214
- expect(stepSource).toContain('activeBillingDiscounts');
215
- expect(stepSource).toContain('useFunnelRuntimeConfig');
216
- expect(stepSource).toContain('resolveGeneratedOfferSetRuntime');
217
- expect(stepSource).not.toContain("from '@/config/claimbee-paywall.config'");
218
- expect(stepSource).toContain('activeBillingDiscounts.second.couponId');
219
- expect(stepSource).toContain('resolvePaywallDiscountState');
220
- expect(stepSource).toContain('advancePaywallDiscountState');
221
- expect(stepSource).toContain('serializePaywallDiscountState');
222
- expect(stepSource).toContain('readPaywallStateValue');
223
- expect(stepSource).toContain('updatePaywallStateValue');
224
- expect(stepSource).toContain('CLAIMBEE_PAYWALL_DISCOUNT_STORAGE_KEYS');
225
- expect(stepSource).toContain('const [specialOfferDialogOpen, setSpecialOfferDialogOpen] = useState(() => {');
226
- expect(stepSource).toContain('activateSecondPaywallDiscount');
227
- expect(stepSource).toContain("searchParams.get('checkout_offer') === 'first'");
228
- expect(stepSource).toContain('closeCheckoutDialog');
229
- expect(stepSource).toContain('handleSpecialOfferAccept');
230
- expect(stepSource).toContain('SharedCheckoutSpecialOfferDialog');
231
- expect(stepSource).toContain('const paywallStepRef = useRef<HTMLDivElement | null>(null);');
232
- expect(stepSource).toContain("paywallStepRef.current?.scrollTo({ top: 0, behavior: 'smooth' });");
233
- expect(stepSource).toContain("ref={paywallStepRef}");
234
- expect(stepSource).toContain('discountState.firstStartedAtMs');
235
- expect(stepSource).toContain('discountState.secondStartedAtMs');
236
- expect(stepSource).not.toContain('readWindowStorageValue');
237
- expect(stepSource).not.toContain('writeWindowStorageValue');
238
- expect(stepSource).toContain('couponId: checkoutPromoActive ? selectedDisplayPlan?.couponId ?? null : null');
239
- expect(stepSource).not.toContain('couponId: selectedDisplayPlan.hasActiveDiscount');
240
- expect(stepSource).toContain('setSpecialOfferDialogOpen(false);');
241
- expect(stepSource).not.toContain("claimbeeCheckout");
242
- expect(stepSource).not.toContain('window.history.replaceState');
243
- expect(stepSource).not.toContain('discountState.remainingSeconds % 15 === 0');
244
- });
245
-
246
- it('waits for the stored discount state before rendering the paywall and shows a white loading screen first', () => {
247
- const stepSource = readFileSync(
248
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
249
- 'utf8',
250
- );
251
- const contentSource = readFileSync(
252
- path.resolve(__dirname, '../../../src/steps/content/step-32-paywall.content.ts'),
253
- 'utf8',
254
- );
255
-
256
- expect(stepSource).toContain('const [discountStateReady, setDiscountStateReady] = useState(false);');
257
- expect(stepSource).toContain('setDiscountStateReady(true);');
258
- expect(stepSource).toContain('if (!discountStateReady) {');
259
- expect(stepSource).toContain("className='paywall-v2-loading-screen'");
260
- expect(stepSource).toContain("className='paywall-v2-loading-spinner'");
261
- expect(stepSource).toContain('background: #fff;');
262
- expect(contentSource).toContain("loadingLabel: 'Loading...'");
263
- });
264
-
265
- it('keeps checkout dialog copy in the step content contract and editor', () => {
266
- const contentSource = readFileSync(
267
- path.resolve(__dirname, '../../../src/steps/content/step-32-paywall.content.ts'),
268
- 'utf8',
269
- );
270
- const editorSource = readFileSync(
271
- path.resolve(__dirname, '../../../src/steps/editor/step-32-paywall.editor.ts'),
272
- 'utf8',
273
- );
274
-
275
- expect(contentSource).toContain("title: 'Checkout'");
276
- expect(contentSource).toContain("cardSubmitLabel: 'Confirm payment'");
277
- expect(contentSource).toContain("specialOffer:");
278
- expect(editorSource).toContain("id: 'checkout'");
279
- expect(editorSource).toContain("path: 'topBarDiscountLabel'");
280
- expect(editorSource).toContain("path: 'checkout.title'");
281
- expect(editorSource).toContain("path: 'checkout.originalPriceLabel'");
282
- expect(editorSource).toContain("path: 'checkout.promoCodeLabel'");
283
- expect(editorSource).toContain("path: 'checkout.specialOffer.image'");
284
- expect(editorSource).toContain("path: 'checkout.specialOffer.buttonLabel'");
285
- });
286
-
287
- it('passes customer email and active discount state into subscription checkout', () => {
288
- const stepSource = readFileSync(
289
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
290
- 'utf8',
291
- );
292
-
293
- expect(stepSource).toContain('customerEmail: checkoutEmail || user.email');
294
- expect(stepSource).toContain('customerEmailLabel={content.checkout.customerEmailLabel}');
295
- expect(stepSource).toContain('customerEmailPlaceholder={content.checkout.customerEmailPlaceholder}');
296
- expect(stepSource).toContain('apiService.updateUser');
297
- expect(stepSource).toContain('collectCurrentFunnelAttribution');
298
- expect(stepSource).toContain(
299
- 'const { user, setUser, attributes, featureFlags, completeStep } = useFunnel();',
300
- );
301
- expect(stepSource).toContain('const checkoutPromoActive =');
302
- expect(stepSource).toContain("discountState.status === 'active'");
303
- expect(stepSource).toContain('summaryLabel: checkoutSummaryLabel');
304
- });
305
-
306
- it('renders the checkout CTAs immediately after the plans and before the feature grid', () => {
307
- const stepSource = readFileSync(
308
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
309
- 'utf8',
310
- );
311
-
312
- const plansIndex = stepSource.indexOf("id='paywall-v2-plans'");
313
- const paymentStackIndex = stepSource.indexOf("className='paywall-v2-payment-stack'");
314
- const featureGridIndex = stepSource.indexOf("className='paywall-v2-feature-grid'");
315
-
316
- expect(plansIndex).toBeGreaterThan(-1);
317
- expect(paymentStackIndex).toBeGreaterThan(plansIndex);
318
- expect(featureGridIndex).toBeGreaterThan(paymentStackIndex);
319
- expect(stepSource).toContain('StripeSubscriptionWalletSurface');
320
- expect(stepSource).toContain('.paywall-v2-apple-pay-button,');
321
- expect(stepSource).toContain('.paywall-v2-google-pay-button {');
322
- expect(stepSource).toContain('min-height: 64px;');
323
- expect(stepSource).toContain('border-radius: 16px;');
324
- expect(stepSource).not.toContain('paywallApplePayAvailable');
325
- expect(stepSource).not.toContain('APPLE_PAY_QR_CODE_URL');
326
- expect(stepSource).toContain('beforeConfirm={preparePaywallWalletCheckout}');
327
- expect(stepSource).not.toContain("className='paywall-v2-apple-pay-express'");
328
- expect(stepSource).not.toContain("className='paywall-v2-plan-image-card'");
329
- expect(stepSource).not.toContain("src='/paywall/paywall-image.png'");
330
- });
331
-
332
- it('keeps the mobile paywall geometry inside the viewport without horizontal scroll', () => {
333
- const stepSource = readFileSync(
334
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
335
- 'utf8',
336
- );
337
-
338
- expect(stepSource).toContain('overflow-x: hidden;');
339
- expect(stepSource).toContain('grid-template-columns: minmax(128px, 164px) minmax(132px, 1fr);');
340
- expect(stepSource).toContain('width: min(100%, 390px);');
341
- expect(stepSource).toContain('margin: -12px auto 0;');
342
- expect(stepSource).toContain('min-width: 0;');
343
- expect(stepSource).toContain('width: min(34%, 118px);');
344
- expect(stepSource).toContain('@container (max-width: 429px) {');
345
- expect(stepSource).toContain('padding: 0 16px 48px;');
346
- expect(stepSource).toContain('margin: 0 -16px 0;');
347
- });
348
-
349
- it('routes Apple Pay and Google Pay controls through subscription-backed Express Checkout', () => {
350
- const stepSource = readFileSync(
351
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
352
- 'utf8',
353
- );
354
-
355
- expect(stepSource).toContain('<StripeSubscriptionWalletSurface');
356
- expect(stepSource).toContain("surfaceId={PAYWALL_WALLET_SURFACE_ID}");
357
- expect(stepSource).toContain('checkout={walletCheckoutInput}');
358
- expect(stepSource).toContain('resolveMethodClassName={resolvePaywallWalletMethodClassName}');
359
- expect(stepSource).toContain('resolveMethodWrapperClassName={resolvePaywallWalletMethodWrapperClassName}');
360
- expect(stepSource).toContain('onUnavailableClick={openCardCheckoutFromUnavailableWallet}');
361
- expect(stepSource).not.toContain('session={checkoutSession}');
362
- expect(stepSource).not.toContain('onClick={() => void startWalletCheckout()}');
363
- expect(stepSource).not.toContain('StripeExpressCheckoutElement');
364
- expect(stepSource).not.toContain('createStripeSubscriptionCheckout');
365
- expect(stepSource).not.toContain('redirectToStripeCheckout');
366
- expect(stepSource).not.toContain('applepaycode/index.html');
367
- expect(stepSource).not.toContain('href={paywallApplePayQrFallbackAllowed');
368
- });
369
-
370
- it('carries paywall wallet availability into checkout so reopened checkout keeps known wallets visible', () => {
371
- const stepSource = readFileSync(
372
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
373
- 'utf8',
374
- );
375
-
376
- expect(stepSource).toContain('type PaywallWalletAvailabilityState = Partial<Record<string, boolean>>;');
377
- expect(stepSource).toContain('useState<PaywallWalletAvailabilityState>({});');
378
- expect(stepSource).toContain('const handleWalletSurfaceAvailabilityChange = (');
379
- expect(stepSource).toContain('availability: StripeSubscriptionWalletSurfaceAvailability');
380
- expect(stepSource).toContain('paywallWalletAvailability[PAYWALL_WALLET_SURFACE_ID] ?? null');
381
- expect(stepSource).toContain('initialWalletAvailable={checkoutInitialWalletAvailable}');
382
- expect(stepSource).toContain('handleWalletSurfaceAvailabilityChange(PAYWALL_WALLET_SURFACE_ID, availability)');
383
- });
384
-
385
- it('prepares the real Apple Pay element on load instead of waiting for the placeholder tap', () => {
386
- const stepSource = readFileSync(
387
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
388
- 'utf8',
389
- );
390
-
391
- expect(stepSource).toContain('useStripeSubscriptionCheckoutSession');
392
- expect(stepSource).toContain('StripeSubscriptionWalletSurface');
393
- expect(stepSource).toContain('checkoutSession.prepareCardCheckout');
394
- expect(stepSource).toContain('checkoutSession.activeClientSecret');
395
- expect(stepSource).not.toContain('const autoWalletIntentKeyRef = useRef<string | null>(null);');
396
- expect(stepSource).not.toContain('createStripeSubscriptionCheckout');
397
- expect(stepSource).not.toContain('StripeExpressCheckoutElement');
398
- expect(stepSource).not.toContain('if (!available && clientSecret) {');
399
- });
400
-
401
- it('keeps the paywall Apple Pay control visible and confirms the subscription client secret', () => {
402
- const stepSource = readFileSync(
403
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
404
- 'utf8',
405
- );
406
-
407
- expect(stepSource).toContain('useStripeSubscriptionCheckoutSession');
408
- expect(stepSource).toContain('plan: selectedPlan');
409
- expect(stepSource).toContain('customerEmail: checkoutEmail || user.email');
410
- expect(stepSource).toContain('couponId: checkoutPromoActive ? selectedDisplayPlan?.couponId ?? null : null');
411
- expect(stepSource).toContain('summaryLabel: checkoutSummaryLabel');
412
- expect(stepSource).toContain('checkoutSession.activeClientSecret');
413
- expect(stepSource).toContain('<StripeSubscriptionWalletSurface');
414
- expect(stepSource).toContain('const walletCheckoutInput = useMemo<StripeSubscriptionWalletSurfaceCheckoutInput>');
415
- expect(stepSource).not.toContain('StripeExpressCheckoutElement');
416
- expect(stepSource).not.toContain('preparePaywallWalletSubscription');
417
- expect(stepSource).not.toContain('const handlePaywallApplePayPlaceholderClick = () => {');
418
- expect(stepSource).not.toContain('onClick={handlePaywallApplePayPlaceholderClick}');
419
- });
420
-
421
- it('leaves checkout-start ownership to the shared session-aware payment helpers', () => {
422
- const stepSource = readFileSync(
423
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
424
- 'utf8',
425
- );
426
-
427
- expect(stepSource).not.toContain('publicAnalyticsSdk');
428
- expect(stepSource).not.toContain('publicAnalyticsSdk.trackCheckoutStarted({');
429
- expect(stepSource).toContain('stepType: stepPaywall.type');
430
- expect(stepSource).toContain('value: selectedDisplayPlan.analyticsPurchaseValue');
431
- expect(stepSource).toContain("currency: 'USD'");
432
- expect(stepSource).toContain('analyticsMetadata: checkoutAnalyticsMetadata');
433
- expect(stepSource).toContain('const checkoutAnalyticsContext = useMemo(');
434
- expect(stepSource).toContain('checkoutAnalytics={checkoutAnalyticsContext}');
435
- expect(stepSource).toContain('checkoutSessionId={checkoutSession.checkoutSessionId}');
436
- expect(stepSource).not.toContain('onPaymentInfoSubmitted=');
437
- expect(stepSource).toContain("await checkoutSession.prepareCardCheckout({ checkoutStartSource: 'checkout_render' });");
438
- expect(stepSource).toContain('const handleCheckoutCompleted = () => {');
439
- expect(stepSource).not.toContain('publicAnalyticsSdk.trackCheckoutCompleted({');
440
- expect(stepSource).toContain('onSuccess={handleCheckoutCompleted}');
441
- expect(stepSource).not.toContain("eventType: 'checkout_started'");
442
- expect(stepSource).not.toContain("eventType: 'checkout_completed'");
443
- });
444
-
445
- it('delegates payment-info analytics to shared checkout components with session context', () => {
446
- const stepSource = readFileSync(
447
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
448
- 'utf8',
449
- );
450
- const analyticsMetadata = stepSource.slice(
451
- stepSource.indexOf('const checkoutAnalyticsMetadata = useMemo(() => {'),
452
- stepSource.indexOf('const checkoutAnalyticsContext = useMemo(() => {'),
453
- );
454
- const checkoutAnalyticsContext = stepSource.slice(
455
- stepSource.indexOf('const checkoutAnalyticsContext = useMemo(() => {'),
456
- stepSource.indexOf('const successReturnUrl = useMemo(() => {'),
457
- );
458
- expect(analyticsMetadata).toContain("currency: 'USD'");
459
- expect(checkoutAnalyticsContext).toContain('metadata: checkoutAnalyticsMetadata');
460
- expect(stepSource.match(/checkoutAnalytics=\{checkoutAnalyticsContext\}/g)).toHaveLength(2);
461
- expect(stepSource).toContain('checkoutSessionId={checkoutSession.checkoutSessionId}');
462
- expect(stepSource).not.toContain('trackPaymentInfoSubmitted');
463
- expect(stepSource).not.toContain('add_payment_info:');
464
- });
465
-
466
- it('uses a local three-plan catalog so the paywall shows weekly, monthly, and yearly offers', () => {
467
- const billingConfigPath = path.resolve(__dirname, '../../../src/config/billing.plans.ts');
468
- const paywallPlansHookPath = path.resolve(__dirname, './use-claimbee-paywall-plans.ts');
469
- const stepPaywallPlansPath = path.resolve(__dirname, './step-32-paywall.plans.ts');
470
- const stepPaywallOfferPath = path.resolve(__dirname, './step-32-paywall.offer.ts');
471
- const paywallConfigPath = path.resolve(__dirname, '../config/claimbee-paywall.config.ts');
472
- const billingTestConfigPath = path.resolve(__dirname, '../../../src/config/billing.test.plans.ts');
473
- const stepSource = readFileSync(
474
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
475
- 'utf8',
476
- );
477
-
478
- expect(existsSync(billingConfigPath)).toBe(true);
479
- expect(existsSync(paywallPlansHookPath)).toBe(false);
480
- expect(existsSync(stepPaywallPlansPath)).toBe(false);
481
- expect(existsSync(stepPaywallOfferPath)).toBe(false);
482
- expect(existsSync(paywallConfigPath)).toBe(false);
483
- expect(existsSync(billingTestConfigPath)).toBe(true);
484
-
485
- const billingSource = existsSync(billingConfigPath)
486
- ? readFileSync(billingConfigPath, 'utf8')
487
- : '';
488
-
489
- expect(stepSource).toContain("from '@/config/billing.plans'");
490
- expect(stepSource).not.toContain("from '@/config/claimbee-paywall.config'");
491
- expect(stepSource).toContain('resolveClaimbeeCheckoutMode(runtimeMode)');
492
- expect(stepSource).toContain('const availablePlans = useMemo(');
493
- expect(stepSource).toContain('buildConfigPaywallPlans');
494
- expect(stepSource).toContain("['primary', 'secondary', 'tertiary']");
495
- expect(stepSource).toContain('[checkoutMode, publishedOfferSetRuntime]');
496
- expect(stepSource).toContain('publishedOfferSetRuntime?.getPlanCatalog(checkoutMode)');
497
- expect(stepSource).toContain("const defaultSelectedPlan = 'secondary'");
498
- expect(stepSource).not.toContain('CLAIMBEE_PAYWALL_PLAN_ORDER');
499
- expect(stepSource).not.toContain('getOrderedPaywallPlans');
500
- expect(stepSource).toContain("from '@funnelsgrove/payments'");
501
- expect(stepSource).not.toContain('useClaimbeePaywallPlans');
502
- expect(stepSource).not.toContain("from './step-32-paywall.offer'");
503
- expect(stepSource).not.toContain('/sdk/public/payments/plans');
504
- expect(stepSource).not.toContain('claimbeeBillingPlans');
505
- expect(billingSource).toContain('createRuntimeModeBillingPlanCatalog');
506
- expect(billingSource).toContain('claimbeePlansByMode');
507
- expect(billingSource).not.toContain('runtimeEnvConfig.stripeActiveMode');
508
- expect(billingSource).toContain("title: '1-Week Plan'");
509
- expect(billingSource).toContain("title: '1-Month Plan'");
510
- expect(billingSource).toContain("title: '1-Year Plan'");
511
- expect(billingSource).toContain("featuredTag: '🔥 Most Popular Choice'");
512
- expect(billingSource).toContain("stage: 'first'");
513
- expect(billingSource).toContain("couponId: 'fg_30_off_one'");
514
- expect(billingSource).toContain('discountPercent: 30');
515
- expect(billingSource).toContain("stage: 'second'");
516
- expect(billingSource).toContain("couponId: 'fg_46_off_once'");
517
- expect(billingSource).toContain('discountPercent: 46');
518
- expect(billingSource).not.toContain('claimbeePaywallDiscounts');
519
- expect(billingSource).not.toContain('oldPriceLabel');
520
- expect(billingSource).not.toContain('perDayLabel');
521
- expect(billingSource).not.toContain('billingInterval');
522
- expect(billingSource).not.toContain('isDefault');
523
- });
524
-
525
- it('selects the monthly paywall plan by default', () => {
526
- const stepSource = readFileSync(
527
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
528
- 'utf8',
529
- );
530
-
531
- expect(stepSource).toContain("const defaultSelectedPlan = 'secondary'");
532
- expect(stepSource).toContain('availablePlans.find((plan) => plan.id === defaultSelectedPlan)?.id');
533
- expect(stepSource).not.toContain('availablePlans.find((plan) => Boolean(plan.featuredTag))?.id');
534
- });
535
-
536
- it('renders the FAQ items as a collapsed accordion by default', () => {
537
- const stepSource = readFileSync(
538
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
539
- 'utf8',
540
- );
541
- const contentSource = readFileSync(
542
- path.resolve(__dirname, '../../../src/steps/content/step-32-paywall.content.ts'),
543
- 'utf8',
544
- );
545
-
546
- expect(stepSource).toContain('const [openFaqIds, setOpenFaqIds] = useState<string[]>([])');
547
- expect(stepSource).toContain('paywall-v2-faq');
548
- expect(stepSource).toContain('paywall-v2-faq-toggle');
549
- expect(stepSource).toContain('aria-expanded={isOpen}');
550
- expect(contentSource).toContain(
551
- 'Do you only cover the big, famous lawsuits, or do you track smaller ones too?',
552
- );
553
- expect(stepSource).not.toContain("index === 0 ? 'paywall-v2-plan--featured' : ''");
554
- expect(stepSource).toContain("plan.featuredTag ? 'paywall-v2-plan--featured' : ''");
555
- });
556
-
557
- it('renders the gold promo-applied strip below the hero image', () => {
558
- const stepSource = readFileSync(
559
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
560
- 'utf8',
561
- );
562
- const contentSource = readFileSync(
563
- path.resolve(__dirname, '../../../src/steps/content/step-32-paywall.content.ts'),
564
- 'utf8',
565
- );
566
-
567
- const heroIndex = stepSource.indexOf("className='paywall-v2-hero'");
568
- const promoIndex = stepSource.indexOf("'paywall-v2-promo-banner'");
569
- const highlightIndex = stepSource.indexOf("className='paywall-v2-highlight'");
570
-
571
- expect(heroIndex).toBeGreaterThan(-1);
572
- expect(promoIndex).toBeGreaterThan(heroIndex);
573
- expect(highlightIndex).toBeGreaterThan(promoIndex);
574
- expect(contentSource).toContain("initialLabel: '% Your promo code applied!'");
575
- expect(contentSource).toContain("upgradedLabel: 'New promo code applied!'");
576
- expect(stepSource).toContain('getPaywallPromoDisplayName');
577
- expect(stepSource).toContain('promoDisplayName');
578
- expect(stepSource).toContain('discountState.previousDiscountPercent');
579
- });
580
-
581
- it('matches the Figma promo strip treatment with a notched split card', () => {
582
- const stepSource = readFileSync(
583
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
584
- 'utf8',
585
- );
586
-
587
- const promoStylesStart = stepSource.indexOf('.paywall-v2-promo-banner {');
588
- const promoStylesEnd = stepSource.indexOf('.paywall-v2-highlight {');
589
- const promoStyles = stepSource.slice(promoStylesStart, promoStylesEnd);
590
-
591
- expect(promoStylesStart).toBeGreaterThan(-1);
592
- expect(promoStyles).toContain('max-width: 382px;');
593
- expect(promoStyles).toContain('min-height: 86px;');
594
- expect(promoStyles).toContain('position: relative;');
595
- expect(promoStyles).toContain('display: flex;');
596
- expect(promoStyles).toContain('flex-direction: column;');
597
- expect(promoStyles).toContain('overflow: hidden;');
598
- expect(promoStyles).toContain('margin: -20px auto 0;');
599
- expect(promoStyles).toContain('.paywall-v2-promo-banner::before,');
600
- expect(promoStyles).toContain('.paywall-v2-promo-banner::after {');
601
- expect(promoStyles).toContain('background: #ecf7ff;');
602
- expect(promoStyles).toContain('repeating-linear-gradient(');
603
- expect(promoStyles).toContain('.paywall-v2-promo-label {');
604
- expect(promoStyles).toContain(
605
- "font-family: var(--font-family-base, Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);",
606
- );
607
- expect(promoStyles).toContain('font-size: 16px;');
608
- expect(promoStyles).toContain('font-weight: 600;');
609
- expect(promoStyles).toContain('line-height: 1.35;');
610
- expect(promoStyles).toContain('text-align: center;');
611
- expect(promoStyles).toContain('justify-content: center;');
612
- expect(promoStyles).toContain('.paywall-v2-promo-code {');
613
- expect(promoStyles).toContain('font-size: 14px;');
614
- expect(promoStyles).toContain('font-weight: 700;');
615
- expect(promoStyles).toContain('--paywall-v2-promo-code-height: 41px;');
616
- expect(promoStyles).toContain('width: 100%;');
617
- expect(promoStyles).toContain('.paywall-v2-promo-banner.is-upgraded {');
618
- expect(promoStyles).toContain('.paywall-v2-promo-upgrade-row {');
619
- expect(promoStyles).toContain('.paywall-v2-promo-upgrade-badge {');
620
- expect(promoStyles).toContain('.paywall-v2-promo-upgrade-label {');
621
- expect(promoStyles).not.toContain('.paywall-v2-promo-glyph {');
622
- expect(readFileSync(path.resolve(__dirname, '../../../src/steps/content/step-32-paywall.content.ts'), 'utf8')).toContain(
623
- "initialLabel: '% Your promo code applied!'",
624
- );
625
- expect(stepSource).not.toContain("className='paywall-v2-promo-glyph'");
626
- });
627
-
628
- it('renders the discounted per-day row and the auto-renew disclaimer below the buttons', () => {
629
- const stepSource = readFileSync(
630
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
631
- 'utf8',
632
- );
633
-
634
- expect(stepSource).toContain("'paywall-v2-plan-indicator'");
635
- expect(stepSource).toContain("className='paywall-v2-plan-divider'");
636
- expect(stepSource).toContain("className='paywall-v2-plan-per-day-price'");
637
- expect(stepSource).toContain("className='paywall-v2-plan-per-day-old'");
638
- expect(stepSource).toContain("className='paywall-v2-disclaimer'");
639
- expect(stepSource).toContain('buildPaywallRenewalDisclaimer');
640
- expect(stepSource).toContain("className='paywall-v2-disclaimer'");
641
- });
642
-
643
- it('renders paywall support from the runtime project support email', () => {
644
- const stepSource = readFileSync(
645
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
646
- 'utf8',
647
- );
648
- const contentSource = readFileSync(
649
- path.resolve(__dirname, '../../../src/steps/content/step-32-paywall.content.ts'),
650
- 'utf8',
651
- );
652
- const editorSource = readFileSync(
653
- path.resolve(__dirname, '../../../src/steps/editor/step-32-paywall.editor.ts'),
654
- 'utf8',
655
- );
656
-
657
- expect(stepSource).toContain('runtimePublicConfig.supportEmail');
658
- expect(stepSource).toContain('const supportEmail = runtimePublicConfig.supportEmail;');
659
- expect(stepSource).toContain('content.checkout.supportText');
660
- expect(stepSource).toContain("className='paywall-v2-support'");
661
- expect(stepSource).toContain('href={`mailto:${supportEmail}`}');
662
- expect(stepSource).not.toContain('mailto:hello@claimbee.app');
663
- expect(contentSource).not.toContain('supportEmail:');
664
- expect(editorSource).not.toContain("path: 'checkout.supportEmail'");
665
- });
666
-
667
- it('matches the Figma plan-card spacing so the per-day label stays aligned', () => {
668
- const stepSource = readFileSync(
669
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
670
- 'utf8',
671
- );
672
-
673
- const planStylesStart = stepSource.indexOf('.paywall-v2-plan {');
674
- const planStylesEnd = stepSource.indexOf('.paywall-v2-payment-stack {');
675
- const planStyles = stepSource.slice(planStylesStart, planStylesEnd);
676
-
677
- expect(planStylesStart).toBeGreaterThan(-1);
678
- expect(planStyles).toContain('padding: 16px 14px;');
679
- expect(planStyles).toContain('justify-content: flex-start;');
680
- expect(planStyles).toContain('align-items: center;');
681
- expect(planStyles).toContain('width: 22px;');
682
- expect(planStyles).toContain('height: 22px;');
683
- expect(planStyles).toContain('width: 7px;');
684
- expect(planStyles).toContain('height: 14px;');
685
- expect(planStyles).toContain('.paywall-v2-plan-indicator.is-selected {');
686
- expect(planStyles).toContain('background: var(--color-primary);');
687
- expect(planStyles).toContain('border-left: 1px solid rgb(31 16 0 / 8%);');
688
- expect(planStyles).toContain('text-align: center;');
689
- expect(planStyles).toContain('width: min(34%, 118px);');
690
- expect(planStyles).toContain('width: 100%;');
691
- expect(planStyles).toContain('gap: 6px;');
692
- expect(planStyles).toContain('gap: 2px;');
693
- expect(planStyles).toContain('min-height: 32px;');
694
- expect(planStyles).toContain('min-height: 92px;');
695
- expect(planStyles).toContain('font-size: 22px;');
696
- expect(planStyles).toContain('font-size: 16px;');
697
- expect(planStyles).toContain('font-size: 15px;');
698
- expect(planStyles).toContain('font-size: 30px;');
699
- expect(planStyles).toContain('color: rgba(31, 16, 0, 0.72);');
700
- expect(planStyles).toContain('color: rgba(31, 16, 0, 0.44);');
701
- expect(planStyles).toContain('font-weight: 500;');
702
- expect(planStyles).toContain('.paywall-v2-plan.is-selected .paywall-v2-plan-title,');
703
- expect(planStyles).toContain('.paywall-v2-plan.is-selected .paywall-v2-plan-per-day-amount {');
704
- expect(planStyles).toContain('color: var(--color-secondary-text);');
705
- expect(planStyles).not.toContain('align-items: flex-start;');
706
- expect(planStyles).not.toContain('.paywall-v2-plan--featured .paywall-v2-plan-row {');
707
- });
708
-
709
- it('keeps the featured plan taller so the badge sits above a full-height body', () => {
710
- const stepSource = readFileSync(
711
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
712
- 'utf8',
713
- );
714
-
715
- const planStylesStart = stepSource.indexOf('.paywall-v2-plan {');
716
- const planStylesEnd = stepSource.indexOf('.paywall-v2-payment-stack {');
717
- const planStyles = stepSource.slice(planStylesStart, planStylesEnd);
718
-
719
- expect(planStyles).toContain('.paywall-v2-plan-tag {');
720
- expect(planStyles).toContain('min-height: 32px;');
721
- expect(planStyles).toContain('.paywall-v2-plan-row {');
722
- expect(planStyles).toContain('min-height: 92px;');
723
- expect(planStyles).not.toContain('min-height: 70px;');
724
- });
725
-
726
- it('keeps the feature grid below checkout and ends the paywall with the guarantee', () => {
727
- const stepSource = readFileSync(
728
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
729
- 'utf8',
730
- );
731
-
732
- const paymentStackIndex = stepSource.indexOf("className='paywall-v2-payment-stack'");
733
- const featureGridIndex = stepSource.indexOf("className='paywall-v2-feature-grid'");
734
- const feedbackIndex = stepSource.indexOf("className='paywall-v2-feedback'");
735
- const faqIndex = stepSource.indexOf("className='paywall-v2-faq'");
736
- const guaranteeIndex = stepSource.indexOf("className='paywall-v2-guarantee-card'");
737
- const styleIndex = stepSource.lastIndexOf('<style>{stepPaywallStyles}</style>');
738
-
739
- expect(paymentStackIndex).toBeGreaterThan(-1);
740
- expect(featureGridIndex).toBeGreaterThan(paymentStackIndex);
741
- expect(feedbackIndex).toBeGreaterThan(featureGridIndex);
742
- expect(faqIndex).toBeGreaterThan(feedbackIndex);
743
- expect(guaranteeIndex).toBeGreaterThan(faqIndex);
744
- expect(styleIndex).toBeGreaterThan(guaranteeIndex);
745
- expect(stepSource.slice(guaranteeIndex, styleIndex)).not.toContain("className='paywall-v2-feature-grid'");
746
- expect(stepSource.slice(guaranteeIndex, styleIndex)).toContain('width={184}');
747
- expect(stepSource).toContain('width: 184px;');
748
- expect(stepSource).toContain('height: 184px;');
749
- expect(stepSource).toContain('object-fit: cover;');
750
- expect(stepSource).toContain('object-position: center 54%;');
751
- expect(stepSource).toContain('font-size: 22px;');
752
- expect(stepSource).toContain('white-space: nowrap;');
753
- });
754
-
755
- it('keeps the rotating reviews card at a fixed height so the paywall does not jump between stories', () => {
756
- const stepSource = readFileSync(
757
- path.resolve(__dirname, '../../../src/steps/step-32-paywall.tsx'),
758
- 'utf8',
759
- );
760
-
761
- const feedbackCardStylesStart = stepSource.indexOf('.paywall-v2-feedback-card {');
762
- const feedbackCardStylesEnd = stepSource.indexOf('.paywall-v2-feedback-quote {');
763
- const feedbackCardStyles = stepSource.slice(
764
- feedbackCardStylesStart,
765
- feedbackCardStylesEnd,
766
- );
767
-
768
- expect(feedbackCardStylesStart).toBeGreaterThan(-1);
769
- expect(feedbackCardStyles).toContain('box-sizing: border-box;');
770
- expect(feedbackCardStyles).toContain('height: 372px;');
771
- expect(feedbackCardStyles).not.toContain('min-height: 228px;');
772
- });
773
- });