first commit

This commit is contained in:
2025-04-24 13:11:28 +08:00
commit ff9c54d5e4
5960 changed files with 834111 additions and 0 deletions

View File

@@ -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('Youre 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('Wed 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 = 'Ill 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,
})
})
})
})

View File

@@ -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,
})
})
})
})

View File

@@ -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,
})
})
})