mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 21:23:45 +01:00
[web] Add cancel-loss-messaging split test (#36363)
* [web] Add cancel-loss-messaging split test Populate the cancel confirmation step with plan-specific losses when the cancel-loss-messaging split test is enabled: "Are you sure you want to cancel?" heading, the subscription end date, and a card listing what the user loses moving to the Free plan (compile timeout, AI, collaborators, history), with the lost benefit struck through next to its Free-plan replacement. Shown only to non-trial Student/Standard/Pro individual subscribers with no other retention offer (trial extension or downgrade upsell) — those keep the existing confirmation. Copy matches the mockups in the ticket. No new analytics events; the existing subscription-page-cancel-button-click / subscription-cancelled funnel is used. Part of #35765 * [web] Consolidate ActiveSubscription stories, add cancel confirmation story Replace the 13 fixture-per-story exports with a single Dashboard story driven by controls (subscription fixture select, flexible licensing, cancel-loss-messaging variant), using the CSF factories pattern. Add a CancelConfirmation story that renders the cancel confirmation step directly so the cancel-loss-messaging variant can be previewed without clicking through, with a Professional fixture available to see the Pro loss rows. * [web] Use withSplitTests for the ActiveSubscription stories Register cancel-loss-messaging in defaultSplitTestsArgTypes and drive the stories' variant from the shared withSplitTests decorator (radio control, SplitTestContext provided from args) instead of hand-rolled meta tags and SplitTestProvider. Generalize withSplitTests' typing so it composes with CSF factories (preview.meta) as well as legacy meta objects. * [web] Fix Storybook indexing of the ActiveSubscription stories Storybook's static CSF indexer requires preview.meta() to receive an object literal, so spread withSplitTests' argTypes/decorators into the meta literal instead of wrapping the whole config in the helper call. * [web] Compose ActiveSubscription stories from orthogonal controls - Wrap the stories in #subscription-dashboard-root so the dashboard's scoped styles (e.g. the cancel-loss strikethrough) apply in Storybook - Replace the single fixture dropdown with independent controls (group, plan, period, currency, trial, status, pending plan change, add-ons, coupons, pending license change), building the subscription from the fixtures' base + patches - Guard impossible mixes with argTypes `if` conditions: individual-only controls hide when group is on, and pendingLicenseChange only shows for groups * [web] Add student plan to the ActiveSubscription story controls The cancel-loss-messaging variant has student-specific rows (AI Assistant / 10 collaborators), so the plan control needs a student option. Groups have no student plans, so for group subscriptions the student choice maps to the Standard group fixture. * [web] Provide ol-plans in the ActiveSubscription stories The cancel confirmation's downgrade branch (monthly Standard is isEligibleForDowngradeUpsell) looks up paid-personal in ol-plans and spins forever when the meta tag is missing. Provide ol-plans and ol-groupPlans from the fixtures, matching the test render helper. GitOrigin-RevId: e8491c8d39ed059e42ea3442e8d70c3ef304b009
This commit is contained in:
@@ -173,6 +173,11 @@ async function userSubscriptionPage(req, res) {
|
||||
'group-discount-10'
|
||||
)
|
||||
await SplitTestHandler.promises.getAssignment(req, res, 'ai-toggling')
|
||||
await SplitTestHandler.promises.getAssignment(
|
||||
req,
|
||||
res,
|
||||
'cancel-loss-messaging'
|
||||
)
|
||||
|
||||
const showGroupDiscount = groupPricingDiscount.variant === 'enabled'
|
||||
|
||||
|
||||
@@ -125,6 +125,8 @@
|
||||
"ai_assist_unavailable": "",
|
||||
"ai_assistance": "",
|
||||
"ai_assistant": "",
|
||||
"ai_assistant_and_higher_ai_allowance": "",
|
||||
"ai_assistant_and_max_ai_allowance": "",
|
||||
"ai_assistant_disabled": "",
|
||||
"ai_can_make_mistakes": "",
|
||||
"ai_features": "",
|
||||
@@ -183,6 +185,7 @@
|
||||
"are_you_affiliated_with_an_institution": "",
|
||||
"are_you_getting_an_undefined_control_sequence_error": "",
|
||||
"are_you_still_at": "",
|
||||
"are_you_sure_you_want_to_cancel": "",
|
||||
"are_you_sure_you_want_to_cancel_add_on": "",
|
||||
"as_email": "",
|
||||
"ask_proj_owner_to_unlink_from_current_github": "",
|
||||
@@ -213,6 +216,7 @@
|
||||
"back_to_subscription": "",
|
||||
"back_to_your_projects": "",
|
||||
"basic_compile_time": "",
|
||||
"basic_compile_timeout_for_small_projects": "",
|
||||
"been_offline_changes_saved_in_browser": "",
|
||||
"before_you_use_error_assistant": "",
|
||||
"beta_program_already_participating": "",
|
||||
@@ -256,6 +260,7 @@
|
||||
"cancel_personal_subscription_first": "",
|
||||
"cancel_subscription": "",
|
||||
"cancel_your_subscription": "",
|
||||
"cancelling_will_end_your_subscription_on": "",
|
||||
"cannot_invite_non_user": "",
|
||||
"cannot_invite_self": "",
|
||||
"cannot_verify_user_not_robot": "",
|
||||
@@ -774,6 +779,7 @@
|
||||
"from_your_library": "",
|
||||
"from_your_project": "",
|
||||
"full_doc_history": "",
|
||||
"full_project_history": "",
|
||||
"full_width": "",
|
||||
"future_payments": "",
|
||||
"generate_from_text": "",
|
||||
@@ -903,6 +909,7 @@
|
||||
"hide_outline": "",
|
||||
"higher_ai_allowance": "",
|
||||
"history": "",
|
||||
"history_24_hours_only": "",
|
||||
"history_add_label": "",
|
||||
"history_are_you_sure_delete_label": "",
|
||||
"history_compare_from_this_version": "",
|
||||
@@ -1080,6 +1087,7 @@
|
||||
"kb_suggestions_enquiry": "",
|
||||
"keep_current_plan": "",
|
||||
"keep_personal_projects_separate": "",
|
||||
"keep_subscription": "",
|
||||
"keep_your_account_safe_add_another_email": "",
|
||||
"keybindings": "",
|
||||
"keyboard_shortcuts": "",
|
||||
@@ -1324,6 +1332,7 @@
|
||||
"next_suggestion": "",
|
||||
"no_actions": "",
|
||||
"no_add_on_purchase_while_paused": "",
|
||||
"no_ai_assistant": "",
|
||||
"no_audit_logs_found": "",
|
||||
"no_borders": "",
|
||||
"no_caption": "",
|
||||
@@ -1382,6 +1391,7 @@
|
||||
"ok_continue_to_project": "",
|
||||
"ok_join_project": "",
|
||||
"on": "",
|
||||
"one_collaborator_per_project": "",
|
||||
"one_step_away_from_professional_features": "",
|
||||
"only_add_people_who_dont_yet_have_access": "",
|
||||
"only_group_admin_or_managers_can_delete_your_account_1": "",
|
||||
@@ -1483,6 +1493,7 @@
|
||||
"personal_library": "",
|
||||
"pick_up_where_you_left_off": "",
|
||||
"plan": "",
|
||||
"plan_24x_longer_compile_timeout": "",
|
||||
"plan_tooltip": "",
|
||||
"please_ask_the_project_owner_to_upgrade_more_collaborators": "",
|
||||
"please_ask_the_project_owner_to_upgrade_to_track_changes": "",
|
||||
@@ -1919,6 +1930,7 @@
|
||||
"size": "",
|
||||
"skip": "",
|
||||
"skip_welcome_animation": "",
|
||||
"small_ai_allowance_no_assistant": "",
|
||||
"some_dois_could_not_be_resolved": "",
|
||||
"something_not_right": "",
|
||||
"something_went_wrong": "",
|
||||
@@ -2093,6 +2105,7 @@
|
||||
"template_title_taken_from_project_title": "",
|
||||
"templates": "",
|
||||
"temporarily_hides_the_preview": "",
|
||||
"ten_collaborators_per_project": "",
|
||||
"terminated": "",
|
||||
"test": "",
|
||||
"test_configuration": "",
|
||||
@@ -2559,6 +2572,7 @@
|
||||
"you_re_offline": "",
|
||||
"you_unpaused_your_subscription": "",
|
||||
"you_will_be_able_to_reassign_subscription": "",
|
||||
"youll_be_moved_to_the_free_plan": "",
|
||||
"youll_get_best_results_in_visual_but_can_be_used_in_source": "",
|
||||
"youll_need_to_ask_the_github_repository_owner": "",
|
||||
"youll_no_longer_need_to_remember_credentials": "",
|
||||
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { TFunction } from 'i18next'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import OLButton from '@/shared/components/ol/ol-button'
|
||||
import { useSubscriptionDashboardContext } from '../../../../../context/subscription-dashboard-context'
|
||||
|
||||
export type LossMessagingPlanType = 'student' | 'collaborator' | 'professional'
|
||||
|
||||
export function getLossMessagingPlanType(
|
||||
planCode: string
|
||||
): LossMessagingPlanType | null {
|
||||
if (planCode.startsWith('professional')) return 'professional'
|
||||
if (planCode.startsWith('collaborator')) return 'collaborator'
|
||||
if (planCode.startsWith('student')) return 'student'
|
||||
return null
|
||||
}
|
||||
|
||||
type LossRow = { lost: string; replacement: string }
|
||||
|
||||
function getAiLossRow(planType: LossMessagingPlanType, t: TFunction): LossRow {
|
||||
if (planType === 'student') {
|
||||
return { lost: t('ai_assistant'), replacement: t('no_ai_assistant') }
|
||||
}
|
||||
if (planType === 'collaborator') {
|
||||
return {
|
||||
lost: t('ai_assistant_and_higher_ai_allowance'),
|
||||
replacement: t('small_ai_allowance_no_assistant'),
|
||||
}
|
||||
}
|
||||
return {
|
||||
lost: t('ai_assistant_and_max_ai_allowance'),
|
||||
replacement: t('small_ai_allowance_no_assistant'),
|
||||
}
|
||||
}
|
||||
|
||||
function getCollaboratorsLossRow(
|
||||
planType: LossMessagingPlanType,
|
||||
t: TFunction
|
||||
): LossRow {
|
||||
return {
|
||||
lost:
|
||||
planType === 'professional'
|
||||
? t('unlimited_collaborators_per_project')
|
||||
: t('ten_collaborators_per_project'),
|
||||
replacement: t('one_collaborator_per_project'),
|
||||
}
|
||||
}
|
||||
|
||||
function getLossRows(planType: LossMessagingPlanType, t: TFunction): LossRow[] {
|
||||
return [
|
||||
{
|
||||
lost: t('plan_24x_longer_compile_timeout'),
|
||||
replacement: t('basic_compile_timeout_for_small_projects'),
|
||||
},
|
||||
getAiLossRow(planType, t),
|
||||
getCollaboratorsLossRow(planType, t),
|
||||
{
|
||||
lost: t('full_project_history'),
|
||||
replacement: t('history_24_hours_only'),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export function CancelSubscriptionLossMessaging({
|
||||
planType,
|
||||
terminationDate,
|
||||
onCancelSubscription,
|
||||
isButtonDisabled,
|
||||
isCancelLoading,
|
||||
}: {
|
||||
planType: LossMessagingPlanType
|
||||
terminationDate: string
|
||||
onCancelSubscription: () => void
|
||||
isButtonDisabled: boolean
|
||||
isCancelLoading: boolean
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const { setShowCancellation } = useSubscriptionDashboardContext()
|
||||
|
||||
return (
|
||||
<div className="cancel-loss-messaging">
|
||||
<h2>{t('are_you_sure_you_want_to_cancel')}</h2>
|
||||
<p>
|
||||
{t('cancelling_will_end_your_subscription_on', { terminationDate })}
|
||||
</p>
|
||||
<div className="cancel-loss-card">
|
||||
<h3 className="cancel-loss-card-heading">
|
||||
{t('youll_be_moved_to_the_free_plan')}
|
||||
</h3>
|
||||
<ul className="cancel-loss-list">
|
||||
{getLossRows(planType, t).map(row => (
|
||||
<li key={row.lost} className="cancel-loss-list-item">
|
||||
<MaterialIcon type="cancel" className="cancel-loss-icon" />
|
||||
<span>
|
||||
<del className="cancel-loss-lost">{row.lost}</del>{' '}
|
||||
{row.replacement}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="cancel-loss-actions">
|
||||
<OLButton
|
||||
variant="secondary"
|
||||
disabled={isButtonDisabled}
|
||||
onClick={() => setShowCancellation(false)}
|
||||
>
|
||||
{t('keep_subscription')}
|
||||
</OLButton>
|
||||
<OLButton
|
||||
variant="danger-ghost"
|
||||
disabled={isButtonDisabled}
|
||||
isLoading={isCancelLoading}
|
||||
loadingLabel={t('processing_uppercase') + '…'}
|
||||
onClick={onCancelSubscription}
|
||||
>
|
||||
{t('cancel_subscription')}
|
||||
</OLButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+35
@@ -14,6 +14,12 @@ import ExtendTrialButton from './extend-trial-button'
|
||||
import { useLocation } from '../../../../../../../shared/hooks/use-location'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import OLButton from '@/shared/components/ol/ol-button'
|
||||
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||
import isInFreeTrial from '../../../../../util/is-in-free-trial'
|
||||
import {
|
||||
CancelSubscriptionLossMessaging,
|
||||
getLossMessagingPlanType,
|
||||
} from './cancel-subscription-loss-messaging'
|
||||
|
||||
const planCodeToDowngradeTo = 'paid-personal'
|
||||
|
||||
@@ -136,6 +142,7 @@ export function CancelSubscription() {
|
||||
const location = useLocation()
|
||||
const { personalSubscription, plans, userCanExtendTrial } =
|
||||
useSubscriptionDashboardContext()
|
||||
const lossMessagingEnabled = useFeatureFlag('cancel-loss-messaging')
|
||||
const {
|
||||
isLoading: isLoadingCancel,
|
||||
isError: isErrorCancel,
|
||||
@@ -176,6 +183,34 @@ export function CancelSubscription() {
|
||||
|
||||
const showExtendFreeTrial = userCanExtendTrial
|
||||
|
||||
// `cancel-loss-messaging` split test: show plan-specific losses instead of
|
||||
// the bare confirmation, for non-trial individual subscribers with no other
|
||||
// retention offer (trial extension or downgrade)
|
||||
const lossMessagingPlanType = getLossMessagingPlanType(
|
||||
personalSubscription.planCode
|
||||
)
|
||||
const showLossMessaging =
|
||||
lossMessagingEnabled &&
|
||||
!showExtendFreeTrial &&
|
||||
!showDowngrade &&
|
||||
lossMessagingPlanType !== null &&
|
||||
!isInFreeTrial(personalSubscription.payment.trialEndsAt)
|
||||
|
||||
if (showLossMessaging) {
|
||||
return (
|
||||
<>
|
||||
{isErrorCancel && <GenericErrorAlert />}
|
||||
<CancelSubscriptionLossMessaging
|
||||
planType={lossMessagingPlanType}
|
||||
terminationDate={personalSubscription.payment.nextPaymentDueDate}
|
||||
onCancelSubscription={handleCancelSubscription}
|
||||
isButtonDisabled={isButtonDisabled}
|
||||
isCancelLoading={isSuccessCancel || isLoadingCancel}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="text-center">
|
||||
|
||||
@@ -31,6 +31,50 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cancel-loss-card {
|
||||
background-color: var(--bg-light-secondary);
|
||||
border-radius: var(--border-radius-base);
|
||||
padding: var(--spacing-06);
|
||||
margin-bottom: var(--spacing-06);
|
||||
|
||||
.cancel-loss-card-heading {
|
||||
@include body-base;
|
||||
|
||||
font-weight: 600;
|
||||
margin-bottom: var(--spacing-04);
|
||||
}
|
||||
}
|
||||
|
||||
.cancel-loss-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
.cancel-loss-list-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-04);
|
||||
|
||||
& + .cancel-loss-list-item {
|
||||
margin-top: var(--spacing-04);
|
||||
}
|
||||
}
|
||||
|
||||
.cancel-loss-icon {
|
||||
color: var(--content-danger);
|
||||
}
|
||||
|
||||
.cancel-loss-lost {
|
||||
color: var(--content-danger);
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
|
||||
.cancel-loss-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-04);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -154,6 +154,8 @@
|
||||
"ai_assist_unavailable": "AI features are now included in every paid plan. See the <0>pricing page</0> for what each plan includes.",
|
||||
"ai_assistance": "AI assistance",
|
||||
"ai_assistant": "AI Assistant",
|
||||
"ai_assistant_and_higher_ai_allowance": "AI Assistant and higher AI allowance",
|
||||
"ai_assistant_and_max_ai_allowance": "AI Assistant and max AI allowance",
|
||||
"ai_assistant_disabled": "AI Assistant disabled",
|
||||
"ai_assistant_explanation": "A LaTeX-fluent AI Assistant built into your editor.",
|
||||
"ai_can_make_mistakes": "AI can make mistakes. Review fixes before you apply them.",
|
||||
@@ -228,6 +230,7 @@
|
||||
"are_you_affiliated_with_an_institution": "Are you affiliated with an institution?",
|
||||
"are_you_getting_an_undefined_control_sequence_error": "Are you getting an Undefined Control Sequence error? If you are, make sure you’ve loaded the graphicx package—<0>\\usepackage{graphicx}</0>—in the preamble (first section of code) in your document. <1>Learn more</1>",
|
||||
"are_you_still_at": "Are you still at <0>__institutionName__</0>?",
|
||||
"are_you_sure_you_want_to_cancel": "Are you sure you want to cancel?",
|
||||
"are_you_sure_you_want_to_cancel_add_on": "Are you sure you want to cancel the <strong>__addOnName__</strong> add-on?",
|
||||
"article": "Article",
|
||||
"articles": "Articles",
|
||||
@@ -272,6 +275,7 @@
|
||||
"basic_ai_allowance_tooltip": "5 AI uses per day across available AI tools.",
|
||||
"basic_compile_time": "Basic compile time",
|
||||
"basic_compile_timeout": "Basic compile timeout",
|
||||
"basic_compile_timeout_for_small_projects": "Basic compile timeout (for small projects)",
|
||||
"basic_compile_timeout_on_fast_servers": "Basic compile timeout on fast servers",
|
||||
"been_offline_changes_saved_in_browser": "You’ve been offline for a while. Your changes are saved in the browser.",
|
||||
"before_you_use_error_assistant": "Before you use Error Assist",
|
||||
@@ -329,6 +333,7 @@
|
||||
"cancel_personal_subscription_first": "You already have an individual subscription, would you like us to cancel this first before joining the group licence?",
|
||||
"cancel_subscription": "Cancel subscription",
|
||||
"cancel_your_subscription": "Cancel your subscription",
|
||||
"cancelling_will_end_your_subscription_on": "Cancelling will end your subscription on __terminationDate__.",
|
||||
"cannot_invite_non_user": "Can’t send invite. Recipient must already have an __appName__ account",
|
||||
"cannot_invite_self": "Can’t send invite to yourself",
|
||||
"cannot_verify_user_not_robot": "Sorry, we could not verify that you are not a robot. Please check that Google reCAPTCHA is not being blocked by an ad blocker or firewall.",
|
||||
@@ -993,6 +998,7 @@
|
||||
"from_your_project": "From your project",
|
||||
"full_doc_history": "Full document history",
|
||||
"full_document_history": "Full document <0>history</0>",
|
||||
"full_project_history": "Full project history",
|
||||
"full_width": "Full width",
|
||||
"future_payments": "Future payments",
|
||||
"gallery": "Gallery",
|
||||
@@ -1153,6 +1159,7 @@
|
||||
"hide_outline": "Hide File outline",
|
||||
"higher_ai_allowance": "Higher AI allowance",
|
||||
"history": "History",
|
||||
"history_24_hours_only": "24 hours history only",
|
||||
"history_add_label": "Add label",
|
||||
"history_are_you_sure_delete_label": "Are you sure you want to delete the following label",
|
||||
"history_compare_from_this_version": "Compare from this version",
|
||||
@@ -1376,6 +1383,7 @@
|
||||
"kb_suggestions_enquiry": "Have you checked our <0>__kbLink__</0>?",
|
||||
"keep_current_plan": "Keep my current plan",
|
||||
"keep_personal_projects_separate": "Keep personal projects separate",
|
||||
"keep_subscription": "Keep subscription",
|
||||
"keep_your_account_safe": "Keep your account safe",
|
||||
"keep_your_account_safe_add_another_email": "Keep your account safe and make sure you don’t lose access to it by adding another email address.",
|
||||
"keep_your_email_updated": "Keep your email updated so that you don’t lose access to your account and data.",
|
||||
@@ -1703,6 +1711,7 @@
|
||||
"no": "Norwegian",
|
||||
"no_actions": "No actions",
|
||||
"no_add_on_purchase_while_paused": "You need to unpause or cancel your existing subscription to buy an add-on.",
|
||||
"no_ai_assistant": "No AI Assistant",
|
||||
"no_articles_matching_your_tags": "There are no articles matching your tags",
|
||||
"no_audit_logs_found": "No audit logs found",
|
||||
"no_borders": "No borders",
|
||||
@@ -1929,6 +1938,7 @@
|
||||
"pl": "Polish",
|
||||
"plan": "Plan",
|
||||
"plan_24x_basic_compile_timeout": "24x Basic compile timeout",
|
||||
"plan_24x_longer_compile_timeout": "24x longer compile timeout",
|
||||
"plan_tooltip": "You’re on the __plan__ plan. Click to find out how to make the most of your Overleaf premium features.",
|
||||
"planned_maintenance": "Planned Maintenance",
|
||||
"plans_and_pricing": "Plans and Pricing",
|
||||
@@ -2461,6 +2471,7 @@
|
||||
"skip_welcome_animation": "Skip animation and continue",
|
||||
"slash_month": "/month",
|
||||
"slash_year": "/year",
|
||||
"small_ai_allowance_no_assistant": "Small AI allowance (no Assistant)",
|
||||
"solutions": "Solutions",
|
||||
"some_dois_could_not_be_resolved": "Some or all DOIs could not be resolved.",
|
||||
"something_not_right": "Something’s not right",
|
||||
@@ -3209,6 +3220,7 @@
|
||||
"you_unpaused_your_subscription": "You unpaused your subscription.",
|
||||
"you_will_be_able_to_contact_us_any_time_to_share_your_feedback": "<0>You will be able to contact us</0> any time to share your feedback",
|
||||
"you_will_be_able_to_reassign_subscription": "You will be able to reassign their subscription membership to another person in your organization",
|
||||
"youll_be_moved_to_the_free_plan": "You’ll be moved to the Free plan",
|
||||
"youll_get_best_results_in_visual_but_can_be_used_in_source": "You’ll get the best results from using this tool in the <0>Visual Editor</0>, although you can still use it to insert tables in the <1>Code Editor</1>. Once you’ve selected the number of rows and columns you need, the table will appear in your document and you can double click in a cell to add contents to it.",
|
||||
"youll_need_to_ask_the_github_repository_owner": "You’ll need to ask the GitHub repository owner (<0>__repoOwnerEmail__</0>) to reconnect the project.",
|
||||
"youll_no_longer_need_to_remember_credentials": "You’ll no longer need to remember a separate email address and password. Instead, you will use single-sign on to login to Overleaf. <0>Read more about SSO</0>.",
|
||||
|
||||
+80
@@ -387,6 +387,86 @@ describe('<ActiveSubscription />', function () {
|
||||
expect(hiddenText.getAttribute('aria-hidden')).to.equal('true')
|
||||
})
|
||||
|
||||
describe('cancel-loss-messaging split test', function () {
|
||||
const enabledVariant: MetaTag = {
|
||||
name: 'ol-splitTestVariants',
|
||||
value: { 'cancel-loss-messaging': 'enabled' },
|
||||
}
|
||||
|
||||
it('shows the loss messaging for a Standard subscriber when enabled', function () {
|
||||
renderActiveSubscription(annualActiveSubscription, [enabledVariant])
|
||||
showConfirmCancelUI()
|
||||
|
||||
screen.getByRole('heading', {
|
||||
name: 'Are you sure you want to cancel?',
|
||||
})
|
||||
screen.getByText(
|
||||
`Cancelling will end your subscription on ${annualActiveSubscription.payment.nextPaymentDueDate}.`
|
||||
)
|
||||
screen.getByRole('heading', {
|
||||
name: 'You’ll be moved to the Free plan',
|
||||
})
|
||||
screen.getByText('AI Assistant and higher AI allowance')
|
||||
screen.getByText('10 collaborators per project')
|
||||
screen.getByRole('button', { name: 'Keep subscription' })
|
||||
screen.getByRole('button', { name: 'Cancel subscription' })
|
||||
})
|
||||
|
||||
it('shows the Pro losses for a Professional subscriber', function () {
|
||||
const proSubscription = cloneDeep(annualActiveSubscription)
|
||||
proSubscription.planCode = 'professional-annual'
|
||||
renderActiveSubscription(proSubscription, [enabledVariant])
|
||||
showConfirmCancelUI()
|
||||
|
||||
screen.getByText('AI Assistant and max AI allowance')
|
||||
screen.getByText('Unlimited collaborators per project')
|
||||
})
|
||||
|
||||
it('cancels the subscription and redirects', async function () {
|
||||
fetchMock.post(cancelSubscriptionUrl, { status: 200 })
|
||||
renderActiveSubscription(annualActiveSubscription, [enabledVariant])
|
||||
showConfirmCancelUI()
|
||||
fireEvent.click(
|
||||
screen.getByRole('button', { name: 'Cancel subscription' })
|
||||
)
|
||||
const assignStub = this.locationWrapperStub.assign
|
||||
await waitFor(() => {
|
||||
expect(assignStub).to.have.been.called
|
||||
})
|
||||
sinon.assert.calledWithMatch(assignStub, '/user/subscription/canceled')
|
||||
})
|
||||
|
||||
it('returns to the subscription dashboard on "Keep subscription"', function () {
|
||||
renderActiveSubscription(annualActiveSubscription, [enabledVariant])
|
||||
showConfirmCancelUI()
|
||||
fireEvent.click(
|
||||
screen.getByRole('button', { name: 'Keep subscription' })
|
||||
)
|
||||
expect(screen.queryByText('Are you sure you want to cancel?')).to.be
|
||||
.null
|
||||
})
|
||||
|
||||
it('keeps the default confirmation for a subscriber in trial', function () {
|
||||
renderActiveSubscription(trialCollaboratorSubscription, [
|
||||
enabledVariant,
|
||||
])
|
||||
showConfirmCancelUI()
|
||||
|
||||
screen.getByText('We’d love you to stay')
|
||||
expect(screen.queryByText('Are you sure you want to cancel?')).to.be
|
||||
.null
|
||||
})
|
||||
|
||||
it('keeps the default confirmation when the split test is not enabled', function () {
|
||||
renderActiveSubscription(annualActiveSubscription)
|
||||
showConfirmCancelUI()
|
||||
|
||||
screen.getByText('We’d love you to stay')
|
||||
expect(screen.queryByText('Are you sure you want to cancel?')).to.be
|
||||
.null
|
||||
})
|
||||
})
|
||||
|
||||
describe('extend trial', function () {
|
||||
const canExtend: MetaTag = {
|
||||
name: 'ol-userCanExtendTrial',
|
||||
|
||||
Reference in New Issue
Block a user