From 76290a10fa86d9a27d99036836352c03404d5ee9 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Mon, 23 Mar 2026 08:41:02 +0000 Subject: [PATCH] Split active use trial nudge owners --- .../subsystems/frontend-primitives.md | 8 + .../components/shared/ActiveUseTrialNudge.tsx | 125 ++------------ .../SharedPrimitives.guardrails.test.ts | 29 ++++ .../__tests__/ActiveUseTrialNudge.test.tsx | 158 ++++++++++++++++++ .../shared/activeUseTrialNudgeModel.ts | 29 ++++ .../shared/useActiveUseTrialNudgeState.ts | 101 +++++++++++ .../frontendResourceTypeBoundaries.test.ts | 21 +++ 7 files changed, 363 insertions(+), 108 deletions(-) create mode 100644 frontend-modern/src/components/shared/__tests__/ActiveUseTrialNudge.test.tsx create mode 100644 frontend-modern/src/components/shared/activeUseTrialNudgeModel.ts create mode 100644 frontend-modern/src/components/shared/useActiveUseTrialNudgeState.ts diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 422ab434c..42c1ebb9c 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -214,6 +214,14 @@ signals, canvas draw lifecycle, and resize handling, and math, hover target selection, tooltip time formatting, and density-cell opacity rules. Future density-map work should extend those owners instead of pushing canvas lifecycle or chart math back into the shared shell. +The shared active-use trial nudge now follows that same owner split. +`frontend-modern/src/components/shared/ActiveUseTrialNudge.tsx` stays the +render shell, `frontend-modern/src/components/shared/useActiveUseTrialNudgeState.ts` +owns first-seen persistence, snooze state, hourly age refresh, and trial-start +runtime, and `frontend-modern/src/components/shared/activeUseTrialNudgeModel.ts` +owns the eligibility policy, age threshold, and nudge copy/config. Future +active-use trial work should extend those owners instead of pushing storage +policy, timers, or commercial action flow back into the shared shell. The shared dialog now follows that same owner split. `frontend-modern/src/components/shared/Dialog.tsx` stays the render shell, `frontend-modern/src/components/shared/useDialogState.ts` owns focus trap, diff --git a/frontend-modern/src/components/shared/ActiveUseTrialNudge.tsx b/frontend-modern/src/components/shared/ActiveUseTrialNudge.tsx index ea9cb3fcc..af9f68984 100644 --- a/frontend-modern/src/components/shared/ActiveUseTrialNudge.tsx +++ b/frontend-modern/src/components/shared/ActiveUseTrialNudge.tsx @@ -1,132 +1,41 @@ -/** - * ActiveUseTrialNudge - * - * Proactive trial nudge shown after 7+ days of active use for free-tier users - * who haven't started a trial. Dismissible with 7-day snooze. - */ - -import { Component, Show, createSignal, createMemo, onMount, onCleanup } from 'solid-js'; -import { licenseStatus, startProTrial } from '@/stores/license'; -import { isUpsellSnoozed, snoozeUpsell } from '@/utils/snooze'; -import { notificationStore } from '@/stores/notifications'; +import { Component, Show } from 'solid-js'; import { - getProTrialStartedMessage, - getTrialAlreadyUsedMessage, - getTrialStartErrorMessage, - getTrialTryAgainLaterMessage, -} from '@/utils/upgradePresentation'; - -const SNOOZE_KEY = 'pulse_active_use_nudge_snoozed'; -const FIRST_SEEN_KEY = 'pulse_first_seen_ts'; -const MIN_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days - -function getFirstSeenTimestamp(): number { - if (typeof window === 'undefined') return Date.now(); - try { - const raw = window.localStorage.getItem(FIRST_SEEN_KEY); - if (raw) { - const ts = Number(raw); - if (Number.isFinite(ts) && ts > 0) return ts; - } - // First time: record now - const now = Date.now(); - window.localStorage.setItem(FIRST_SEEN_KEY, String(now)); - return now; - } catch { - return Date.now(); - } -} + ACTIVE_USE_TRIAL_NUDGE_SNOOZE_LABEL, + ACTIVE_USE_TRIAL_NUDGE_STARTING_LABEL, + ACTIVE_USE_TRIAL_NUDGE_START_LABEL, + ACTIVE_USE_TRIAL_NUDGE_TITLE, +} from './activeUseTrialNudgeModel'; +import { useActiveUseTrialNudgeState } from './useActiveUseTrialNudgeState'; export const ActiveUseTrialNudge: Component = () => { - const [snoozed, setSnoozed] = createSignal(isUpsellSnoozed(SNOOZE_KEY)); - const [firstSeen, setFirstSeen] = createSignal(Date.now()); - const [now, setNow] = createSignal(Date.now()); - const [startingTrial, setStartingTrial] = createSignal(false); - - onMount(() => { - setFirstSeen(getFirstSeenTimestamp()); - // Re-evaluate once per hour so the nudge can appear if day 7 is crossed during a long session - const timer = setInterval(() => setNow(Date.now()), 60 * 60 * 1000); - onCleanup(() => clearInterval(timer)); - }); - - const isOldEnough = createMemo(() => now() - firstSeen() >= MIN_AGE_MS); - - const isFreeNoTrial = createMemo(() => { - const ent = licenseStatus(); - if (!ent) return false; - // Only show for free tier users who are trial-eligible - return ( - ent.tier === 'free' && - ent.subscription_state !== 'trial' && - ent.subscription_state !== 'active' && - ent.trial_eligible !== false - ); - }); - - const shouldShow = createMemo(() => isOldEnough() && isFreeNoTrial() && !snoozed()); - - const handleSnooze = () => { - snoozeUpsell(SNOOZE_KEY); - setSnoozed(true); - }; - - const handleStartTrial = async () => { - if (startingTrial()) return; - setStartingTrial(true); - try { - const result = await startProTrial(); - if (result?.outcome === 'redirect') { - if (typeof window !== 'undefined') { - window.location.href = result.actionUrl; - } - return; - } - notificationStore.success(getProTrialStartedMessage()); - } catch (err) { - const statusCode = (err as { status?: number } | null)?.status; - if (statusCode === 409) { - notificationStore.error(getTrialAlreadyUsedMessage()); - } else if (statusCode === 429) { - notificationStore.error(getTrialTryAgainLaterMessage()); - } else { - notificationStore.error( - getTrialStartErrorMessage(err instanceof Error ? err.message : undefined, { - branded: true, - }), - ); - } - } finally { - setStartingTrial(false); - } - }; + const state = useActiveUseTrialNudgeState(); return ( - +
- - Experience the full power of Pulse — start your free trial - + {ACTIVE_USE_TRIAL_NUDGE_TITLE}
diff --git a/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts b/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts index a183f6284..db028c9d2 100644 --- a/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts +++ b/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts @@ -2,6 +2,8 @@ import { describe, expect, it } from 'vitest'; import calloutCardSource from '@/components/shared/CalloutCard.tsx?raw'; import commandPaletteModalSource from '@/components/shared/CommandPaletteModal.tsx?raw'; import commandPaletteModelSource from '@/components/shared/commandPaletteModel.ts?raw'; +import activeUseTrialNudgeSource from '@/components/shared/ActiveUseTrialNudge.tsx?raw'; +import activeUseTrialNudgeModelSource from '@/components/shared/activeUseTrialNudgeModel.ts?raw'; import collapsibleSearchInputSource from '@/components/shared/CollapsibleSearchInput.tsx?raw'; import collapsibleSearchInputModelSource from '@/components/shared/collapsibleSearchInputModel.ts?raw'; import containerUpdateBadgeSource from '@/components/shared/ContainerUpdateBadge.tsx?raw'; @@ -46,6 +48,7 @@ import monitoredSystemLimitWarningBannerSource from '@/components/shared/Monitor import selectionCardGroupSource from '@/components/shared/SelectionCardGroup.tsx?raw'; import tagBadgesSource from '@/components/shared/TagBadges.tsx?raw'; import commandPaletteStateSource from '@/components/shared/useCommandPaletteState.ts?raw'; +import activeUseTrialNudgeStateSource from '@/components/shared/useActiveUseTrialNudgeState.ts?raw'; import collapsibleSearchInputStateSource from '@/components/shared/useCollapsibleSearchInputState.ts?raw'; import containerUpdateButtonStateSource from '@/components/shared/useContainerUpdateButtonState.ts?raw'; import densityMapStateSource from '@/components/shared/useDensityMapState.ts?raw'; @@ -147,6 +150,32 @@ describe('shared primitive guardrails', () => { expect(commandPaletteModelSource).toContain('filterCommandPaletteCommands'); }); + it('keeps active use trial nudge on shell, runtime, and model owners', () => { + expect(activeUseTrialNudgeSource).toContain('useActiveUseTrialNudgeState'); + expect(activeUseTrialNudgeSource).toContain('ACTIVE_USE_TRIAL_NUDGE_TITLE'); + expect(activeUseTrialNudgeSource).not.toContain('createSignal'); + expect(activeUseTrialNudgeSource).not.toContain('createMemo'); + expect(activeUseTrialNudgeSource).not.toContain('startProTrial'); + expect(activeUseTrialNudgeSource).not.toContain('localStorage'); + expect(activeUseTrialNudgeSource).not.toContain('setInterval'); + + expect(activeUseTrialNudgeStateSource).toContain( + 'export function useActiveUseTrialNudgeState', + ); + expect(activeUseTrialNudgeStateSource).toContain('createSignal'); + expect(activeUseTrialNudgeStateSource).toContain('createMemo'); + expect(activeUseTrialNudgeStateSource).toContain('window.localStorage'); + expect(activeUseTrialNudgeStateSource).toContain('setInterval'); + expect(activeUseTrialNudgeStateSource).toContain('startProTrial'); + expect(activeUseTrialNudgeStateSource).toContain('snoozeUpsell'); + + expect(activeUseTrialNudgeModelSource).toContain('ACTIVE_USE_TRIAL_NUDGE_SNOOZE_KEY'); + expect(activeUseTrialNudgeModelSource).toContain('ACTIVE_USE_TRIAL_NUDGE_FIRST_SEEN_KEY'); + expect(activeUseTrialNudgeModelSource).toContain('isActiveUseTrialNudgeEligible'); + expect(activeUseTrialNudgeModelSource).toContain('isActiveUseTrialNudgeOldEnough'); + expect(activeUseTrialNudgeModelSource).toContain('ACTIVE_USE_TRIAL_NUDGE_TITLE'); + }); + it('routes settings info callouts through CalloutCard', () => { expect(calloutCardSource).toContain( "type CalloutTone = 'danger' | 'info' | 'success' | 'warning'", diff --git a/frontend-modern/src/components/shared/__tests__/ActiveUseTrialNudge.test.tsx b/frontend-modern/src/components/shared/__tests__/ActiveUseTrialNudge.test.tsx new file mode 100644 index 000000000..7e39a16a7 --- /dev/null +++ b/frontend-modern/src/components/shared/__tests__/ActiveUseTrialNudge.test.tsx @@ -0,0 +1,158 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { cleanup, fireEvent, render, screen, waitFor } from '@solidjs/testing-library'; +import activeUseTrialNudgeSource from '@/components/shared/ActiveUseTrialNudge.tsx?raw'; +import activeUseTrialNudgeModelSource from '@/components/shared/activeUseTrialNudgeModel.ts?raw'; +import activeUseTrialNudgeStateSource from '@/components/shared/useActiveUseTrialNudgeState.ts?raw'; +import { + ACTIVE_USE_TRIAL_NUDGE_FIRST_SEEN_KEY, + ACTIVE_USE_TRIAL_NUDGE_MIN_AGE_MS, + ACTIVE_USE_TRIAL_NUDGE_SNOOZE_KEY, +} from '@/components/shared/activeUseTrialNudgeModel'; + +const { + licenseStatusMock, + startProTrialMock, + showSuccessMock, + showErrorMock, + isUpsellSnoozedMock, + snoozeUpsellMock, +} = vi.hoisted(() => ({ + licenseStatusMock: vi.fn(), + startProTrialMock: vi.fn(), + showSuccessMock: vi.fn(), + showErrorMock: vi.fn(), + isUpsellSnoozedMock: vi.fn(), + snoozeUpsellMock: vi.fn(), +})); + +vi.mock('@/stores/license', () => ({ + licenseStatus: (...args: unknown[]) => licenseStatusMock(...args), + startProTrial: (...args: unknown[]) => startProTrialMock(...args), +})); + +vi.mock('@/stores/notifications', () => ({ + notificationStore: { + success: (...args: unknown[]) => showSuccessMock(...args), + error: (...args: unknown[]) => showErrorMock(...args), + }, +})); + +vi.mock('@/utils/snooze', () => ({ + isUpsellSnoozed: (...args: unknown[]) => isUpsellSnoozedMock(...args), + snoozeUpsell: (...args: unknown[]) => snoozeUpsellMock(...args), +})); + +vi.mock('@/utils/upgradePresentation', () => ({ + getProTrialStartedMessage: () => 'Pro trial started', + getTrialAlreadyUsedMessage: () => 'Trial already used', + getTrialStartErrorMessage: () => 'Trial start failed', + getTrialTryAgainLaterMessage: () => 'Try again later', +})); + +import { ActiveUseTrialNudge } from '@/components/shared/ActiveUseTrialNudge'; + +function setEligibleFreeLicense() { + licenseStatusMock.mockReturnValue({ + tier: 'free', + subscription_state: 'expired', + trial_eligible: true, + }); +} + +describe('ActiveUseTrialNudge', () => { + beforeEach(() => { + localStorage.clear(); + licenseStatusMock.mockReset(); + startProTrialMock.mockReset(); + showSuccessMock.mockReset(); + showErrorMock.mockReset(); + isUpsellSnoozedMock.mockReset(); + snoozeUpsellMock.mockReset(); + isUpsellSnoozedMock.mockReturnValue(false); + }); + + afterEach(() => { + cleanup(); + }); + + it('keeps active use trial nudge on shell, runtime, and model owners', () => { + expect(activeUseTrialNudgeSource).toContain('useActiveUseTrialNudgeState'); + expect(activeUseTrialNudgeSource).toContain('ACTIVE_USE_TRIAL_NUDGE_TITLE'); + expect(activeUseTrialNudgeSource).not.toContain('createSignal'); + expect(activeUseTrialNudgeSource).not.toContain('createMemo'); + expect(activeUseTrialNudgeSource).not.toContain('startProTrial'); + expect(activeUseTrialNudgeSource).not.toContain('localStorage'); + expect(activeUseTrialNudgeSource).not.toContain('setInterval'); + + expect(activeUseTrialNudgeStateSource).toContain('export function useActiveUseTrialNudgeState'); + expect(activeUseTrialNudgeStateSource).toContain('createSignal'); + expect(activeUseTrialNudgeStateSource).toContain('createMemo'); + expect(activeUseTrialNudgeStateSource).toContain('window.localStorage'); + expect(activeUseTrialNudgeStateSource).toContain('setInterval'); + expect(activeUseTrialNudgeStateSource).toContain('startProTrial'); + expect(activeUseTrialNudgeStateSource).toContain('snoozeUpsell'); + + expect(activeUseTrialNudgeModelSource).toContain('ACTIVE_USE_TRIAL_NUDGE_SNOOZE_KEY'); + expect(activeUseTrialNudgeModelSource).toContain('ACTIVE_USE_TRIAL_NUDGE_FIRST_SEEN_KEY'); + expect(activeUseTrialNudgeModelSource).toContain('isActiveUseTrialNudgeEligible'); + expect(activeUseTrialNudgeModelSource).toContain('isActiveUseTrialNudgeOldEnough'); + expect(activeUseTrialNudgeModelSource).toContain('ACTIVE_USE_TRIAL_NUDGE_TITLE'); + }); + + it('renders for eligible free users after the minimum active age', async () => { + setEligibleFreeLicense(); + localStorage.setItem( + ACTIVE_USE_TRIAL_NUDGE_FIRST_SEEN_KEY, + String(Date.now() - ACTIVE_USE_TRIAL_NUDGE_MIN_AGE_MS - 1), + ); + + render(() => ); + + expect(await screen.findByRole('status')).toBeInTheDocument(); + expect(screen.getByText('Experience the full power of Pulse — start your free trial')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Start 14-day trial' })).toBeInTheDocument(); + }); + + it('does not render before the active age threshold is crossed', () => { + setEligibleFreeLicense(); + render(() => ); + + expect(screen.queryByRole('status')).toBeNull(); + expect(localStorage.getItem(ACTIVE_USE_TRIAL_NUDGE_FIRST_SEEN_KEY)).not.toBeNull(); + }); + + it('snoozes and hides the nudge', async () => { + setEligibleFreeLicense(); + localStorage.setItem( + ACTIVE_USE_TRIAL_NUDGE_FIRST_SEEN_KEY, + String(Date.now() - ACTIVE_USE_TRIAL_NUDGE_MIN_AGE_MS - 1), + ); + + render(() => ); + + fireEvent.click(await screen.findByRole('button', { name: 'Snooze 7d' })); + + expect(snoozeUpsellMock).toHaveBeenCalledWith(ACTIVE_USE_TRIAL_NUDGE_SNOOZE_KEY); + await waitFor(() => { + expect(screen.queryByRole('status')).toBeNull(); + }); + }); + + it('starts a trial successfully and shows a success notification', async () => { + setEligibleFreeLicense(); + startProTrialMock.mockResolvedValue({ outcome: 'activated' }); + localStorage.setItem( + ACTIVE_USE_TRIAL_NUDGE_FIRST_SEEN_KEY, + String(Date.now() - ACTIVE_USE_TRIAL_NUDGE_MIN_AGE_MS - 1), + ); + + render(() => ); + + fireEvent.click(await screen.findByRole('button', { name: 'Start 14-day trial' })); + + await waitFor(() => { + expect(startProTrialMock).toHaveBeenCalledTimes(1); + }); + expect(showSuccessMock).toHaveBeenCalledWith('Pro trial started'); + }); +}); diff --git a/frontend-modern/src/components/shared/activeUseTrialNudgeModel.ts b/frontend-modern/src/components/shared/activeUseTrialNudgeModel.ts new file mode 100644 index 000000000..0de6f48e3 --- /dev/null +++ b/frontend-modern/src/components/shared/activeUseTrialNudgeModel.ts @@ -0,0 +1,29 @@ +import type { LicenseEntitlements } from '@/api/license'; + +export const ACTIVE_USE_TRIAL_NUDGE_SNOOZE_KEY = 'pulse_active_use_nudge_snoozed'; +export const ACTIVE_USE_TRIAL_NUDGE_FIRST_SEEN_KEY = 'pulse_first_seen_ts'; +export const ACTIVE_USE_TRIAL_NUDGE_MIN_AGE_MS = 7 * 24 * 60 * 60 * 1000; +export const ACTIVE_USE_TRIAL_NUDGE_REFRESH_MS = 60 * 60 * 1000; + +export const ACTIVE_USE_TRIAL_NUDGE_TITLE = + 'Experience the full power of Pulse — start your free trial'; +export const ACTIVE_USE_TRIAL_NUDGE_START_LABEL = 'Start 14-day trial'; +export const ACTIVE_USE_TRIAL_NUDGE_STARTING_LABEL = 'Starting...'; +export const ACTIVE_USE_TRIAL_NUDGE_SNOOZE_LABEL = 'Snooze 7d'; + +export function isActiveUseTrialNudgeEligible( + entitlements: LicenseEntitlements | null | undefined, +): boolean { + if (!entitlements) return false; + + return ( + entitlements.tier === 'free' && + entitlements.subscription_state !== 'trial' && + entitlements.subscription_state !== 'active' && + entitlements.trial_eligible !== false + ); +} + +export function isActiveUseTrialNudgeOldEnough(now: number, firstSeen: number): boolean { + return now - firstSeen >= ACTIVE_USE_TRIAL_NUDGE_MIN_AGE_MS; +} diff --git a/frontend-modern/src/components/shared/useActiveUseTrialNudgeState.ts b/frontend-modern/src/components/shared/useActiveUseTrialNudgeState.ts new file mode 100644 index 000000000..3b77403b5 --- /dev/null +++ b/frontend-modern/src/components/shared/useActiveUseTrialNudgeState.ts @@ -0,0 +1,101 @@ +import { createMemo, createSignal, onCleanup, onMount } from 'solid-js'; +import { licenseStatus, startProTrial } from '@/stores/license'; +import { notificationStore } from '@/stores/notifications'; +import { isUpsellSnoozed, snoozeUpsell } from '@/utils/snooze'; +import { + getProTrialStartedMessage, + getTrialAlreadyUsedMessage, + getTrialStartErrorMessage, + getTrialTryAgainLaterMessage, +} from '@/utils/upgradePresentation'; +import { + ACTIVE_USE_TRIAL_NUDGE_FIRST_SEEN_KEY, + ACTIVE_USE_TRIAL_NUDGE_REFRESH_MS, + ACTIVE_USE_TRIAL_NUDGE_SNOOZE_KEY, + isActiveUseTrialNudgeEligible, + isActiveUseTrialNudgeOldEnough, +} from './activeUseTrialNudgeModel'; + +function getActiveUseTrialNudgeFirstSeenTimestamp(): number { + if (typeof window === 'undefined') return Date.now(); + + try { + const raw = window.localStorage.getItem(ACTIVE_USE_TRIAL_NUDGE_FIRST_SEEN_KEY); + if (raw) { + const timestamp = Number(raw); + if (Number.isFinite(timestamp) && timestamp > 0) return timestamp; + } + + const now = Date.now(); + window.localStorage.setItem(ACTIVE_USE_TRIAL_NUDGE_FIRST_SEEN_KEY, String(now)); + return now; + } catch { + return Date.now(); + } +} + +export function useActiveUseTrialNudgeState() { + const [snoozed, setSnoozed] = createSignal( + isUpsellSnoozed(ACTIVE_USE_TRIAL_NUDGE_SNOOZE_KEY), + ); + const [firstSeen, setFirstSeen] = createSignal(Date.now()); + const [now, setNow] = createSignal(Date.now()); + const [startingTrial, setStartingTrial] = createSignal(false); + + onMount(() => { + setFirstSeen(getActiveUseTrialNudgeFirstSeenTimestamp()); + const timer = window.setInterval(() => setNow(Date.now()), ACTIVE_USE_TRIAL_NUDGE_REFRESH_MS); + onCleanup(() => window.clearInterval(timer)); + }); + + const shouldShow = createMemo(() => { + return ( + isActiveUseTrialNudgeOldEnough(now(), firstSeen()) && + isActiveUseTrialNudgeEligible(licenseStatus()) && + !snoozed() + ); + }); + + const handleSnooze = () => { + snoozeUpsell(ACTIVE_USE_TRIAL_NUDGE_SNOOZE_KEY); + setSnoozed(true); + }; + + const handleStartTrial = async () => { + if (startingTrial()) return; + + setStartingTrial(true); + try { + const result = await startProTrial(); + if (result?.outcome === 'redirect') { + if (typeof window !== 'undefined') { + window.location.href = result.actionUrl; + } + return; + } + notificationStore.success(getProTrialStartedMessage()); + } catch (error) { + const statusCode = (error as { status?: number } | null)?.status; + if (statusCode === 409) { + notificationStore.error(getTrialAlreadyUsedMessage()); + } else if (statusCode === 429) { + notificationStore.error(getTrialTryAgainLaterMessage()); + } else { + notificationStore.error( + getTrialStartErrorMessage(error instanceof Error ? error.message : undefined, { + branded: true, + }), + ); + } + } finally { + setStartingTrial(false); + } + }; + + return { + handleSnooze, + handleStartTrial, + shouldShow, + startingTrial, + }; +} diff --git a/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts b/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts index 8372b22f0..5beca6d75 100644 --- a/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts +++ b/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts @@ -10,6 +10,8 @@ import alertTargetTypesSource from '@/utils/alertTargetTypes.ts?raw'; import resourceBadgesSource from '@/components/Infrastructure/resourceBadges.ts?raw'; import commandPaletteModalSource from '@/components/shared/CommandPaletteModal.tsx?raw'; import commandPaletteModelSource from '@/components/shared/commandPaletteModel.ts?raw'; +import activeUseTrialNudgeSource from '@/components/shared/ActiveUseTrialNudge.tsx?raw'; +import activeUseTrialNudgeModelSource from '@/components/shared/activeUseTrialNudgeModel.ts?raw'; import collapsibleSearchInputSource from '@/components/shared/CollapsibleSearchInput.tsx?raw'; import collapsibleSearchInputModelSource from '@/components/shared/collapsibleSearchInputModel.ts?raw'; import containerUpdateBadgeSource from '@/components/shared/ContainerUpdateBadge.tsx?raw'; @@ -47,6 +49,7 @@ import interactiveSparklineModelSource from '@/components/shared/interactiveSpar import infrastructureSelectorModelSource from '@/components/shared/infrastructureSelectorModel.ts?raw'; import sharedInfrastructureSummaryTableModelSource from '@/components/shared/infrastructureSummaryTableModel.ts?raw'; import commandPaletteStateSource from '@/components/shared/useCommandPaletteState.ts?raw'; +import activeUseTrialNudgeStateSource from '@/components/shared/useActiveUseTrialNudgeState.ts?raw'; import collapsibleSearchInputStateSource from '@/components/shared/useCollapsibleSearchInputState.ts?raw'; import containerUpdateButtonStateSource from '@/components/shared/useContainerUpdateButtonState.ts?raw'; import dialogStateSource from '@/components/shared/useDialogState.ts?raw'; @@ -2709,6 +2712,24 @@ describe('frontend resource type boundaries', () => { expect(searchTipsPopoverModelSource).toContain('getSearchTipsPopoverPositionClass'); expect(searchTipsPopoverModelSource).toContain('getSearchTipsPopoverTriggerVariant'); expect(searchTipsPopoverModelSource).toContain('shouldSearchTipsPopoverOpenOnHover'); + expect(activeUseTrialNudgeSource).toContain('useActiveUseTrialNudgeState'); + expect(activeUseTrialNudgeSource).toContain('ACTIVE_USE_TRIAL_NUDGE_TITLE'); + expect(activeUseTrialNudgeSource).not.toContain('createSignal'); + expect(activeUseTrialNudgeSource).not.toContain('createMemo'); + expect(activeUseTrialNudgeSource).not.toContain('startProTrial'); + expect(activeUseTrialNudgeSource).not.toContain('localStorage'); + expect(activeUseTrialNudgeSource).not.toContain('setInterval'); + expect(activeUseTrialNudgeStateSource).toContain('createSignal'); + expect(activeUseTrialNudgeStateSource).toContain('createMemo'); + expect(activeUseTrialNudgeStateSource).toContain('window.localStorage'); + expect(activeUseTrialNudgeStateSource).toContain('setInterval'); + expect(activeUseTrialNudgeStateSource).toContain('startProTrial'); + expect(activeUseTrialNudgeStateSource).toContain('snoozeUpsell'); + expect(activeUseTrialNudgeModelSource).toContain('ACTIVE_USE_TRIAL_NUDGE_SNOOZE_KEY'); + expect(activeUseTrialNudgeModelSource).toContain('ACTIVE_USE_TRIAL_NUDGE_FIRST_SEEN_KEY'); + expect(activeUseTrialNudgeModelSource).toContain('isActiveUseTrialNudgeEligible'); + expect(activeUseTrialNudgeModelSource).toContain('isActiveUseTrialNudgeOldEnough'); + expect(activeUseTrialNudgeModelSource).toContain('ACTIVE_USE_TRIAL_NUDGE_TITLE'); expect(whatsNewModalSource).toContain('useWhatsNewModalState'); expect(whatsNewModalSource).toContain('WHATS_NEW_FEATURE_CARDS'); expect(whatsNewModalSource).not.toContain('createLocalStorageBooleanSignal');