mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 14:13:43 +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:
+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