Merge pull request #11681 from overleaf/jel-confirm-subscription-change

[web] Plan change request in subscription dash

GitOrigin-RevId: 9afe22b2da035f887c6fa9abe8129711d492108c
This commit is contained in:
Jessica Lawshe 2023-02-16 10:29:49 -06:00 committed by Copybot
parent 4dcdd2e07a
commit d539aaf226
11 changed files with 523 additions and 75 deletions

View file

@ -224,6 +224,7 @@
"error": "",
"error_performing_request": "",
"example_project": "",
"existing_plan_active_until_term_end": "",
"expand": "",
"export_csv": "",
"export_project_to_github": "",
@ -286,6 +287,7 @@
"galileo_suggestion_feedback_button": "",
"galileo_suggestions_loading_error": "",
"galileo_toggle_description": "",
"generic_if_problem_continues_contact_us": "",
"generic_linked_file_compile_error": "",
"generic_something_went_wrong": "",
"get_collaborative_benefits": "",
@ -566,6 +568,7 @@
"proceed_to_paypal": "",
"proceeding_to_paypal_takes_you_to_the_paypal_site_to_pay": "",
"processing": "",
"processing_uppercase": "",
"professional": "",
"project": "",
"project_approaching_file_limit": "",
@ -633,6 +636,7 @@
"resend_confirmation_email": "",
"resending_confirmation_email": "",
"reverse_x_sort_order": "",
"revert_pending_plan_change": "",
"review": "",
"revoke": "",
"revoke_invite": "",
@ -733,6 +737,8 @@
"subscription_admins_cannot_be_deleted": "",
"subscription_canceled_and_terminate_on_x": "",
"subscription_will_remain_active_until_end_of_billing_period_x": "",
"sure_you_want_to_cancel_plan_change": "",
"sure_you_want_to_change_plan": "",
"sure_you_want_to_delete": "",
"switch_to_editor": "",
"switch_to_pdf": "",

View file

@ -2,7 +2,9 @@ import { useTranslation } from 'react-i18next'
import LoadingSpinner from '../../../../../../../shared/components/loading-spinner'
import { useSubscriptionDashboardContext } from '../../../../../context/subscription-dashboard-context'
import { ChangeToGroupPlan } from './change-to-group-plan'
import { ConfirmChangePlanModal } from './confirm-change-plan-modal'
import { IndividualPlansTable } from './individual-plans-table'
import { KeepCurrentPlanModal } from './keep-current-plan-modal'
export function ChangePlan() {
const { t } = useTranslation()
@ -28,6 +30,8 @@ export function ChangePlan() {
<h2>{t('change_plan')}</h2>
<IndividualPlansTable plans={plans} />
<ChangeToGroupPlan />
<ConfirmChangePlanModal />
<KeepCurrentPlanModal />
</>
)
}

View file

@ -0,0 +1,101 @@
import { useState } from 'react'
import { Modal } from 'react-bootstrap'
import { useTranslation, Trans } from 'react-i18next'
import { postJSON } from '../../../../../../../infrastructure/fetch-json'
import AccessibleModal from '../../../../../../../shared/components/accessible-modal'
import getMeta from '../../../../../../../utils/meta'
import { useSubscriptionDashboardContext } from '../../../../../context/subscription-dashboard-context'
import { subscriptionUrl } from '../../../../../data/subscription-url'
export function ConfirmChangePlanModal() {
const modalId = 'change-to-plan'
const [error, setError] = useState(false)
const [inflight, setInflight] = useState(false)
const { t } = useTranslation()
const { handleCloseModal, modalIdShown, plans, planCodeToChangeTo } =
useSubscriptionDashboardContext()
const planCodesChangingAtTermEnd = getMeta('ol-planCodesChangingAtTermEnd')
async function handleConfirmChange() {
setError(false)
setInflight(true)
try {
await postJSON(`${subscriptionUrl}?origin=confirmChangePlan`, {
body: {
plan_code: planCodeToChangeTo,
},
})
} catch (e) {
setError(true)
setInflight(false)
}
window.location.reload()
}
if (modalIdShown !== modalId || !planCodeToChangeTo) return null
const plan = plans.find(p => p.planCode === planCodeToChangeTo)
if (!plan) return null
const planWillChangeAtTermEnd =
planCodesChangingAtTermEnd?.indexOf(planCodeToChangeTo) > -1
return (
<AccessibleModal
id={modalId}
show
animation
onHide={handleCloseModal}
backdrop="static"
>
<Modal.Header>
<Modal.Title>{t('change_plan')}</Modal.Title>
</Modal.Header>
<Modal.Body>
{error && (
<div className="alert alert-warning">
{t('generic_something_went_wrong')}. {t('try_again')}.{' '}
{t('generic_if_problem_continues_contact_us')}.
</div>
)}
<p>
<Trans
i18nKey="sure_you_want_to_change_plan"
values={{
planName: plan.name,
}}
components={[
// eslint-disable-next-line react/jsx-key
<strong />,
]}
/>
</p>
{planWillChangeAtTermEnd && (
<>
<p>{t('existing_plan_active_until_term_end')}</p>
<p>{t('want_change_to_apply_before_plan_end')}</p>
</>
)}
</Modal.Body>
<Modal.Footer>
<button
disabled={inflight}
className="btn btn-secondary"
onClick={handleCloseModal}
>
{t('cancel')}
</button>
<button
disabled={inflight}
className="btn btn-primary"
onClick={handleConfirmChange}
>
{!inflight ? t('change_plan') : t('processing_uppercase') + '…'}
</button>
</Modal.Footer>
</AccessibleModal>
)
}

View file

@ -3,37 +3,33 @@ import { Plan } from '../../../../../../../../../types/subscription/plan'
import Icon from '../../../../../../../shared/components/icon'
import { useSubscriptionDashboardContext } from '../../../../../context/subscription-dashboard-context'
function ChangeToPlanButton({ plan }: { plan: Plan }) {
function ChangeToPlanButton({ planCode }: { planCode: string }) {
const { t } = useTranslation()
// for when the user selected to change a plan, but the plan change is still pending
const { handleOpenModal } = useSubscriptionDashboardContext()
const handleClick = () => {
handleOpenModal('change-to-plan', planCode)
}
return (
<form>
{/* todo: ng-model="plan_code" */}
<input type="hidden" name="plan_code" value={plan.planCode} />
{/* todo: handle submit changePlan */}
<input
type="submit"
value={t('change_to_this_plan')}
className="btn btn-primary"
/>
</form>
<button className="btn btn-primary" onClick={handleClick}>
{t('change_to_this_plan')}
</button>
)
}
function KeepCurrentPlanButton({ plan }: { plan: Plan }) {
const { t } = useTranslation()
// for when the user selected to change a plan, but the plan change is still pending
const { handleOpenModal } = useSubscriptionDashboardContext()
const handleClick = () => {
handleOpenModal('keep-current-plan')
}
return (
<form>
{/* todo: ng-model="plan_code" */}
<input type="hidden" name="plan_code" value={plan.planCode} />
{/* todo: handle submit cancelPendingPlanChange */}
<input
type="submit"
value={t('keep_current_plan')}
className="btn btn-primary"
/>
</form>
<button className="btn btn-primary" onClick={handleClick}>
{t('keep_current_plan')}
</button>
)
}
@ -61,7 +57,7 @@ function ChangePlanButton({ plan }: { plan: Plan }) {
</b>
)
} else {
return <ChangeToPlanButton plan={plan} />
return <ChangeToPlanButton planCode={plan.planCode} />
}
}

View file

@ -0,0 +1,85 @@
import { useState } from 'react'
import { Modal } from 'react-bootstrap'
import { useTranslation, Trans } from 'react-i18next'
import { postJSON } from '../../../../../../../infrastructure/fetch-json'
import AccessibleModal from '../../../../../../../shared/components/accessible-modal'
import { useSubscriptionDashboardContext } from '../../../../../context/subscription-dashboard-context'
import { cancelPendingSubscriptionChangeUrl } from '../../../../../data/subscription-url'
export function KeepCurrentPlanModal() {
const modalId = 'keep-current-plan'
const [error, setError] = useState(false)
const [inflight, setInflight] = useState(false)
const { t } = useTranslation()
const { modalIdShown, handleCloseModal, personalSubscription } =
useSubscriptionDashboardContext()
async function confirmCancelPendingPlanChange() {
setError(false)
setInflight(true)
try {
await postJSON(cancelPendingSubscriptionChangeUrl)
} catch (e) {
setError(true)
setInflight(false)
}
window.location.reload()
}
if (modalIdShown !== modalId || !personalSubscription) return null
return (
<AccessibleModal
id={modalId}
show
animation
onHide={handleCloseModal}
backdrop="static"
>
<Modal.Header>
<Modal.Title>{t('change_plan')}</Modal.Title>
</Modal.Header>
<Modal.Body>
{error && (
<div className="alert alert-warning">
{t('generic_something_went_wrong')}. {t('try_again')}.{' '}
{t('generic_if_problem_continues_contact_us')}.
</div>
)}
<p>
<Trans
i18nKey="sure_you_want_to_cancel_plan_change"
values={{
planName: personalSubscription.plan.name,
}}
components={[
// eslint-disable-next-line react/jsx-key
<strong />,
]}
/>
</p>
</Modal.Body>
<Modal.Footer>
<button
disabled={inflight}
className="btn btn-secondary"
onClick={handleCloseModal}
>
{t('cancel')}
</button>
<button
disabled={inflight}
className="btn btn-primary"
onClick={confirmCancelPendingPlanChange}
>
{!inflight
? t('revert_pending_plan_change')
: t('processing_uppercase') + '…'}
</button>
</Modal.Footer>
</AccessibleModal>
)
}

View file

@ -19,15 +19,23 @@ import { loadDisplayPriceWithTaxPromise } from '../util/recurly-pricing'
import { isRecurlyLoaded } from '../util/is-recurly-loaded'
type SubscriptionDashboardContextValue = {
handleCloseModal: () => void
handleOpenModal: (modalIdToOpen: string, planCode?: string) => void
hasDisplayedSubscription: boolean
institutionMemberships?: Institution[]
managedGroupSubscriptions: ManagedGroupSubscription[]
managedInstitutions: ManagedInstitution[]
updateManagedInstitution: (institution: ManagedInstitution) => void
modalIdShown?: string
personalSubscription?: Subscription
plans: Plan[]
planCodeToChangeTo?: string
queryingIndividualPlansData: boolean
recurlyLoadError: boolean
setModalIdShown: React.Dispatch<React.SetStateAction<string | undefined>>
setPlanCodeToChangeTo: React.Dispatch<
React.SetStateAction<string | undefined>
>
setRecurlyLoadError: React.Dispatch<React.SetStateAction<boolean>>
showCancellation: boolean
setShowCancellation: React.Dispatch<React.SetStateAction<boolean>>
@ -44,12 +52,16 @@ export function SubscriptionDashboardProvider({
}: {
children: ReactNode
}) {
const [modalIdShown, setModalIdShown] = useState<string | undefined>()
const [recurlyLoadError, setRecurlyLoadError] = useState(false)
const [showCancellation, setShowCancellation] = useState(false)
const [showChangePersonalPlan, setShowChangePersonalPlan] = useState(false)
const [plans, setPlans] = useState([])
const [queryingIndividualPlansData, setQueryingIndividualPlansData] =
useState(true)
const [planCodeToChangeTo, setPlanCodeToChangeTo] = useState<
string | undefined
>()
const plansWithoutDisplayPrice = getMeta('ol-plans')
const institutionMemberships = getMeta('ol-currentInstitutionsWithLicence')
@ -111,18 +123,36 @@ export function SubscriptionDashboardProvider({
},
[]
)
const handleCloseModal = useCallback(() => {
setModalIdShown('')
setPlanCodeToChangeTo(undefined)
}, [setModalIdShown, setPlanCodeToChangeTo])
const handleOpenModal = useCallback(
(id, planCode) => {
setModalIdShown(id)
setPlanCodeToChangeTo(planCode)
},
[setModalIdShown, setPlanCodeToChangeTo]
)
const value = useMemo<SubscriptionDashboardContextValue>(
() => ({
handleCloseModal,
handleOpenModal,
hasDisplayedSubscription,
institutionMemberships,
managedGroupSubscriptions,
managedInstitutions,
updateManagedInstitution,
modalIdShown,
personalSubscription,
plans,
planCodeToChangeTo,
queryingIndividualPlansData,
recurlyLoadError,
setModalIdShown,
setPlanCodeToChangeTo,
setRecurlyLoadError,
showCancellation,
setShowCancellation,
@ -130,15 +160,21 @@ export function SubscriptionDashboardProvider({
setShowChangePersonalPlan,
}),
[
handleCloseModal,
handleOpenModal,
hasDisplayedSubscription,
institutionMemberships,
managedGroupSubscriptions,
managedInstitutions,
updateManagedInstitution,
modalIdShown,
personalSubscription,
plans,
planCodeToChangeTo,
queryingIndividualPlansData,
recurlyLoadError,
setModalIdShown,
setPlanCodeToChangeTo,
setRecurlyLoadError,
showCancellation,
setShowCancellation,

View file

@ -0,0 +1,3 @@
export const subscriptionUrl = '/user/subscription/update'
export const cancelPendingSubscriptionChangeUrl =
'/user/subscription/cancel-pending'

View file

@ -1117,6 +1117,7 @@
"proceed_to_paypal": "Proceed to PayPal",
"proceeding_to_paypal_takes_you_to_the_paypal_site_to_pay": "Proceeding to PayPal will take you to the PayPal site to pay for your subscription.",
"processing": "processing",
"processing_uppercase": "Processing",
"processing_your_request": "Please wait while we process your request.",
"professional": "Professional",
"project": "project",

View file

@ -1,7 +1,6 @@
import { expect } from 'chai'
import { fireEvent, screen } from '@testing-library/react'
import * as eventTracking from '../../../../../../../../frontend/js/infrastructure/event-tracking'
import { ActiveSubscription } from '../../../../../../../../frontend/js/features/subscription/components/dashboard/states/active/active'
import { Subscription } from '../../../../../../../../types/subscription/dashboard/subscription'
import {
annualActiveSubscription,
@ -11,11 +10,8 @@ import {
trialSubscription,
} from '../../../../fixtures/subscriptions'
import sinon from 'sinon'
import { plans } from '../../../../fixtures/plans'
import {
cleanUpContext,
renderWithSubscriptionDashContext,
} from '../../../../helpers/render-with-subscription-dash-context'
import { cleanUpContext } from '../../../../helpers/render-with-subscription-dash-context'
import { renderActiveSubscription } from '../../../../helpers/render-active-subscription'
describe('<ActiveSubscription />', function () {
let sendMBSpy: sinon.SinonSpy
@ -61,19 +57,6 @@ describe('<ActiveSubscription />', function () {
screen.getByRole('link', { name: 'View Your Invoices' })
}
function renderActiveSubscription(subscription: Subscription) {
const renderOptions = {
metaTags: [
{ name: 'ol-plans', value: plans },
{ name: 'ol-subscription', value: subscription },
],
}
renderWithSubscriptionDashContext(
<ActiveSubscription subscription={subscription} />,
renderOptions
)
}
it('renders the dash annual active subscription', function () {
renderActiveSubscription(annualActiveSubscription)
expectedInActiveSubscription(annualActiveSubscription)

View file

@ -1,5 +1,5 @@
import { expect } from 'chai'
import { fireEvent, screen } from '@testing-library/react'
import { fireEvent, screen, waitFor, within } from '@testing-library/react'
import { ChangePlan } from '../../../../../../../../../frontend/js/features/subscription/components/dashboard/states/active/change-plan/change-plan'
import { plans } from '../../../../../fixtures/plans'
import {
@ -11,40 +11,45 @@ import {
cleanUpContext,
renderWithSubscriptionDashContext,
} from '../../../../../helpers/render-with-subscription-dash-context'
import sinon from 'sinon'
import fetchMock from 'fetch-mock'
import {
cancelPendingSubscriptionChangeUrl,
subscriptionUrl,
} from '../../../../../../../../../frontend/js/features/subscription/data/subscription-url'
import { renderActiveSubscription } from '../../../../../helpers/render-active-subscription'
describe('<ChangePlan />', function () {
let reloadStub: () => void
const originalLocation = window.location
const plansMetaTag = { name: 'ol-plans', value: plans }
const renderOptions = { metaTags: [plansMetaTag] }
beforeEach(function () {
reloadStub = sinon.stub()
Object.defineProperty(window, 'location', {
value: { reload: reloadStub },
})
})
afterEach(function () {
cleanUpContext()
fetchMock.reset()
Object.defineProperty(window, 'location', {
value: originalLocation,
})
})
it('does not render the UI when showChangePersonalPlan is false', function () {
window.metaAttributesCache.delete('ol-plans')
const { container } = renderWithSubscriptionDashContext(
<ChangePlan />,
renderOptions
)
const { container } = renderWithSubscriptionDashContext(<ChangePlan />, {
metaTags: [plansMetaTag],
})
expect(container.firstChild).to.be.null
})
it('renders the individual plans table and group plans UI', async function () {
renderWithSubscriptionDashContext(
<ActiveSubscription subscription={annualActiveSubscription} />,
{
metaTags: [
{ name: 'ol-subscription', value: annualActiveSubscription },
plansMetaTag,
{
name: 'ol-recommendedCurrency',
value: 'USD',
},
],
}
)
renderActiveSubscription(annualActiveSubscription)
const button = screen.getByRole('button', { name: 'Change plan' })
fireEvent.click(button)
@ -68,15 +73,7 @@ describe('<ChangePlan />', function () {
})
it('renders "Your new plan" and "Keep current plan" when there is a pending plan change', async function () {
renderWithSubscriptionDashContext(
<ActiveSubscription subscription={pendingSubscriptionChange} />,
{
metaTags: [
{ name: 'ol-subscription', value: pendingSubscriptionChange },
plansMetaTag,
],
}
)
renderActiveSubscription(pendingSubscriptionChange)
const button = screen.getByRole('button', { name: 'Change plan' })
fireEvent.click(button)
@ -98,13 +95,12 @@ describe('<ChangePlan />', function () {
expect(container).not.to.be.null
})
it('shows a loading message while still querying Recurly for prices', function () {
it('shows a loading message while still querying Recurly for prices', async function () {
renderWithSubscriptionDashContext(
<ActiveSubscription subscription={pendingSubscriptionChange} />,
{
metaTags: [
{ name: 'ol-subscription', value: pendingSubscriptionChange },
plansMetaTag,
],
}
)
@ -112,6 +108,218 @@ describe('<ChangePlan />', function () {
const button = screen.getByRole('button', { name: 'Change plan' })
fireEvent.click(button)
screen.findByText('Loading', { exact: false })
await screen.findByText('Loading', { exact: false })
})
describe('Change plan modal', function () {
it('open confirmation modal when "Change to this plan" clicked', async function () {
renderActiveSubscription(annualActiveSubscription)
const button = screen.getByRole('button', { name: 'Change plan' })
fireEvent.click(button)
const buttons = await screen.findAllByRole('button', {
name: 'Change to this plan',
})
fireEvent.click(buttons[0])
await screen.findByText('Are you sure you want to change plan to', {
exact: false,
})
screen.getByRole('button', { name: 'Change plan' })
expect(
screen.queryByText(
'Your existing plan and its features will remain active until the end of the current billing period.'
)
).to.be.null
expect(
screen.queryByText(
'If you wish this change to apply before the end of your current billing period, please contact us.'
)
).to.be.null
})
it('shows message in confirmation dialog about plan remaining active until end of term when expected', async function () {
let planIndex = 0
const planThatWillChange = plans.find((p, i) => {
if (p.planCode !== annualActiveSubscription.planCode) {
planIndex = i
}
return p.planCode !== annualActiveSubscription.planCode
})
renderActiveSubscription(annualActiveSubscription, [
{
name: 'ol-planCodesChangingAtTermEnd',
value: [planThatWillChange!.planCode],
},
])
const button = screen.getByRole('button', { name: 'Change plan' })
fireEvent.click(button)
const buttons = await screen.findAllByRole('button', {
name: 'Change to this plan',
})
fireEvent.click(buttons[planIndex])
const confirmModal = screen.getByRole('dialog')
await within(confirmModal).findByText(
'Your existing plan and its features will remain active until the end of the current billing period.'
)
screen.getByText(
'If you wish this change to apply before the end of your current billing period, please contact us.'
)
})
it('changes plan after confirmed in modal', async function () {
const endPointResponse = {
status: 200,
}
fetchMock.post(
`${subscriptionUrl}?origin=confirmChangePlan`,
endPointResponse
)
renderActiveSubscription(annualActiveSubscription, [
{
name: 'ol-planCodesChangingAtTermEnd',
value: [annualActiveSubscription.planCode],
},
])
const button = screen.getByRole('button', { name: 'Change plan' })
fireEvent.click(button)
const buttons = await screen.findAllByRole('button', {
name: 'Change to this plan',
})
fireEvent.click(buttons[0])
await screen.findByText('Are you sure you want to change plan to', {
exact: false,
})
const buttonConfirm = screen.getByRole('button', { name: 'Change plan' })
fireEvent.click(buttonConfirm)
screen.getByText('processing', { exact: false })
// page is reloaded on success
await waitFor(() => {
expect(reloadStub).to.have.been.called
})
})
it('shows error if changing plan failed', async function () {
const endPointResponse = {
status: 500,
}
fetchMock.post(
`${subscriptionUrl}?origin=confirmChangePlan`,
endPointResponse
)
renderActiveSubscription(annualActiveSubscription)
const button = screen.getByRole('button', { name: 'Change plan' })
fireEvent.click(button)
const buttons = await screen.findAllByRole('button', {
name: 'Change to this plan',
})
fireEvent.click(buttons[0])
await screen.findByText('Are you sure you want to change plan to', {
exact: false,
})
const buttonConfirm = screen.getByRole('button', { name: 'Change plan' })
fireEvent.click(buttonConfirm)
screen.getByText('processing', { exact: false })
await screen.findByText('Sorry, something went wrong. ', { exact: false })
await screen.findByText('Please try again. ', { exact: false })
await screen.findByText('If the problem continues please contact us.', {
exact: false,
})
expect(
within(screen.getByRole('dialog'))
.getByRole('button', { name: 'Change plan' })
.getAttribute('disabled')
).to.not.exist
})
})
describe('Keep current plan modal', function () {
let confirmModal: HTMLElement
beforeEach(async function () {
renderActiveSubscription(pendingSubscriptionChange)
const button = screen.getByRole('button', { name: 'Change plan' })
fireEvent.click(button)
const keepPlanButton = await screen.findByRole('button', {
name: 'Keep my current plan',
})
fireEvent.click(keepPlanButton)
confirmModal = screen.getByRole('dialog')
})
it('opens confirmation modal when "Keep my current plan" is clicked', async function () {
within(confirmModal).getByText(
'Are you sure you want to revert your scheduled plan change? You will remain subscribed to the',
{
exact: false,
}
)
screen.getByRole('button', { name: 'Revert scheduled plan change' })
})
it('keeps current plan when "Revert scheduled plan change" is clicked in modal', async function () {
const endPointResponse = {
status: 200,
}
fetchMock.post(cancelPendingSubscriptionChangeUrl, endPointResponse)
const buttonConfirm = within(confirmModal).getByRole('button', {
name: 'Revert scheduled plan change',
})
fireEvent.click(buttonConfirm)
screen.getByText('processing', { exact: false })
// page is reloaded on success
await waitFor(() => {
expect(reloadStub).to.have.been.called
})
})
it('shows error if keeping plan failed', async function () {
const endPointResponse = {
status: 500,
}
fetchMock.post(cancelPendingSubscriptionChangeUrl, endPointResponse)
const buttonConfirm = within(confirmModal).getByRole('button', {
name: 'Revert scheduled plan change',
})
fireEvent.click(buttonConfirm)
screen.getByText('processing', { exact: false })
await screen.findByText('Sorry, something went wrong. ', { exact: false })
await screen.findByText('Please try again. ', { exact: false })
await screen.findByText('If the problem continues please contact us.', {
exact: false,
})
expect(
within(screen.getByRole('dialog'))
.getByRole('button', { name: 'Revert scheduled plan change' })
.getAttribute('disabled')
).to.not.exist
})
})
})

View file

@ -0,0 +1,25 @@
import { ActiveSubscription } from '../../../../../frontend/js/features/subscription/components/dashboard/states/active/active'
import { Subscription } from '../../../../../types/subscription/dashboard/subscription'
import { plans } from '../fixtures/plans'
import { renderWithSubscriptionDashContext } from './render-with-subscription-dash-context'
export function renderActiveSubscription(
subscription: Subscription,
tags: { name: string; value: string | object | Array<object> }[] = []
) {
const renderOptions = {
metaTags: [
...tags,
{ name: 'ol-plans', value: plans },
{ name: 'ol-subscription', value: subscription },
{
name: 'ol-recommendedCurrency',
value: 'USD',
},
],
}
renderWithSubscriptionDashContext(
<ActiveSubscription subscription={subscription} />,
renderOptions
)
}