diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json
index b68d0a8d1f..4abe726c1f 100644
--- a/services/web/frontend/extracted-translations.json
+++ b/services/web/frontend/extracted-translations.json
@@ -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": "",
diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/change-plan.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/change-plan.tsx
index 0316d4de42..adeb755ed8 100644
--- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/change-plan.tsx
+++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/change-plan.tsx
@@ -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() {
{t('change_plan')}
+
+
>
)
}
diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/confirm-change-plan-modal.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/confirm-change-plan-modal.tsx
new file mode 100644
index 0000000000..6818c15e4b
--- /dev/null
+++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/confirm-change-plan-modal.tsx
@@ -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 (
+
+
+ {t('change_plan')}
+
+
+
+ {error && (
+
+ {t('generic_something_went_wrong')}. {t('try_again')}.{' '}
+ {t('generic_if_problem_continues_contact_us')}.
+
+ )}
+
+ ,
+ ]}
+ />
+
+ {planWillChangeAtTermEnd && (
+ <>
+ {t('existing_plan_active_until_term_end')}
+ {t('want_change_to_apply_before_plan_end')}
+ >
+ )}
+
+
+
+
+
+
+
+ )
+}
diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/individual-plans-table.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/individual-plans-table.tsx
index dac9f40f6b..b7b53bd58c 100644
--- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/individual-plans-table.tsx
+++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/individual-plans-table.tsx
@@ -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 (
-
+
)
}
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 (
-
+
)
}
@@ -61,7 +57,7 @@ function ChangePlanButton({ plan }: { plan: Plan }) {
)
} else {
- return
+ return
}
}
diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/keep-current-plan-modal.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/keep-current-plan-modal.tsx
new file mode 100644
index 0000000000..46e30701b7
--- /dev/null
+++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/keep-current-plan-modal.tsx
@@ -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 (
+
+
+ {t('change_plan')}
+
+
+
+ {error && (
+
+ {t('generic_something_went_wrong')}. {t('try_again')}.{' '}
+ {t('generic_if_problem_continues_contact_us')}.
+
+ )}
+
+ ,
+ ]}
+ />
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/services/web/frontend/js/features/subscription/context/subscription-dashboard-context.tsx b/services/web/frontend/js/features/subscription/context/subscription-dashboard-context.tsx
index d562d6fff2..75c828cb54 100644
--- a/services/web/frontend/js/features/subscription/context/subscription-dashboard-context.tsx
+++ b/services/web/frontend/js/features/subscription/context/subscription-dashboard-context.tsx
@@ -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>
+ setPlanCodeToChangeTo: React.Dispatch<
+ React.SetStateAction
+ >
setRecurlyLoadError: React.Dispatch>
showCancellation: boolean
setShowCancellation: React.Dispatch>
@@ -44,12 +52,16 @@ export function SubscriptionDashboardProvider({
}: {
children: ReactNode
}) {
+ const [modalIdShown, setModalIdShown] = useState()
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(
() => ({
+ 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,
diff --git a/services/web/frontend/js/features/subscription/data/subscription-url.ts b/services/web/frontend/js/features/subscription/data/subscription-url.ts
new file mode 100644
index 0000000000..3fe149eda9
--- /dev/null
+++ b/services/web/frontend/js/features/subscription/data/subscription-url.ts
@@ -0,0 +1,3 @@
+export const subscriptionUrl = '/user/subscription/update'
+export const cancelPendingSubscriptionChangeUrl =
+ '/user/subscription/cancel-pending'
diff --git a/services/web/locales/en.json b/services/web/locales/en.json
index 1acef3f5e6..77226c01d4 100644
--- a/services/web/locales/en.json
+++ b/services/web/locales/en.json
@@ -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",
diff --git a/services/web/test/frontend/features/subscription/components/dashboard/states/active/active.test.tsx b/services/web/test/frontend/features/subscription/components/dashboard/states/active/active.test.tsx
index d46ff04f82..61044d9f66 100644
--- a/services/web/test/frontend/features/subscription/components/dashboard/states/active/active.test.tsx
+++ b/services/web/test/frontend/features/subscription/components/dashboard/states/active/active.test.tsx
@@ -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('', function () {
let sendMBSpy: sinon.SinonSpy
@@ -61,19 +57,6 @@ describe('', 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(
- ,
- renderOptions
- )
- }
-
it('renders the dash annual active subscription', function () {
renderActiveSubscription(annualActiveSubscription)
expectedInActiveSubscription(annualActiveSubscription)
diff --git a/services/web/test/frontend/features/subscription/components/dashboard/states/active/change-plan/change-plan.test.tsx b/services/web/test/frontend/features/subscription/components/dashboard/states/active/change-plan/change-plan.test.tsx
index 70190af80f..f0767df4e6 100644
--- a/services/web/test/frontend/features/subscription/components/dashboard/states/active/change-plan/change-plan.test.tsx
+++ b/services/web/test/frontend/features/subscription/components/dashboard/states/active/change-plan/change-plan.test.tsx
@@ -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('', 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(
- ,
- renderOptions
- )
+ const { container } = renderWithSubscriptionDashContext(, {
+ metaTags: [plansMetaTag],
+ })
expect(container.firstChild).to.be.null
})
it('renders the individual plans table and group plans UI', async function () {
- renderWithSubscriptionDashContext(
- ,
- {
- 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('', function () {
})
it('renders "Your new plan" and "Keep current plan" when there is a pending plan change', async function () {
- renderWithSubscriptionDashContext(
- ,
- {
- 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('', 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(
,
{
metaTags: [
{ name: 'ol-subscription', value: pendingSubscriptionChange },
- plansMetaTag,
],
}
)
@@ -112,6 +108,218 @@ describe('', 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
+ })
})
})
diff --git a/services/web/test/frontend/features/subscription/helpers/render-active-subscription.tsx b/services/web/test/frontend/features/subscription/helpers/render-active-subscription.tsx
new file mode 100644
index 0000000000..4a1d6f9de4
--- /dev/null
+++ b/services/web/test/frontend/features/subscription/helpers/render-active-subscription.tsx
@@ -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