first commit
This commit is contained in:
@@ -0,0 +1,533 @@
|
||||
import { expect } from 'chai'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import * as eventTracking from '@/infrastructure/event-tracking'
|
||||
import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
|
||||
import {
|
||||
annualActiveSubscription,
|
||||
groupActiveSubscription,
|
||||
groupActiveSubscriptionWithPendingLicenseChange,
|
||||
monthlyActiveCollaborator,
|
||||
pendingSubscriptionChange,
|
||||
trialCollaboratorSubscription,
|
||||
trialSubscription,
|
||||
} from '../../../../fixtures/subscriptions'
|
||||
import sinon from 'sinon'
|
||||
import { cleanUpContext } from '../../../../helpers/render-with-subscription-dash-context'
|
||||
import { renderActiveSubscription } from '../../../../helpers/render-active-subscription'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import {
|
||||
cancelSubscriptionUrl,
|
||||
extendTrialUrl,
|
||||
subscriptionUpdateUrl,
|
||||
} from '@/features/subscription/data/subscription-url'
|
||||
import * as useLocationModule from '../../../../../../../../frontend/js/shared/hooks/use-location'
|
||||
import { MetaTag } from '@/utils/meta'
|
||||
|
||||
describe('<ActiveSubscription />', function () {
|
||||
let sendMBSpy: sinon.SinonSpy
|
||||
|
||||
beforeEach(function () {
|
||||
sendMBSpy = sinon.spy(eventTracking, 'sendMB')
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
cleanUpContext()
|
||||
sendMBSpy.restore()
|
||||
})
|
||||
|
||||
function expectedInActiveSubscription(subscription: PaidSubscription) {
|
||||
// sentence broken up by bolding
|
||||
screen.getByText('You are currently subscribed to the', { exact: false })
|
||||
screen.getByText(subscription.plan.name, { exact: false })
|
||||
|
||||
screen.getByRole('button', { name: 'Change plan' })
|
||||
|
||||
// sentence broken up by bolding
|
||||
screen.getByText('The next payment of', { exact: false })
|
||||
screen.getByText(subscription.payment.displayPrice, {
|
||||
exact: false,
|
||||
})
|
||||
screen.getByText('will be collected on', { exact: false })
|
||||
const dates = screen.getAllByText(subscription.payment.nextPaymentDueAt, {
|
||||
exact: false,
|
||||
})
|
||||
expect(dates.length).to.equal(2)
|
||||
|
||||
screen.getByText(
|
||||
'* Prices may be subject to additional VAT, depending on your country.'
|
||||
)
|
||||
|
||||
screen.getByRole('link', { name: 'Update your billing details' })
|
||||
screen.getByRole('link', { name: 'View your invoices' })
|
||||
}
|
||||
|
||||
it('renders the dash annual active subscription', function () {
|
||||
renderActiveSubscription(annualActiveSubscription)
|
||||
expectedInActiveSubscription(annualActiveSubscription)
|
||||
})
|
||||
|
||||
it('shows change plan UI when button clicked', async function () {
|
||||
renderActiveSubscription(annualActiveSubscription)
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Change plan' })
|
||||
fireEvent.click(button)
|
||||
|
||||
// confirm main dash UI still shown
|
||||
screen.getByText('You are currently subscribed to the', { exact: false })
|
||||
|
||||
await screen.findByRole('heading', { name: 'Change plan' })
|
||||
expect(
|
||||
screen.getAllByRole('button', { name: 'Change to this plan' }).length > 0
|
||||
).to.be.true
|
||||
})
|
||||
|
||||
it('notes when user is changing plan at end of current plan term', function () {
|
||||
renderActiveSubscription(pendingSubscriptionChange)
|
||||
|
||||
expectedInActiveSubscription(pendingSubscriptionChange)
|
||||
|
||||
screen.getByText('Your plan is changing to', { exact: false })
|
||||
|
||||
screen.getByText(pendingSubscriptionChange.pendingPlan!.name)
|
||||
screen.getByText(' at the end of the current billing period', {
|
||||
exact: false,
|
||||
})
|
||||
|
||||
screen.getByText(
|
||||
'If you wish this change to apply before the end of your current billing period, please contact us.'
|
||||
)
|
||||
|
||||
expect(screen.queryByRole('link', { name: 'contact support' })).to.be.null
|
||||
expect(screen.queryByText('if you wish to change your group subscription.'))
|
||||
.to.be.null
|
||||
})
|
||||
|
||||
it('does not show "Change plan" option when past due', function () {
|
||||
// account is likely in expired state, but be sure to not show option if state is still active
|
||||
const activePastDueSubscription = Object.assign(
|
||||
{},
|
||||
JSON.parse(JSON.stringify(annualActiveSubscription))
|
||||
)
|
||||
|
||||
activePastDueSubscription.payment.hasPastDueInvoice = true
|
||||
|
||||
renderActiveSubscription(activePastDueSubscription)
|
||||
|
||||
const changePlan = screen.queryByRole('button', { name: 'Change plan' })
|
||||
expect(changePlan).to.be.null
|
||||
})
|
||||
|
||||
it('shows the pending license change message when plan change is pending', function () {
|
||||
renderActiveSubscription(groupActiveSubscriptionWithPendingLicenseChange)
|
||||
|
||||
screen.getByText('Your subscription is changing to include', {
|
||||
exact: false,
|
||||
})
|
||||
|
||||
screen.getByText(
|
||||
groupActiveSubscriptionWithPendingLicenseChange.payment
|
||||
.pendingAdditionalLicenses!
|
||||
)
|
||||
|
||||
screen.getByText('additional license(s) for a total of', { exact: false })
|
||||
|
||||
screen.getByText(
|
||||
groupActiveSubscriptionWithPendingLicenseChange.payment
|
||||
.pendingTotalLicenses!
|
||||
)
|
||||
|
||||
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 the pending license change message when plan change is not pending', function () {
|
||||
const subscription = Object.assign({}, groupActiveSubscription)
|
||||
subscription.payment.additionalLicenses = 4
|
||||
subscription.payment.totalLicenses =
|
||||
subscription.payment.totalLicenses +
|
||||
subscription.payment.additionalLicenses
|
||||
|
||||
renderActiveSubscription(subscription)
|
||||
|
||||
screen.getByText('Your subscription includes', {
|
||||
exact: false,
|
||||
})
|
||||
|
||||
screen.getByText(subscription.payment.additionalLicenses)
|
||||
|
||||
screen.getByText('additional license(s) for a total of', { exact: false })
|
||||
|
||||
screen.getByText(subscription.payment.totalLicenses)
|
||||
})
|
||||
|
||||
it('shows when trial ends and first payment collected and when subscription would become inactive if cancelled', function () {
|
||||
renderActiveSubscription(trialSubscription)
|
||||
screen.getByText('You’re on a free trial which ends on', { exact: false })
|
||||
|
||||
const endDate = screen.getAllByText(
|
||||
trialSubscription.payment.trialEndsAtFormatted!
|
||||
)
|
||||
expect(endDate.length).to.equal(3)
|
||||
})
|
||||
|
||||
it('shows current discounts', function () {
|
||||
const subscriptionWithActiveCoupons = cloneDeep(annualActiveSubscription)
|
||||
subscriptionWithActiveCoupons.payment.activeCoupons = [
|
||||
{
|
||||
name: 'fake coupon name',
|
||||
code: 'fake-coupon',
|
||||
description: '',
|
||||
},
|
||||
]
|
||||
renderActiveSubscription(subscriptionWithActiveCoupons)
|
||||
screen.getByText(
|
||||
/this does not include your current discounts, which will be applied automatically before your next payment/i
|
||||
)
|
||||
screen.getByText(
|
||||
subscriptionWithActiveCoupons.payment.activeCoupons[0].name
|
||||
)
|
||||
})
|
||||
|
||||
describe('cancel plan', function () {
|
||||
const assignStub = sinon.stub()
|
||||
const reloadStub = sinon.stub()
|
||||
|
||||
beforeEach(function () {
|
||||
this.locationStub = sinon.stub(useLocationModule, 'useLocation').returns({
|
||||
assign: assignStub,
|
||||
replace: sinon.stub(),
|
||||
reload: reloadStub,
|
||||
setHash: sinon.stub(),
|
||||
toString: sinon.stub(),
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
this.locationStub.restore()
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
function showConfirmCancelUI() {
|
||||
const button = screen.getByRole('button', {
|
||||
name: 'Cancel your subscription',
|
||||
})
|
||||
fireEvent.click(button)
|
||||
}
|
||||
|
||||
it('shows cancel UI', function () {
|
||||
renderActiveSubscription(annualActiveSubscription)
|
||||
screen.getByText(
|
||||
'Your subscription will remain active until the end of your billing period',
|
||||
{ exact: false }
|
||||
)
|
||||
const dates = screen.getAllByText(
|
||||
annualActiveSubscription.payment.nextPaymentDueAt,
|
||||
{
|
||||
exact: false,
|
||||
}
|
||||
)
|
||||
expect(dates.length).to.equal(2)
|
||||
const button = screen.getByRole('button', {
|
||||
name: 'Cancel your subscription',
|
||||
})
|
||||
expect(button).to.exist
|
||||
})
|
||||
|
||||
it('shows cancel UI when still in a trial period', function () {
|
||||
renderActiveSubscription(trialSubscription)
|
||||
screen.getByText(
|
||||
'Your subscription will remain active until the end of your trial period',
|
||||
{ exact: false }
|
||||
)
|
||||
const dates = screen.getAllByText(
|
||||
trialSubscription.payment.trialEndsAtFormatted!
|
||||
)
|
||||
expect(dates.length).to.equal(3)
|
||||
const button = screen.getByRole('button', {
|
||||
name: 'Cancel your subscription',
|
||||
})
|
||||
expect(button).to.exist
|
||||
})
|
||||
|
||||
it('shows cancel prompt on button click and sends event', function () {
|
||||
renderActiveSubscription(annualActiveSubscription)
|
||||
|
||||
showConfirmCancelUI()
|
||||
|
||||
expect(sendMBSpy).to.be.calledOnceWith(
|
||||
'subscription-page-cancel-button-click'
|
||||
)
|
||||
|
||||
screen.getByText('We’d love you to stay')
|
||||
screen.getByRole('button', { name: 'Cancel my subscription' })
|
||||
})
|
||||
|
||||
it('cancels subscription and redirects page', async function () {
|
||||
const endPointResponse = {
|
||||
status: 200,
|
||||
}
|
||||
fetchMock.post(cancelSubscriptionUrl, endPointResponse)
|
||||
renderActiveSubscription(annualActiveSubscription)
|
||||
showConfirmCancelUI()
|
||||
const button = screen.getByRole('button', {
|
||||
name: 'Cancel my subscription',
|
||||
})
|
||||
fireEvent.click(button)
|
||||
await waitFor(() => {
|
||||
expect(assignStub).to.have.been.called
|
||||
})
|
||||
sinon.assert.calledWithMatch(assignStub, '/user/subscription/canceled')
|
||||
})
|
||||
|
||||
it('shows an error message if canceling subscription failed', async function () {
|
||||
const endPointResponse = {
|
||||
status: 500,
|
||||
}
|
||||
fetchMock.post(cancelSubscriptionUrl, endPointResponse)
|
||||
renderActiveSubscription(annualActiveSubscription)
|
||||
showConfirmCancelUI()
|
||||
const button = screen.getByRole('button', {
|
||||
name: 'Cancel my subscription',
|
||||
})
|
||||
fireEvent.click(button)
|
||||
await screen.findByText('Sorry, something went wrong. ', {
|
||||
exact: false,
|
||||
})
|
||||
screen.getByText('Please try again. ', { exact: false })
|
||||
screen.getByText('If the problem continues please contact us.', {
|
||||
exact: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('disables cancels subscription button after clicking and shows loading spinner', async function () {
|
||||
renderActiveSubscription(annualActiveSubscription)
|
||||
showConfirmCancelUI()
|
||||
screen.getByRole('button', {
|
||||
name: 'I want to stay',
|
||||
})
|
||||
const button = screen.getByRole('button', {
|
||||
name: 'Cancel my subscription',
|
||||
})
|
||||
fireEvent.click(button)
|
||||
|
||||
const cancelButton = screen.getByRole('button', {
|
||||
name: 'Processing…',
|
||||
}) as HTMLButtonElement
|
||||
expect(cancelButton.disabled).to.be.true
|
||||
|
||||
const hiddenText = screen.getByText('Cancel my subscription')
|
||||
expect(hiddenText.getAttribute('aria-hidden')).to.equal('true')
|
||||
})
|
||||
|
||||
describe('extend trial', function () {
|
||||
const canExtend: MetaTag = {
|
||||
name: 'ol-userCanExtendTrial',
|
||||
value: true,
|
||||
}
|
||||
const cancelButtonText = 'No thanks, I still want to cancel'
|
||||
const extendTrialButtonText = 'I’ll take it!'
|
||||
it('shows alternate cancel subscription button text for cancel button and option to extend trial', function () {
|
||||
renderActiveSubscription(trialCollaboratorSubscription, [canExtend])
|
||||
showConfirmCancelUI()
|
||||
screen.getByText('Have another', { exact: false })
|
||||
screen.getByText('14 days', { exact: false })
|
||||
screen.getByText('on your Trial!', { exact: false })
|
||||
screen.getByRole('button', {
|
||||
name: cancelButtonText,
|
||||
})
|
||||
screen.getByRole('button', {
|
||||
name: extendTrialButtonText,
|
||||
})
|
||||
})
|
||||
|
||||
it('disables both buttons and updates text for when trial button clicked', function () {
|
||||
renderActiveSubscription(trialCollaboratorSubscription, [canExtend])
|
||||
showConfirmCancelUI()
|
||||
const extendTrialButton = screen.getByRole('button', {
|
||||
name: extendTrialButtonText,
|
||||
})
|
||||
fireEvent.click(extendTrialButton)
|
||||
|
||||
const buttons = screen.getAllByRole('button')
|
||||
expect(buttons.length).to.equal(2)
|
||||
expect(buttons[0].getAttribute('disabled')).to.equal('')
|
||||
expect(buttons[1].getAttribute('disabled')).to.equal('')
|
||||
screen.getByRole('button', {
|
||||
name: cancelButtonText,
|
||||
})
|
||||
screen.getByRole('button', {
|
||||
name: 'Processing…',
|
||||
})
|
||||
})
|
||||
|
||||
it('disables both buttons and updates text for when cancel button clicked', function () {
|
||||
renderActiveSubscription(trialCollaboratorSubscription, [canExtend])
|
||||
showConfirmCancelUI()
|
||||
const cancelButtton = screen.getByRole('button', {
|
||||
name: cancelButtonText,
|
||||
})
|
||||
fireEvent.click(cancelButtton)
|
||||
|
||||
const buttons = screen.getAllByRole('button')
|
||||
expect(buttons.length).to.equal(2)
|
||||
expect(buttons[0].getAttribute('disabled')).to.equal('')
|
||||
expect(buttons[1].getAttribute('disabled')).to.equal('')
|
||||
screen.getByRole('button', {
|
||||
name: 'Processing…',
|
||||
})
|
||||
screen.getByRole('button', {
|
||||
name: extendTrialButtonText,
|
||||
})
|
||||
})
|
||||
|
||||
it('does not show option to extend trial when user is not eligible', function () {
|
||||
renderActiveSubscription(trialCollaboratorSubscription)
|
||||
showConfirmCancelUI()
|
||||
expect(
|
||||
screen.queryByRole('button', {
|
||||
name: extendTrialButtonText,
|
||||
})
|
||||
).to.be.null
|
||||
})
|
||||
|
||||
it('reloads page after the successful request to extend trial', async function () {
|
||||
const endPointResponse = {
|
||||
status: 200,
|
||||
}
|
||||
fetchMock.put(extendTrialUrl, endPointResponse)
|
||||
renderActiveSubscription(trialCollaboratorSubscription, [canExtend])
|
||||
showConfirmCancelUI()
|
||||
const extendTrialButton = screen.getByRole('button', {
|
||||
name: extendTrialButtonText,
|
||||
})
|
||||
fireEvent.click(extendTrialButton)
|
||||
// page is reloaded on success
|
||||
await waitFor(() => {
|
||||
expect(reloadStub).to.have.been.called
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('downgrade plan', function () {
|
||||
const cancelButtonText = 'No thanks, I still want to cancel'
|
||||
const downgradeButtonText = 'Yes, move me to the Personal plan'
|
||||
it('shows alternate cancel subscription button text', async function () {
|
||||
renderActiveSubscription(monthlyActiveCollaborator)
|
||||
showConfirmCancelUI()
|
||||
await screen.findByRole('button', {
|
||||
name: cancelButtonText,
|
||||
})
|
||||
screen.getByRole('button', {
|
||||
name: downgradeButtonText,
|
||||
})
|
||||
screen.getByText('Would you be interested in the cheaper', {
|
||||
exact: false,
|
||||
})
|
||||
screen.getByText('Personal plan?', {
|
||||
exact: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('disables both buttons and updates text for when trial button clicked', async function () {
|
||||
renderActiveSubscription(monthlyActiveCollaborator)
|
||||
showConfirmCancelUI()
|
||||
const downgradeButton = await screen.findByRole('button', {
|
||||
name: downgradeButtonText,
|
||||
})
|
||||
fireEvent.click(downgradeButton)
|
||||
|
||||
const buttons = screen.getAllByRole('button')
|
||||
expect(buttons.length).to.equal(2)
|
||||
expect(buttons[0].getAttribute('disabled')).to.equal('')
|
||||
expect(buttons[1].getAttribute('disabled')).to.equal('')
|
||||
screen.getByRole('button', {
|
||||
name: cancelButtonText,
|
||||
})
|
||||
screen.getByRole('button', {
|
||||
name: 'Processing…',
|
||||
})
|
||||
})
|
||||
|
||||
it('disables both buttons and updates text for when cancel button clicked', async function () {
|
||||
renderActiveSubscription(monthlyActiveCollaborator)
|
||||
showConfirmCancelUI()
|
||||
const cancelButtton = await screen.findByRole('button', {
|
||||
name: cancelButtonText,
|
||||
})
|
||||
fireEvent.click(cancelButtton)
|
||||
|
||||
const buttons = screen.getAllByRole('button')
|
||||
expect(buttons.length).to.equal(2)
|
||||
expect(buttons[0].getAttribute('disabled')).to.equal('')
|
||||
expect(buttons[1].getAttribute('disabled')).to.equal('')
|
||||
screen.getByRole('button', {
|
||||
name: 'Processing…',
|
||||
})
|
||||
screen.getByRole('button', {
|
||||
name: downgradeButtonText,
|
||||
})
|
||||
})
|
||||
|
||||
it('does not show option to downgrade when not a collaborator plan', function () {
|
||||
const trialPlan = cloneDeep(monthlyActiveCollaborator)
|
||||
trialPlan.plan.planCode = 'anotherplan'
|
||||
renderActiveSubscription(trialPlan)
|
||||
showConfirmCancelUI()
|
||||
expect(
|
||||
screen.queryByRole('button', {
|
||||
name: downgradeButtonText,
|
||||
})
|
||||
).to.be.null
|
||||
})
|
||||
|
||||
it('does not show option to extend trial when on a collaborator trial', function () {
|
||||
const trialPlan = cloneDeep(trialCollaboratorSubscription)
|
||||
renderActiveSubscription(trialPlan)
|
||||
showConfirmCancelUI()
|
||||
expect(
|
||||
screen.queryByRole('button', {
|
||||
name: downgradeButtonText,
|
||||
})
|
||||
).to.be.null
|
||||
})
|
||||
|
||||
it('reloads page after the successful request to downgrade plan', async function () {
|
||||
const endPointResponse = {
|
||||
status: 200,
|
||||
}
|
||||
fetchMock.post(subscriptionUpdateUrl, endPointResponse)
|
||||
renderActiveSubscription(monthlyActiveCollaborator)
|
||||
showConfirmCancelUI()
|
||||
const downgradeButton = await screen.findByRole('button', {
|
||||
name: downgradeButtonText,
|
||||
})
|
||||
fireEvent.click(downgradeButton)
|
||||
// page is reloaded on success
|
||||
await waitFor(() => {
|
||||
expect(reloadStub).to.have.been.called
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('group plans', function () {
|
||||
it('does not show "Change plan" option for group plans', function () {
|
||||
renderActiveSubscription(groupActiveSubscription)
|
||||
|
||||
const changePlan = screen.queryByRole('button', { name: 'Change plan' })
|
||||
expect(changePlan).to.be.null
|
||||
})
|
||||
|
||||
it('shows contact support message for group plan change requests', function () {
|
||||
renderActiveSubscription(groupActiveSubscription)
|
||||
screen.getByRole('link', { name: 'contact support' })
|
||||
screen.getByText('if you wish to change your group subscription.', {
|
||||
exact: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
@@ -0,0 +1,561 @@
|
||||
import { expect } from 'chai'
|
||||
import { fireEvent, screen, waitFor, within } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { groupPlans, plans } from '../../../../../fixtures/plans'
|
||||
import {
|
||||
annualActiveSubscription,
|
||||
annualActiveSubscriptionEuro,
|
||||
annualActiveSubscriptionPro,
|
||||
pendingSubscriptionChange,
|
||||
} from '../../../../../fixtures/subscriptions'
|
||||
import { ActiveSubscription } from '../../../../../../../../../frontend/js/features/subscription/components/dashboard/states/active/active'
|
||||
import {
|
||||
cleanUpContext,
|
||||
renderWithSubscriptionDashContext,
|
||||
} from '../../../../../helpers/render-with-subscription-dash-context'
|
||||
import sinon from 'sinon'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import {
|
||||
cancelPendingSubscriptionChangeUrl,
|
||||
subscriptionUpdateUrl,
|
||||
} from '../../../../../../../../../frontend/js/features/subscription/data/subscription-url'
|
||||
import { renderActiveSubscription } from '../../../../../helpers/render-active-subscription'
|
||||
import * as useLocationModule from '../../../../../../../../../frontend/js/shared/hooks/use-location'
|
||||
|
||||
describe('<ChangePlanModal />', function () {
|
||||
let reloadStub: sinon.SinonStub
|
||||
|
||||
beforeEach(function () {
|
||||
reloadStub = sinon.stub()
|
||||
this.locationStub = sinon.stub(useLocationModule, 'useLocation').returns({
|
||||
assign: sinon.stub(),
|
||||
replace: sinon.stub(),
|
||||
reload: reloadStub,
|
||||
setHash: sinon.stub(),
|
||||
toString: sinon.stub(),
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
cleanUpContext()
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
this.locationStub.restore()
|
||||
})
|
||||
|
||||
it('renders the individual plans table and group plans UI', async function () {
|
||||
renderActiveSubscription(annualActiveSubscription)
|
||||
const button = screen.getByRole('button', { name: 'Change plan' })
|
||||
fireEvent.click(button)
|
||||
|
||||
await screen.findByText('Looking for multiple licenses?')
|
||||
|
||||
const changeToPlanButtons = screen.queryAllByRole('button', {
|
||||
name: 'Change to this plan',
|
||||
})
|
||||
expect(changeToPlanButtons.length).to.equal(plans.length - 3) // excludes paid-personal and paid-personal-annual
|
||||
screen.getByText('Your plan')
|
||||
|
||||
const annualPlans = plans.filter(plan => plan.annual)
|
||||
expect(screen.getAllByText('/ year', { exact: false }).length).to.equal(
|
||||
annualPlans.length - 1
|
||||
) // excludes paid-personal-annual
|
||||
|
||||
expect(screen.getAllByText('/ month', { exact: false }).length).to.equal(
|
||||
plans.length - annualPlans.length - 1
|
||||
) // excludes paid-personal
|
||||
|
||||
expect(screen.queryByText('loading', { exact: false })).to.be.null
|
||||
})
|
||||
|
||||
it('renders "Your new plan" and "Keep current plan" when there is a pending plan change', async function () {
|
||||
renderActiveSubscription(pendingSubscriptionChange)
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Change plan' })
|
||||
fireEvent.click(button)
|
||||
|
||||
await screen.findByText('Your new plan')
|
||||
screen.getByRole('button', { name: 'Keep my current plan' })
|
||||
})
|
||||
|
||||
it('does not render when Recurly did not load', function () {
|
||||
const { container } = renderWithSubscriptionDashContext(
|
||||
<ActiveSubscription subscription={annualActiveSubscription} />,
|
||||
{
|
||||
metaTags: [
|
||||
{ name: 'ol-subscription', value: annualActiveSubscription },
|
||||
{ name: 'ol-plans', value: plans },
|
||||
],
|
||||
}
|
||||
)
|
||||
expect(container).not.to.be.null
|
||||
})
|
||||
|
||||
it('shows a loading message while still querying Recurly for prices', async function () {
|
||||
renderWithSubscriptionDashContext(
|
||||
<ActiveSubscription subscription={pendingSubscriptionChange} />,
|
||||
{
|
||||
metaTags: [
|
||||
{ name: 'ol-subscription', value: pendingSubscriptionChange },
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Change plan' })
|
||||
fireEvent.click(button)
|
||||
|
||||
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])
|
||||
|
||||
const confirmModal = screen.getByRole('dialog')
|
||||
await within(confirmModal).findByText(
|
||||
'Are you sure you want to change plan to',
|
||||
{
|
||||
exact: false,
|
||||
}
|
||||
)
|
||||
within(confirmModal).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(
|
||||
`${subscriptionUpdateUrl}?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 = within(screen.getByRole('dialog')).getByRole(
|
||||
'button',
|
||||
{ name: 'Change plan' }
|
||||
)
|
||||
fireEvent.click(buttonConfirm)
|
||||
|
||||
screen.getByRole('button', { name: 'Processing…' })
|
||||
|
||||
// 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(
|
||||
`${subscriptionUpdateUrl}?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 = within(screen.getByRole('dialog')).getByRole(
|
||||
'button',
|
||||
{ name: 'Change plan' }
|
||||
)
|
||||
fireEvent.click(buttonConfirm)
|
||||
|
||||
screen.getByRole('button', { name: 'Processing…' })
|
||||
|
||||
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.getByRole('button', { name: 'Processing…' })
|
||||
|
||||
// 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.getByRole('button', { name: 'Processing…' })
|
||||
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
|
||||
})
|
||||
})
|
||||
|
||||
describe('Change to group plan modal', function () {
|
||||
const standardPlanCollaboratorText = '10 collaborators per project'
|
||||
const professionalPlanCollaboratorText = 'Unlimited collaborators'
|
||||
const educationInputLabel =
|
||||
'Get a total of 40% off for groups using Overleaf for teaching'
|
||||
|
||||
let modal: HTMLElement
|
||||
async function openModal() {
|
||||
const button = screen.getByRole('button', { name: 'Change plan' })
|
||||
fireEvent.click(button)
|
||||
|
||||
const buttonGroupModal = await screen.findByRole('button', {
|
||||
name: 'Change to a group plan',
|
||||
})
|
||||
fireEvent.click(buttonGroupModal)
|
||||
|
||||
modal = await screen.findByRole('dialog')
|
||||
}
|
||||
|
||||
it('open group plan modal "Change to a group plan" clicked', async function () {
|
||||
renderActiveSubscription(annualActiveSubscription)
|
||||
await openModal()
|
||||
|
||||
within(modal).getByText('Customize your group subscription')
|
||||
|
||||
within(modal).getByText('$1,290 per year')
|
||||
expect(within(modal).getAllByText('$129 per user').length).to.equal(2)
|
||||
|
||||
within(modal).getByText('Each user will have access to:')
|
||||
within(modal).getByText('All premium features')
|
||||
within(modal).getByText('Sync with Dropbox and GitHub')
|
||||
within(modal).getByText('Full document history')
|
||||
within(modal).getByText('plus more')
|
||||
|
||||
within(modal).getByText(standardPlanCollaboratorText)
|
||||
expect(within(modal).queryByText(professionalPlanCollaboratorText)).to.be
|
||||
.null
|
||||
|
||||
const plans = within(modal).getByRole('group')
|
||||
const planOptions = within(plans).getAllByRole('radio')
|
||||
expect(planOptions.length).to.equal(groupPlans.plans.length)
|
||||
const standardPlanRadioInput = within(modal).getByLabelText(
|
||||
'Standard'
|
||||
) as HTMLInputElement
|
||||
expect(standardPlanRadioInput.checked).to.be.true
|
||||
|
||||
const sizeSelect = within(modal).getByRole('combobox') as HTMLInputElement
|
||||
expect(sizeSelect.value).to.equal('10')
|
||||
const sizeOption = within(sizeSelect).getAllByRole('option')
|
||||
expect(sizeOption.length).to.equal(groupPlans.sizes.length)
|
||||
within(modal).getByText(
|
||||
'Get a total of 40% off for groups using Overleaf for teaching'
|
||||
)
|
||||
|
||||
const educationalCheckbox = within(modal).getByRole(
|
||||
'checkbox'
|
||||
) as HTMLInputElement
|
||||
expect(educationalCheckbox.checked).to.be.false
|
||||
|
||||
within(modal).getByText(
|
||||
'Your new subscription will be billed immediately to your current payment method.'
|
||||
)
|
||||
|
||||
expect(within(modal).queryByText('tax', { exact: false })).to.be.null
|
||||
|
||||
within(modal).getByRole('button', { name: 'Upgrade now' })
|
||||
|
||||
within(modal).getByRole('button', {
|
||||
name: 'Need more than 20 licenses? Please get in touch',
|
||||
})
|
||||
})
|
||||
|
||||
it('changes the collaborator count when the plan changes', async function () {
|
||||
renderActiveSubscription(annualActiveSubscription)
|
||||
await openModal()
|
||||
|
||||
const professionalPlanOption =
|
||||
within(modal).getByLabelText('Professional')
|
||||
fireEvent.click(professionalPlanOption)
|
||||
|
||||
await within(modal).findByText(professionalPlanCollaboratorText)
|
||||
expect(within(modal).queryByText(standardPlanCollaboratorText)).to.be.null
|
||||
})
|
||||
|
||||
it('shows educational discount applied when input checked', async function () {
|
||||
const discountAppliedText = '40% educational discount applied!'
|
||||
renderActiveSubscription(annualActiveSubscription)
|
||||
|
||||
await openModal()
|
||||
|
||||
const educationInput = within(modal).getByLabelText(educationInputLabel)
|
||||
fireEvent.click(educationInput)
|
||||
await within(modal).findByText(discountAppliedText)
|
||||
|
||||
const sizeSelect = within(modal).getByRole('combobox') as HTMLInputElement
|
||||
await userEvent.selectOptions(sizeSelect, [screen.getByText('5')])
|
||||
await within(modal).findByText(discountAppliedText)
|
||||
})
|
||||
|
||||
it('shows total with tax when tax applied', async function () {
|
||||
renderActiveSubscription(annualActiveSubscriptionEuro, undefined, 'EUR')
|
||||
|
||||
await openModal()
|
||||
|
||||
within(modal).getByText('Total:', { exact: false })
|
||||
expect(
|
||||
within(modal).getAllByText('€1,438.40', { exact: false }).length
|
||||
).to.equal(3)
|
||||
within(modal).getByText('(€1,160.00 + €278.40 tax) per year', {
|
||||
exact: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('changes the price when options change', async function () {
|
||||
renderActiveSubscription(annualActiveSubscription)
|
||||
|
||||
await openModal()
|
||||
|
||||
within(modal).getByText('$1,290 per year')
|
||||
within(modal).getAllByText('$129 per user')
|
||||
|
||||
// plan type (pro collab)
|
||||
let standardPlanRadioInput = within(modal).getByLabelText(
|
||||
'Standard'
|
||||
) as HTMLInputElement
|
||||
expect(standardPlanRadioInput.checked).to.be.true
|
||||
let professionalPlanRadioInput = within(modal).getByLabelText(
|
||||
'Professional'
|
||||
) as HTMLInputElement
|
||||
expect(professionalPlanRadioInput.checked).to.be.false
|
||||
|
||||
fireEvent.click(professionalPlanRadioInput)
|
||||
|
||||
standardPlanRadioInput = within(modal).getByLabelText(
|
||||
'Standard'
|
||||
) as HTMLInputElement
|
||||
expect(standardPlanRadioInput.checked).to.be.false
|
||||
professionalPlanRadioInput = within(modal).getByLabelText(
|
||||
'Professional'
|
||||
) as HTMLInputElement
|
||||
expect(professionalPlanRadioInput.checked).to.be.true
|
||||
|
||||
await within(modal).findByText('$2,590 per year')
|
||||
await within(modal).findAllByText('$259 per user')
|
||||
|
||||
// user count
|
||||
let sizeSelect = within(modal).getByRole('combobox') as HTMLInputElement
|
||||
expect(sizeSelect.value).to.equal('10')
|
||||
await userEvent.selectOptions(sizeSelect, [screen.getByText('5')])
|
||||
sizeSelect = within(modal).getByRole('combobox') as HTMLInputElement
|
||||
expect(sizeSelect.value).to.equal('5')
|
||||
|
||||
await within(modal).findByText('$1,395 per year')
|
||||
await within(modal).findAllByText('$279 per user')
|
||||
|
||||
// usage (enterprise or educational)
|
||||
let educationInput = within(modal).getByLabelText(
|
||||
educationInputLabel
|
||||
) as HTMLInputElement
|
||||
expect(educationInput.checked).to.be.false
|
||||
fireEvent.click(educationInput)
|
||||
educationInput = within(modal).getByLabelText(
|
||||
educationInputLabel
|
||||
) as HTMLInputElement
|
||||
expect(educationInput.checked).to.be.true
|
||||
|
||||
// make sure doesn't change price until back at min user to qualify
|
||||
await within(modal).findByText('$1,395 per year')
|
||||
await within(modal).findAllByText('$279 per user')
|
||||
|
||||
await userEvent.selectOptions(sizeSelect, [screen.getByText('10')])
|
||||
|
||||
await within(modal).findByText('$1,550 per year')
|
||||
await within(modal).findAllByText('$155 per user')
|
||||
})
|
||||
|
||||
it('has pro as the default group plan type if user is on a pro plan', async function () {
|
||||
renderActiveSubscription(annualActiveSubscriptionPro)
|
||||
|
||||
await openModal()
|
||||
|
||||
const standardPlanRadioInput = within(modal).getByLabelText(
|
||||
'Professional'
|
||||
) as HTMLInputElement
|
||||
expect(standardPlanRadioInput.checked).to.be.true
|
||||
})
|
||||
|
||||
it('submits the changes and reloads the page', async function () {
|
||||
const endPointResponse = {
|
||||
status: 200,
|
||||
}
|
||||
fetchMock.post(subscriptionUpdateUrl, endPointResponse)
|
||||
|
||||
renderActiveSubscription(annualActiveSubscriptionPro)
|
||||
|
||||
await openModal()
|
||||
|
||||
const buttonConfirm = screen.getByRole('button', { name: 'Upgrade now' })
|
||||
fireEvent.click(buttonConfirm)
|
||||
|
||||
screen.getByRole('button', { name: 'Processing…' })
|
||||
|
||||
// // page is reloaded on success
|
||||
await waitFor(() => {
|
||||
expect(reloadStub).to.have.been.called
|
||||
})
|
||||
})
|
||||
|
||||
it('shows message if error after submitting form', async function () {
|
||||
const endPointResponse = {
|
||||
status: 500,
|
||||
}
|
||||
fetchMock.post(subscriptionUpdateUrl, endPointResponse)
|
||||
|
||||
renderActiveSubscription(annualActiveSubscriptionPro)
|
||||
|
||||
await openModal()
|
||||
|
||||
const buttonConfirm = screen.getByRole('button', { name: 'Upgrade now' })
|
||||
fireEvent.click(buttonConfirm)
|
||||
|
||||
screen.getByRole('button', { name: 'Processing…' })
|
||||
|
||||
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,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
@@ -0,0 +1,13 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { ExpiredSubscription } from '../../../../../../../frontend/js/features/subscription/components/dashboard/states/expired'
|
||||
import { pastDueExpiredSubscription } from '../../../fixtures/subscriptions'
|
||||
|
||||
describe('<ExpiredSubscription />', function () {
|
||||
it('renders the invoices link', function () {
|
||||
render(<ExpiredSubscription subscription={pastDueExpiredSubscription} />)
|
||||
|
||||
screen.getByText('View Your Invoices', {
|
||||
exact: false,
|
||||
})
|
||||
})
|
||||
})
|
Reference in New Issue
Block a user