notifications.spec.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. import {OrganizationFixture} from 'sentry-fixture/organization';
  2. import {RouteComponentPropsFixture} from 'sentry-fixture/routeComponentPropsFixture';
  3. import {SubscriptionFixture} from 'getsentry-test/fixtures/subscription';
  4. import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  5. import SubscriptionStore from 'getsentry/stores/subscriptionStore';
  6. import Notifications from 'getsentry/views/subscriptionPage/notifications';
  7. describe('Subscription > Notifications', function () {
  8. const organization = OrganizationFixture({
  9. slug: 'chum-bucket',
  10. });
  11. const subscription = SubscriptionFixture({organization});
  12. beforeEach(function () {
  13. MockApiClient.clearMockResponses();
  14. MockApiClient.addMockResponse({
  15. url: `/customers/${organization.slug}/spend-notifications/`,
  16. method: 'GET',
  17. body: {reservedPercent: [90], perProductOndemandPercent: [80, 50]},
  18. });
  19. MockApiClient.addMockResponse({
  20. url: `/organizations/${organization.slug}/promotions/trigger-check/`,
  21. method: 'POST',
  22. });
  23. MockApiClient.addMockResponse({
  24. url: `/organizations/${organization.slug}/members/`,
  25. method: 'GET',
  26. body: [],
  27. });
  28. MockApiClient.addMockResponse({
  29. url: `/organizations/org-slug/members/`,
  30. method: 'GET',
  31. body: [],
  32. });
  33. MockApiClient.addMockResponse({
  34. url: `/organizations/${organization.slug}/prompts-activity/`,
  35. body: {},
  36. });
  37. organization.access = ['org:billing'];
  38. subscription.planDetails.allowOnDemand = false;
  39. SubscriptionStore.set(organization.slug, subscription);
  40. });
  41. it('renders', async function () {
  42. render(
  43. <Notifications
  44. {...RouteComponentPropsFixture()}
  45. organization={organization}
  46. subscription={subscription}
  47. />
  48. );
  49. expect(
  50. await screen.findByText(
  51. "Configure the thresholds for your organization's spend notifications."
  52. )
  53. ).toBeInTheDocument();
  54. expect(screen.getByText('Subscription Consumption')).toBeInTheDocument();
  55. expect(screen.getByText('90%')).toBeInTheDocument();
  56. await userEvent.click(screen.getByRole('button', {name: '90%'}));
  57. // 90% is selected so it is not one of the options
  58. const expectedOptions = ['80%', '70%', '60%', '50%', '40%', '30%', '20%', '10%'];
  59. const actualOptions = screen.getAllByTestId('menu-list-item-label');
  60. expect(actualOptions).toHaveLength(expectedOptions.length);
  61. actualOptions.forEach((element, idx) => {
  62. expect(element).toHaveTextContent(expectedOptions[idx]!);
  63. });
  64. expect(screen.queryByText('On-Demand Consumption')).not.toBeInTheDocument();
  65. expect(screen.getByRole('button', {name: 'Reset'})).toBeDisabled();
  66. expect(screen.getByRole('button', {name: 'Save Changes'})).toBeDisabled();
  67. });
  68. it('renders an error for non-billing users', async function () {
  69. organization.access = [];
  70. render(
  71. <Notifications
  72. {...RouteComponentPropsFixture()}
  73. organization={organization}
  74. subscription={subscription}
  75. />
  76. );
  77. expect(await screen.findByTestId('permission-denied')).toBeInTheDocument();
  78. expect(
  79. screen.queryByText(
  80. "Configure the thresholds for your organization's spend notifications."
  81. )
  82. ).not.toBeInTheDocument();
  83. });
  84. it('renders On-Demand Consumption if on-demand is enabled', async function () {
  85. subscription.planDetails.allowOnDemand = true;
  86. SubscriptionStore.set(organization.slug, subscription);
  87. render(
  88. <Notifications
  89. {...RouteComponentPropsFixture()}
  90. organization={organization}
  91. subscription={subscription}
  92. />
  93. );
  94. expect(
  95. await screen.findByText(
  96. "Configure the thresholds for your organization's spend notifications."
  97. )
  98. ).toBeInTheDocument();
  99. expect(screen.getByText('Subscription Consumption')).toBeInTheDocument();
  100. expect(screen.getByText('90%')).toBeInTheDocument();
  101. expect(screen.getByText('On-Demand Consumption')).toBeInTheDocument();
  102. expect(screen.getByText('80%')).toBeInTheDocument();
  103. expect(screen.getByText('50%')).toBeInTheDocument();
  104. });
  105. it('enables delete button if there is more than two thresholds for a section', async function () {
  106. subscription.planDetails.allowOnDemand = true;
  107. SubscriptionStore.set(organization.slug, subscription);
  108. render(
  109. <Notifications
  110. {...RouteComponentPropsFixture()}
  111. organization={organization}
  112. subscription={subscription}
  113. />
  114. );
  115. expect(await screen.findByText('90%')).toBeInTheDocument();
  116. const deleteButtons = screen.getAllByRole('button', {
  117. name: 'Remove notification threshold',
  118. });
  119. expect(deleteButtons).toHaveLength(3);
  120. expect(deleteButtons[0]).toBeDisabled();
  121. expect(screen.getByText('80%')).toBeInTheDocument();
  122. expect(screen.getByText('50%')).toBeInTheDocument();
  123. expect(deleteButtons[1]).toBeEnabled();
  124. expect(deleteButtons[2]).toBeEnabled();
  125. });
  126. it('allows 9 thresholds per section max', async function () {
  127. render(
  128. <Notifications
  129. {...RouteComponentPropsFixture()}
  130. organization={organization}
  131. subscription={subscription}
  132. />
  133. );
  134. expect(await screen.findByText('90%')).toBeInTheDocument();
  135. const clickOptions = {skipHover: true, delay: null};
  136. for (const percentage of ['80%', '70%', '60%', '50%', '40%', '30%', '20%', '10%']) {
  137. await userEvent.click(
  138. screen.getByRole('button', {name: 'Add threshold'}),
  139. clickOptions
  140. );
  141. await userEvent.click(screen.getByRole('option', {name: percentage}), clickOptions);
  142. await userEvent.click(
  143. screen.getByRole('button', {name: 'Add notification threshold'}),
  144. clickOptions
  145. );
  146. expect(screen.getByText(percentage)).toBeInTheDocument();
  147. }
  148. expect(screen.queryByText('Add threshold')).not.toBeInTheDocument();
  149. });
  150. it('reverts to saved thresholds on reset', async function () {
  151. render(
  152. <Notifications
  153. {...RouteComponentPropsFixture()}
  154. organization={organization}
  155. subscription={subscription}
  156. />
  157. );
  158. expect(await screen.findByText('90%')).toBeInTheDocument();
  159. await userEvent.click(screen.getByRole('button', {name: 'Add threshold'}));
  160. await userEvent.click(screen.getByRole('option', {name: '70%'}));
  161. await userEvent.click(
  162. screen.getByRole('button', {name: 'Add notification threshold'})
  163. );
  164. expect(screen.getByText('70%')).toBeInTheDocument();
  165. await userEvent.click(screen.getByRole('button', {name: 'Add threshold'}));
  166. await userEvent.click(screen.getByRole('option', {name: '50%'}));
  167. await userEvent.click(
  168. screen.getByRole('button', {name: 'Add notification threshold'})
  169. );
  170. expect(screen.getByText('50%')).toBeInTheDocument();
  171. await userEvent.click(screen.getByRole('button', {name: 'Reset'}));
  172. expect(screen.getByText('90%')).toBeInTheDocument();
  173. expect(screen.queryByText('70%')).not.toBeInTheDocument();
  174. expect(screen.queryByText('50%')).not.toBeInTheDocument();
  175. });
  176. it('calls api with correct args', async () => {
  177. const postMock = MockApiClient.addMockResponse({
  178. url: `/customers/${organization.slug}/spend-notifications/`,
  179. method: 'POST',
  180. body: {reservedPercent: [90, 60], perProductOndemandPercent: [80, 50]},
  181. });
  182. render(
  183. <Notifications
  184. {...RouteComponentPropsFixture()}
  185. organization={organization}
  186. subscription={subscription}
  187. />
  188. );
  189. expect(await screen.findByText('90%')).toBeInTheDocument();
  190. await userEvent.click(screen.getByRole('button', {name: 'Add threshold'}));
  191. await userEvent.click(screen.getByRole('option', {name: '60%'}));
  192. await userEvent.click(
  193. screen.getByRole('button', {name: 'Add notification threshold'})
  194. );
  195. await userEvent.click(screen.getByRole('button', {name: 'Save Changes'}));
  196. expect(postMock).toHaveBeenCalledWith(
  197. `/customers/${organization.slug}/spend-notifications/`,
  198. expect.objectContaining({
  199. method: 'POST',
  200. data: {
  201. reservedPercent: [90, 60],
  202. perProductOndemandPercent: [80, 50],
  203. },
  204. })
  205. );
  206. });
  207. });