@funnelsgrove/cli 0.1.28 → 0.1.35
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 +7 -4
- package/dist/cli.d.ts +20 -0
- package/dist/cli.js +33 -8
- package/dist/githubSyncFlow.d.ts +6 -0
- package/dist/githubSyncFlow.js +55 -10
- package/funnel-contract-compatibility.json +28 -28
- package/package.json +8 -3
- package/template_docs/.funnelsgrove-docs.json +3 -3
- package/template_docs/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
- package/template_docs/funnel-docs.config.json +1 -1
- package/template_scaffold/.funnelsgrove-docs.json +3 -3
- package/template_scaffold/.funnelsgrove-scaffold.json +150 -150
- package/template_scaffold/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
- package/template_scaffold/funnel-docs.config.json +1 -1
- package/template_scaffold/package-lock.json +13 -13
- package/template_scaffold/package.json +3 -3
- package/template_scaffold/public/paywall/money-back-badge.png +0 -0
- package/template_scaffold/src/steps/step-32-paywall.tsx +2 -2
- package/template_scaffold/src/steps/step-33-subscription-started.tsx +1 -43
- package/template_scaffold/{funnel-agent-docs.test.ts → tests/funnel-agent-docs.test.ts} +4 -4
- package/template_scaffold/{package.test.ts → tests/package.test.ts} +4 -4
- package/template_scaffold/{src → tests/src}/app/layout.test.ts +2 -2
- package/template_scaffold/{src → tests/src}/components/FunnelFlow.test.tsx +2 -2
- package/template_scaffold/{src → tests/src}/config/billing.plans.test.ts +4 -4
- package/template_scaffold/{src → tests/src}/config/experiments.test.ts +1 -1
- package/template_scaffold/{src → tests/src}/config/funnel.manifest.test.ts +1 -1
- package/template_scaffold/{src → tests/src}/contract/funnel-project.validation.test.ts +2 -2
- package/template_scaffold/{src → tests/src}/contract/golden/golden-fixtures.test.ts +16 -16
- package/template_scaffold/{src → tests/src}/contract/validate-funnel.cli.test.ts +2 -2
- package/template_scaffold/{src → tests/src}/runtime/checkout-runtime-config.test.ts +3 -3
- package/template_scaffold/{src → tests/src}/runtime/funnel-runtime.test.ts +1 -1
- package/template_scaffold/{src → tests/src}/runtime/step-content-context.test.ts +1 -1
- package/template_scaffold/{src → tests/src}/runtime/use-funnel-flow-controller.integration.test.ts +4 -4
- package/template_scaffold/{src → tests/src}/runtime/use-funnel-flow-controller.test.ts +3 -3
- package/template_scaffold/{src → tests/src}/steps/paywall-checkout-options.test.ts +1 -1
- package/template_scaffold/{src → tests/src}/steps/shared-header-contract.test.ts +3 -1
- package/template_scaffold/{src → tests/src}/steps/step-02.test.ts +3 -3
- package/template_scaffold/{src → tests/src}/steps/step-03.test.ts +1 -1
- package/template_scaffold/{src → tests/src}/steps/step-04.test.ts +1 -1
- package/template_scaffold/{src → tests/src}/steps/step-31-email-capture.test.ts +1 -1
- package/template_scaffold/{src → tests/src}/steps/step-32-paywall.test.ts +43 -43
- package/template_scaffold/{src → tests/src}/steps/step-33-subscription-started.test.ts +6 -7
- package/template_scaffold/{src → tests/src}/steps/step-34-upsell-form.test.ts +3 -3
- package/template_scaffold/{src → tests/src}/steps/step-image-migration.test.ts +1 -1
- /package/template_scaffold/{src → tests/src}/app/[stepId]/page.test.tsx +0 -0
- /package/template_scaffold/{src → tests/src}/components/FunnelStepImage.test.ts +0 -0
- /package/template_scaffold/{src → tests/src}/steps/step-01.test.ts +0 -0
|
@@ -7,9 +7,9 @@ import { promisify } from 'node:util';
|
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import { describe, expect, it } from 'vitest';
|
|
9
9
|
|
|
10
|
-
import contract from '
|
|
10
|
+
import contract from '../../funnel-runtime/contracts/step-contract-v2.json';
|
|
11
11
|
|
|
12
|
-
const templateRoot = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const templateRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
13
13
|
const repoRoot = resolve(templateRoot, '../..');
|
|
14
14
|
const cliDocsRoot = join(repoRoot, 'apps/cli/template_docs');
|
|
15
15
|
|
|
@@ -34,7 +34,7 @@ const renderCodes = (values: readonly unknown[]): string => (
|
|
|
34
34
|
);
|
|
35
35
|
|
|
36
36
|
async function loadGenerator() {
|
|
37
|
-
return import('
|
|
37
|
+
return import('../../../scripts/generate-funnel-agent-docs.mjs');
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
describe('funnel agent documentation supply', () => {
|
|
@@ -340,7 +340,7 @@ describe('funnel agent documentation supply', () => {
|
|
|
340
340
|
|
|
341
341
|
expect(manifest).toMatchObject({
|
|
342
342
|
schemaVersion: 1,
|
|
343
|
-
bundleVersion: '2.0.
|
|
343
|
+
bundleVersion: '2.0.23',
|
|
344
344
|
stepContractVersion: contract.stepContractVersion,
|
|
345
345
|
contractHash: contract.contractHash,
|
|
346
346
|
});
|
|
@@ -11,10 +11,10 @@ const readPackageJson = (url: URL): PackageJson =>
|
|
|
11
11
|
|
|
12
12
|
describe('funnel-template shared package dependencies', () => {
|
|
13
13
|
it('tracks the current shared runtime, analytics, and payments package versions', () => {
|
|
14
|
-
const templatePackage = readPackageJson(new URL('
|
|
15
|
-
const runtimePackage = readPackageJson(new URL('
|
|
16
|
-
const analyticsPackage = readPackageJson(new URL('
|
|
17
|
-
const paymentsPackage = readPackageJson(new URL('
|
|
14
|
+
const templatePackage = readPackageJson(new URL('../package.json', import.meta.url));
|
|
15
|
+
const runtimePackage = readPackageJson(new URL('../../funnel-runtime/package.json', import.meta.url));
|
|
16
|
+
const analyticsPackage = readPackageJson(new URL('../../funnel-analytics/package.json', import.meta.url));
|
|
17
|
+
const paymentsPackage = readPackageJson(new URL('../../funnel-payments/package.json', import.meta.url));
|
|
18
18
|
|
|
19
19
|
expect(templatePackage.dependencies?.['@funnelsgrove/runtime']).toBe(`^${runtimePackage.version}`);
|
|
20
20
|
expect(templatePackage.dependencies?.['@funnelsgrove/analytics']).toBe(`^${analyticsPackage.version}`);
|
|
@@ -4,7 +4,7 @@ import { describe, expect, it } from 'vitest';
|
|
|
4
4
|
|
|
5
5
|
describe('RootLayout metadata source', () => {
|
|
6
6
|
it('wires project page metadata settings into Next metadata', () => {
|
|
7
|
-
const layoutSource = readFileSync(path.resolve(__dirname, '
|
|
7
|
+
const layoutSource = readFileSync(path.resolve(__dirname, '../../../src/app/layout.tsx'), 'utf8');
|
|
8
8
|
|
|
9
9
|
expect(layoutSource).toContain('runtimePublicConfig.pageTitle');
|
|
10
10
|
expect(layoutSource).toContain('runtimePublicConfig.pageDescription');
|
|
@@ -15,7 +15,7 @@ describe('RootLayout metadata source', () => {
|
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
it('renders Google tag head scripts through the shared analytics package', () => {
|
|
18
|
-
const layoutSource = readFileSync(path.resolve(__dirname, '
|
|
18
|
+
const layoutSource = readFileSync(path.resolve(__dirname, '../../../src/app/layout.tsx'), 'utf8');
|
|
19
19
|
|
|
20
20
|
expect(layoutSource).toContain("import { buildGoogleTagHeadScripts } from '@funnelsgrove/analytics'");
|
|
21
21
|
expect(layoutSource).toContain('const googleTagHeadScripts = buildGoogleTagHeadScripts();');
|
|
@@ -3,9 +3,9 @@ import { describe, expect, it } from 'vitest';
|
|
|
3
3
|
|
|
4
4
|
describe('FunnelFlow lifecycle wiring', () => {
|
|
5
5
|
it('delegates one analytics adapter and the authoritative manifest version to the shared controller', () => {
|
|
6
|
-
const flowSource = readFileSync(new URL('
|
|
6
|
+
const flowSource = readFileSync(new URL('../../../src/components/FunnelFlow.tsx', import.meta.url), 'utf8');
|
|
7
7
|
const adapterSource = readFileSync(
|
|
8
|
-
new URL('
|
|
8
|
+
new URL('../../../src/runtime/use-funnel-flow-controller.ts', import.meta.url),
|
|
9
9
|
'utf8',
|
|
10
10
|
);
|
|
11
11
|
|
|
@@ -45,7 +45,7 @@ describe('claimbee billing contract', () => {
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
it('exports minimal live billing config and normalized discounts', async () => {
|
|
48
|
-
const billingModule = await import('
|
|
48
|
+
const billingModule = await import('../../../src/config/billing.plans');
|
|
49
49
|
const {
|
|
50
50
|
billing,
|
|
51
51
|
billingDiscountList,
|
|
@@ -87,7 +87,7 @@ describe('claimbee billing contract', () => {
|
|
|
87
87
|
|
|
88
88
|
it('uses test provider price ids when Stripe mode is test', async () => {
|
|
89
89
|
stubRuntimeMode('test');
|
|
90
|
-
const { billing } = await import('
|
|
90
|
+
const { billing } = await import('../../../src/config/billing.plans');
|
|
91
91
|
|
|
92
92
|
expect(billing.plans.primary.projectPlanId).toBe(CLAIMBEE_PROJECT_PLAN_IDS.weekly);
|
|
93
93
|
expect(billing.plans.primary.providerPlanId).toBe(CLAIMBEE_TEST_PROVIDER_PLAN_IDS.weekly);
|
|
@@ -98,7 +98,7 @@ describe('claimbee billing contract', () => {
|
|
|
98
98
|
});
|
|
99
99
|
|
|
100
100
|
it('exports the split test plan catalog under the generic billingTestPlans name', async () => {
|
|
101
|
-
const billingTestModule = await import('
|
|
101
|
+
const billingTestModule = await import('../../../src/config/billing.test.plans') as Record<string, unknown>;
|
|
102
102
|
|
|
103
103
|
expect(billingTestModule.billingTestPlans).toEqual({
|
|
104
104
|
primary: expect.objectContaining({
|
|
@@ -117,7 +117,7 @@ describe('claimbee billing contract', () => {
|
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
it('lets payments helpers derive ordered paywall plans from semantic keys', async () => {
|
|
120
|
-
const { billingPlans } = await import('
|
|
120
|
+
const { billingPlans } = await import('../../../src/config/billing.plans');
|
|
121
121
|
|
|
122
122
|
expect(buildConfigPaywallPlans(billingPlans, ['primary', 'secondary', 'tertiary'])).toEqual([
|
|
123
123
|
expect.objectContaining({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { experiments, manifestExperiments } from '
|
|
2
|
+
import { experiments, manifestExperiments } from '../../../src/config/experiments';
|
|
3
3
|
|
|
4
4
|
describe('funnel experiments config', () => {
|
|
5
5
|
it('ships the generic template without brand-specific experiments', () => {
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import { stepRegistry } from '@/runtime/step-registry';
|
|
9
9
|
import { stepTwoContent, stepTwoOptions } from '@/steps/content/step-02.content';
|
|
10
10
|
import { contentOptionsByStepId } from '@/steps/step-content.registry';
|
|
11
|
-
import { funnelManifest } from '
|
|
11
|
+
import { funnelManifest } from '../../../src/config/funnel.manifest';
|
|
12
12
|
|
|
13
13
|
describe('funnel manifest breakpoints', () => {
|
|
14
14
|
it('ships the four default QA breakpoints for new funnels', () => {
|
|
@@ -5,14 +5,14 @@ import {
|
|
|
5
5
|
CURRENT_STEP_CONTRACT_VERSION,
|
|
6
6
|
type FunnelStepContractProjectSnapshot,
|
|
7
7
|
} from '@funnelsgrove/runtime';
|
|
8
|
-
import docsManifest from '
|
|
8
|
+
import docsManifest from '../../../.funnelsgrove-docs.json';
|
|
9
9
|
import { manifestExperiments } from '@/config/experiments';
|
|
10
10
|
import { funnelManifest, rawFunnelManifest } from '@/config/funnel.manifest';
|
|
11
11
|
import { stepRegistry } from '@/runtime/step-registry';
|
|
12
12
|
import { contentOptionsByStepId } from '@/steps/step-content.registry';
|
|
13
13
|
|
|
14
14
|
const loadProjectValidation = async () => {
|
|
15
|
-
return import('
|
|
15
|
+
return import('../../../src/contract/funnel-project.validation');
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
describe('funnel project validation', () => {
|
|
@@ -9,22 +9,22 @@ import {
|
|
|
9
9
|
type FunnelManifest,
|
|
10
10
|
} from '@funnelsgrove/runtime';
|
|
11
11
|
import { publicAnalyticsSdk } from '@funnelsgrove/analytics';
|
|
12
|
-
import { goldenFixture as cancellationOffer } from '
|
|
13
|
-
import { goldenFixture as checkout } from '
|
|
14
|
-
import { goldenFixture as purchaseCompleted } from '
|
|
15
|
-
import { goldenFixture as formInput } from '
|
|
16
|
-
import { goldenFixture as introHero } from '
|
|
17
|
-
import { goldenFixture as multiSelectChoice } from '
|
|
18
|
-
import { goldenFixture as paywallOffer } from '
|
|
19
|
-
import { goldenFixture as progressInterstitial } from '
|
|
20
|
-
import { goldenFixture as singleStepChoice } from '
|
|
21
|
-
import { goldenFixture as singleStepChoiceEmoji } from '
|
|
22
|
-
import { goldenFixture as socialProof } from '
|
|
23
|
-
import { goldenFixture as subscriptionHandoff } from '
|
|
24
|
-
import { goldenFixture as subscriptionManagement } from '
|
|
25
|
-
import { goldenFixture as summaryConfirmation } from '
|
|
26
|
-
import { goldenFixture as upsellOffer } from '
|
|
27
|
-
import { goldenFixture as valuePropStory } from '
|
|
12
|
+
import { goldenFixture as cancellationOffer } from '../../../../src/contract/golden/cancellation_offer.fixture';
|
|
13
|
+
import { goldenFixture as checkout } from '../../../../src/contract/golden/checkout.fixture';
|
|
14
|
+
import { goldenFixture as purchaseCompleted } from '../../../../src/contract/golden/purchase_completed.fixture';
|
|
15
|
+
import { goldenFixture as formInput } from '../../../../src/contract/golden/form_input.fixture';
|
|
16
|
+
import { goldenFixture as introHero } from '../../../../src/contract/golden/intro_hero.fixture';
|
|
17
|
+
import { goldenFixture as multiSelectChoice } from '../../../../src/contract/golden/multi_select_choice.fixture';
|
|
18
|
+
import { goldenFixture as paywallOffer } from '../../../../src/contract/golden/paywall_offer.fixture';
|
|
19
|
+
import { goldenFixture as progressInterstitial } from '../../../../src/contract/golden/progress_interstitial.fixture';
|
|
20
|
+
import { goldenFixture as singleStepChoice } from '../../../../src/contract/golden/single_step_choice.fixture';
|
|
21
|
+
import { goldenFixture as singleStepChoiceEmoji } from '../../../../src/contract/golden/single_step_choice_emoji.fixture';
|
|
22
|
+
import { goldenFixture as socialProof } from '../../../../src/contract/golden/social_proof.fixture';
|
|
23
|
+
import { goldenFixture as subscriptionHandoff } from '../../../../src/contract/golden/subscription_handoff.fixture';
|
|
24
|
+
import { goldenFixture as subscriptionManagement } from '../../../../src/contract/golden/subscription_management.fixture';
|
|
25
|
+
import { goldenFixture as summaryConfirmation } from '../../../../src/contract/golden/summary_confirmation.fixture';
|
|
26
|
+
import { goldenFixture as upsellOffer } from '../../../../src/contract/golden/upsell_offer.fixture';
|
|
27
|
+
import { goldenFixture as valuePropStory } from '../../../../src/contract/golden/value_prop_story.fixture';
|
|
28
28
|
|
|
29
29
|
const fixtures = [
|
|
30
30
|
cancellationOffer,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
const execFileAsync = promisify(execFile);
|
|
13
13
|
|
|
14
14
|
const loadCli = async () => {
|
|
15
|
-
return import('
|
|
15
|
+
return import('../../../src/contract/validate-funnel.cli');
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
const diagnostic = createFunnelContractDiagnostic({
|
|
@@ -150,7 +150,7 @@ describe('validate funnel CLI protocol', () => {
|
|
|
150
150
|
});
|
|
151
151
|
|
|
152
152
|
it('runs through vite-node with one clean stdout protocol object', async () => {
|
|
153
|
-
const templateRoot = path.resolve(fileURLToPath(new URL('
|
|
153
|
+
const templateRoot = path.resolve(fileURLToPath(new URL('../../..', import.meta.url)));
|
|
154
154
|
const viteNode = path.resolve(templateRoot, '../../node_modules/.bin/vite-node');
|
|
155
155
|
const script = path.resolve(templateRoot, 'src/contract/validate-funnel.cli.ts');
|
|
156
156
|
|
|
@@ -23,7 +23,7 @@ describe('checkoutRuntimeConfig', () => {
|
|
|
23
23
|
it('does not invent a checkout SDK publishable key from generic Stripe env', async () => {
|
|
24
24
|
process.env.NEXT_PUBLIC_STRIPE_TEST_PUBLISHABLE_KEY = 'pk_test_generic_runtime_value';
|
|
25
25
|
|
|
26
|
-
const { checkoutRuntimeConfig } = await import('
|
|
26
|
+
const { checkoutRuntimeConfig } = await import('../../../src/runtime/checkout-runtime-config');
|
|
27
27
|
|
|
28
28
|
expect(checkoutRuntimeConfig.funnelSdkPublishableKey).toBeNull();
|
|
29
29
|
});
|
|
@@ -43,7 +43,7 @@ describe('checkoutRuntimeConfig', () => {
|
|
|
43
43
|
};
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
const { checkoutRuntimeConfig, resolveClaimbeeCheckoutMode } = await import('
|
|
46
|
+
const { checkoutRuntimeConfig, resolveClaimbeeCheckoutMode } = await import('../../../src/runtime/checkout-runtime-config');
|
|
47
47
|
|
|
48
48
|
expect(checkoutRuntimeConfig.funnelSdkPublishableKey).toBe(
|
|
49
49
|
'sdk_pub_claimbee_project',
|
|
@@ -52,7 +52,7 @@ describe('checkoutRuntimeConfig', () => {
|
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
it('uses the server-provided runtime mode when no preview key is injected', async () => {
|
|
55
|
-
const { resolveClaimbeeCheckoutMode } = await import('
|
|
55
|
+
const { resolveClaimbeeCheckoutMode } = await import('../../../src/runtime/checkout-runtime-config');
|
|
56
56
|
|
|
57
57
|
expect(resolveClaimbeeCheckoutMode('live')).toBe('live');
|
|
58
58
|
expect(resolveClaimbeeCheckoutMode('test')).toBe('test');
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
defaultFunnelStepId,
|
|
5
5
|
resolveConfiguredNextStep,
|
|
6
6
|
resolveRuntimeInitialStepId,
|
|
7
|
-
} from '
|
|
7
|
+
} from '../../../src/runtime/funnel-runtime';
|
|
8
8
|
|
|
9
9
|
describe('funnel runtime', () => {
|
|
10
10
|
it('falls back to the first manifest step when no valid step is requested', () => {
|
|
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest';
|
|
|
2
2
|
import {
|
|
3
3
|
getStepContentLocale,
|
|
4
4
|
getStepCountryCode,
|
|
5
|
-
} from '
|
|
5
|
+
} from '../../../src/runtime/step-content-context';
|
|
6
6
|
|
|
7
7
|
describe('step content context helpers', () => {
|
|
8
8
|
it('prefers stored browser language and explicit country when present', () => {
|
package/template_scaffold/{src → tests/src}/runtime/use-funnel-flow-controller.integration.test.ts
RENAMED
|
@@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
3
3
|
|
|
4
4
|
const { posthogFlagsPath } = vi.hoisted(() => ({
|
|
5
5
|
posthogFlagsPath: new URL(
|
|
6
|
-
'
|
|
6
|
+
'../../../../funnel-runtime/src/runtime/posthog-flags.ts',
|
|
7
7
|
import.meta.url,
|
|
8
8
|
).pathname,
|
|
9
9
|
}));
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
resolveExperimentVariant,
|
|
32
32
|
resolveNextStepId,
|
|
33
33
|
} from '@funnelsgrove/runtime';
|
|
34
|
-
import { computeRenderSuspended } from '
|
|
34
|
+
import { computeRenderSuspended } from '../../../src/runtime/use-funnel-flow-controller';
|
|
35
35
|
|
|
36
36
|
const mockedResolveVariant = vi.mocked(resolveExperimentVariant);
|
|
37
37
|
|
|
@@ -187,9 +187,9 @@ describe.skipIf(!insideMonorepo)('PostHog variant resolution (integration)', ()
|
|
|
187
187
|
});
|
|
188
188
|
|
|
189
189
|
it('renders experiment variants on the source URL before normal continuation', () => {
|
|
190
|
-
const adapterSource = readFileSync(new URL('
|
|
190
|
+
const adapterSource = readFileSync(new URL('../../../src/runtime/use-funnel-flow-controller.ts', import.meta.url), 'utf8');
|
|
191
191
|
const runtimeSource = readFileSync(
|
|
192
|
-
new URL('
|
|
192
|
+
new URL('../../../../funnel-runtime/src/runtime/use-funnel-flow-controller.ts', import.meta.url),
|
|
193
193
|
'utf8',
|
|
194
194
|
);
|
|
195
195
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
|
-
import { computeRenderSuspended } from '
|
|
3
|
+
import { computeRenderSuspended } from '../../../src/runtime/use-funnel-flow-controller';
|
|
4
4
|
|
|
5
5
|
describe('computeRenderSuspended', () => {
|
|
6
6
|
const experiments = [
|
|
@@ -52,7 +52,7 @@ describe('computeRenderSuspended', () => {
|
|
|
52
52
|
|
|
53
53
|
describe('qualified visitor analytics', () => {
|
|
54
54
|
it('delegates flow behavior to the shared runtime controller with template analytics', () => {
|
|
55
|
-
const source = readFileSync(new URL('
|
|
55
|
+
const source = readFileSync(new URL('../../../src/runtime/use-funnel-flow-controller.ts', import.meta.url), 'utf8');
|
|
56
56
|
|
|
57
57
|
expect(source).toContain('useFunnelFlowController as useSharedFunnelFlowController');
|
|
58
58
|
expect(source).toContain('return useSharedFunnelFlowController<FunnelStepId>({');
|
|
@@ -66,7 +66,7 @@ describe('qualified visitor analytics', () => {
|
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
it('does not carry a local copy of shared controller internals', () => {
|
|
69
|
-
const source = readFileSync(new URL('
|
|
69
|
+
const source = readFileSync(new URL('../../../src/runtime/use-funnel-flow-controller.ts', import.meta.url), 'utf8');
|
|
70
70
|
|
|
71
71
|
expect(source).not.toContain('FIRST_STEP_ENGAGEMENT_THRESHOLD_MS');
|
|
72
72
|
expect(source).not.toContain('apiService.bootstrapSession');
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
buildPaywallCheckoutBillingDetails,
|
|
4
4
|
buildPaywallCheckoutExpressOptions,
|
|
5
5
|
buildPaywallCheckoutPaymentElementOptions,
|
|
6
|
-
} from '
|
|
6
|
+
} from '../../../src/steps/paywall-checkout-options';
|
|
7
7
|
|
|
8
8
|
describe('paywall checkout options', () => {
|
|
9
9
|
it('prioritizes Link and stacked express checkout buttons for the checkout dialog', () => {
|
|
@@ -2,8 +2,10 @@ import { readFileSync } from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { describe, expect, it } from 'vitest';
|
|
4
4
|
|
|
5
|
+
const sourceStepsDirectory = path.resolve(__dirname, '../../../src/steps');
|
|
6
|
+
|
|
5
7
|
const readStepSource = (fileName: string): string =>
|
|
6
|
-
readFileSync(path.resolve(
|
|
8
|
+
readFileSync(path.resolve(sourceStepsDirectory, fileName), 'utf8');
|
|
7
9
|
|
|
8
10
|
describe('template shared header contract', () => {
|
|
9
11
|
it('renders template step headers through the shared FunnelStepHeader component', () => {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { describe, expect, it } from 'vitest';
|
|
4
|
-
import { stepTwoContent, stepTwoOptions } from '
|
|
5
|
-
import { stepTwo } from '
|
|
4
|
+
import { stepTwoContent, stepTwoOptions } from '../../../src/steps/content/step-02.content';
|
|
5
|
+
import { stepTwo } from '../../../src/steps/step-02';
|
|
6
6
|
|
|
7
|
-
const stepSource = readFileSync(path.resolve(__dirname, '
|
|
7
|
+
const stepSource = readFileSync(path.resolve(__dirname, '../../../src/steps/step-02.tsx'), 'utf8');
|
|
8
8
|
|
|
9
9
|
describe('StepTwo', () => {
|
|
10
10
|
it('declares its typed multi-select answer contract and immutable option IDs', () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { resolveStepThreeAutoAdvanceMs, stepThree } from '
|
|
2
|
+
import { resolveStepThreeAutoAdvanceMs, stepThree } from '../../../src/steps/step-03';
|
|
3
3
|
|
|
4
4
|
describe('StepThree', () => {
|
|
5
5
|
it('keeps the shell action bar hidden because the step owns its own auto-advance behavior', () => {
|
|
@@ -14,7 +14,7 @@ describe('StepFour scratchpad', () => {
|
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
it('reveals the scratch ticket before advancing through the manifest edge', () => {
|
|
17
|
-
const stepSource = readFileSync(path.resolve(__dirname, '
|
|
17
|
+
const stepSource = readFileSync(path.resolve(__dirname, '../../../src/steps/step-04.tsx'), 'utf8');
|
|
18
18
|
|
|
19
19
|
expect(stepSource).toContain("data-node-id='7390:1456'");
|
|
20
20
|
expect(stepSource).toContain("data-node-id='6992:6168'");
|
|
@@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest';
|
|
|
3
3
|
|
|
4
4
|
describe('email capture workflow ownership', () => {
|
|
5
5
|
it('only validates UI state and calls the shared context helper', () => {
|
|
6
|
-
const source = readFileSync(new URL('
|
|
6
|
+
const source = readFileSync(new URL('../../../src/steps/step-31-email-capture.tsx', import.meta.url), 'utf8');
|
|
7
7
|
|
|
8
8
|
expect(source).toContain('submitEmailCapture } = useFunnel();');
|
|
9
9
|
expect(source).toContain('await submitEmailCapture(email);');
|