personal-subscription.test.tsx 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. import { expect } from 'chai'
  2. import {
  3. screen,
  4. fireEvent,
  5. waitForElementToBeRemoved,
  6. within,
  7. waitFor,
  8. } from '@testing-library/react'
  9. import PersonalSubscription from '../../../../../../frontend/js/features/subscription/components/dashboard/personal-subscription'
  10. import {
  11. annualActiveSubscription,
  12. canceledSubscription,
  13. customSubscription,
  14. pastDueExpiredSubscription,
  15. } from '../../fixtures/subscriptions'
  16. import {
  17. cleanUpContext,
  18. renderWithSubscriptionDashContext,
  19. } from '../../helpers/render-with-subscription-dash-context'
  20. import { reactivateSubscriptionUrl } from '../../../../../../frontend/js/features/subscription/data/subscription-url'
  21. import fetchMock from 'fetch-mock'
  22. import sinon from 'sinon'
  23. import { location } from '@/shared/components/location'
  24. describe('<PersonalSubscription />', function () {
  25. afterEach(function () {
  26. cleanUpContext()
  27. })
  28. describe('no subscription', function () {
  29. it('returns empty container', function () {
  30. const { container } = renderWithSubscriptionDashContext(
  31. <PersonalSubscription />
  32. )
  33. expect(container.firstChild).to.be.null
  34. })
  35. })
  36. describe('custom subscription', function () {
  37. it('displays contact Support message', function () {
  38. renderWithSubscriptionDashContext(<PersonalSubscription />, {
  39. metaTags: [{ name: 'ol-subscription', value: customSubscription }],
  40. })
  41. screen.getByText('Please', { exact: false })
  42. screen.getByText('contact Support', { exact: false })
  43. screen.getByText('to make changes to your plan', { exact: false })
  44. })
  45. })
  46. describe('subscription states ', function () {
  47. beforeEach(function () {
  48. this.locationWrapperSandbox = sinon.createSandbox()
  49. this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
  50. })
  51. afterEach(function () {
  52. this.locationWrapperSandbox.restore()
  53. })
  54. it('renders the active dash', function () {
  55. renderWithSubscriptionDashContext(<PersonalSubscription />, {
  56. metaTags: [
  57. { name: 'ol-subscription', value: annualActiveSubscription },
  58. ],
  59. })
  60. screen.getByRole('heading', { name: /billing/i })
  61. })
  62. it('renders the canceled dash', function () {
  63. renderWithSubscriptionDashContext(<PersonalSubscription />, {
  64. metaTags: [{ name: 'ol-subscription', value: canceledSubscription }],
  65. })
  66. screen.getByText(
  67. 'Your subscription has been canceled and will terminate on',
  68. { exact: false }
  69. )
  70. screen.getByText(canceledSubscription.payment!.nextPaymentDueAt, {
  71. exact: false,
  72. })
  73. screen.getByText('No further payments will be taken.', { exact: false })
  74. screen.getByRole('link', { name: 'View your invoices' })
  75. screen.getByRole('button', { name: 'Reactivate your subscription' })
  76. })
  77. it('reactivates canceled plan', async function () {
  78. renderWithSubscriptionDashContext(<PersonalSubscription />, {
  79. metaTags: [{ name: 'ol-subscription', value: canceledSubscription }],
  80. })
  81. const reactivateBtn = screen.getByRole<HTMLButtonElement>('button', {
  82. name: 'Reactivate your subscription',
  83. })
  84. // 1st click - fail
  85. fetchMock.postOnce(reactivateSubscriptionUrl, 400)
  86. fireEvent.click(reactivateBtn)
  87. expect(reactivateBtn.disabled).to.be.true
  88. await fetchMock.callHistory.flush(true)
  89. await waitFor(() => expect(reactivateBtn.disabled).to.be.false)
  90. expect(this.locationWrapperStub.reload).not.to.have.been.called
  91. fetchMock.removeRoutes().clearHistory()
  92. // 2nd click - success
  93. fetchMock.postOnce(reactivateSubscriptionUrl, 200)
  94. fireEvent.click(reactivateBtn)
  95. await fetchMock.callHistory.flush(true)
  96. await waitFor(() => {
  97. expect(this.locationWrapperStub.reload).to.have.been.calledOnce
  98. })
  99. expect(reactivateBtn.disabled).to.be.true
  100. fetchMock.removeRoutes().clearHistory()
  101. })
  102. it('hides reactivate button for managed users who cannot reactivate', function () {
  103. renderWithSubscriptionDashContext(<PersonalSubscription />, {
  104. metaTags: [
  105. { name: 'ol-subscription', value: canceledSubscription },
  106. { name: 'ol-cannot-reactivate-subscription', value: true },
  107. ],
  108. })
  109. screen.getByText(
  110. 'Your subscription has been canceled and will terminate on',
  111. { exact: false }
  112. )
  113. screen.getByRole('link', { name: 'View your invoices' })
  114. // Should not show the reactivate button
  115. expect(
  116. screen.queryByRole('button', { name: 'Reactivate your subscription' })
  117. ).to.be.null
  118. })
  119. it('shows reactivate button for managed group admins even if they have reactivate restriction', function () {
  120. renderWithSubscriptionDashContext(<PersonalSubscription />, {
  121. metaTags: [
  122. { name: 'ol-subscription', value: canceledSubscription },
  123. { name: 'ol-cannot-reactivate-subscription', value: true },
  124. { name: 'ol-isManagedGroupAdmin', value: true },
  125. ],
  126. })
  127. screen.getByText(
  128. 'Your subscription has been canceled and will terminate on',
  129. { exact: false }
  130. )
  131. screen.getByRole('link', { name: 'View your invoices' })
  132. // Should show the reactivate button for group admins
  133. screen.getByRole('button', { name: 'Reactivate your subscription' })
  134. })
  135. it('renders the expired dash', function () {
  136. renderWithSubscriptionDashContext(<PersonalSubscription />, {
  137. metaTags: [
  138. { name: 'ol-subscription', value: pastDueExpiredSubscription },
  139. ],
  140. })
  141. screen.getByText('Your subscription has expired.')
  142. })
  143. it('renders error message when an unknown subscription state', function () {
  144. const withStateDeleted = Object.assign(
  145. {},
  146. JSON.parse(JSON.stringify(annualActiveSubscription))
  147. )
  148. withStateDeleted.payment.state = undefined
  149. renderWithSubscriptionDashContext(<PersonalSubscription />, {
  150. metaTags: [{ name: 'ol-subscription', value: withStateDeleted }],
  151. })
  152. screen.getByText(
  153. 'There is a problem with your subscription. Please contact us for more information.'
  154. )
  155. })
  156. })
  157. describe('past due subscription', function () {
  158. it('renders error alert', function () {
  159. renderWithSubscriptionDashContext(<PersonalSubscription />, {
  160. metaTags: [
  161. { name: 'ol-subscription', value: pastDueExpiredSubscription },
  162. ],
  163. })
  164. screen.getByRole('alert')
  165. screen.getByText(
  166. 'Your account currently has a past due invoice. You will not be able to change your plan until this is resolved.',
  167. { exact: false }
  168. )
  169. const invoiceLinks = screen.getAllByText('View Your Invoices', {
  170. exact: false,
  171. })
  172. expect(invoiceLinks.length).to.equal(2)
  173. })
  174. })
  175. describe('Recurly JS', function () {
  176. const recurlyFailedToLoadText =
  177. 'Sorry, there was an error talking to our payment provider. Please try again in a few moments. If you are using any ad or script blocking extensions in your browser, you may need to temporarily disable them.'
  178. it('shows an alert and hides "Change plan" option when Recurly did not load', function () {
  179. renderWithSubscriptionDashContext(<PersonalSubscription />, {
  180. metaTags: [
  181. { name: 'ol-subscription', value: annualActiveSubscription },
  182. ],
  183. recurlyNotLoaded: true,
  184. })
  185. screen.getByRole('alert')
  186. screen.getByText(recurlyFailedToLoadText)
  187. expect(screen.queryByText('Change plan')).to.be.null
  188. })
  189. it('should not show an alert and should show "Change plan" option when Recurly did load', function () {
  190. renderWithSubscriptionDashContext(<PersonalSubscription />, {
  191. metaTags: [
  192. { name: 'ol-subscription', value: annualActiveSubscription },
  193. ],
  194. })
  195. expect(screen.queryByRole('alert')).to.be.null
  196. screen.getByText('Change plan')
  197. })
  198. })
  199. it('shows different payment email address section', async function () {
  200. fetchMock.post('/user/subscription/account/email', {
  201. status: 200,
  202. })
  203. const usersEmail = 'foo@example.com'
  204. renderWithSubscriptionDashContext(<PersonalSubscription />, {
  205. metaTags: [
  206. { name: 'ol-subscription', value: annualActiveSubscription },
  207. { name: 'ol-usersEmail', value: usersEmail },
  208. ],
  209. })
  210. const billingText = screen.getByText(
  211. /your billing email address is currently/i
  212. ).textContent
  213. expect(billingText).to.contain(
  214. `Your billing email address is currently ${annualActiveSubscription.payment.accountEmail}.` +
  215. ` If needed you can update your billing address to ${usersEmail}`
  216. )
  217. const submitBtn = screen.getByRole<HTMLButtonElement>('button', {
  218. name: /update/i,
  219. })
  220. expect(submitBtn.disabled).to.be.false
  221. fireEvent.click(submitBtn)
  222. expect(submitBtn.disabled).to.be.true
  223. expect(
  224. screen.getByRole<HTMLButtonElement>('button', { name: /updating/i })
  225. .disabled
  226. ).to.be.true
  227. await waitForElementToBeRemoved(() =>
  228. screen.getByText(/your billing email address is currently/i)
  229. )
  230. within(screen.getByRole('alert')).getByText(
  231. /your billing email address was successfully updated/i
  232. )
  233. expect(screen.queryByRole('button', { name: /update/i })).to.be.null
  234. expect(screen.queryByRole('button', { name: /updating/i })).to.be.null
  235. })
  236. })