index.spec.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import {browserHistory} from 'react-router';
  2. import {Organization} from 'sentry-fixture/organization';
  3. import {initializeOrg} from 'sentry-test/initializeOrg';
  4. import {
  5. act,
  6. render,
  7. renderGlobalModal,
  8. screen,
  9. userEvent,
  10. waitFor,
  11. within,
  12. } from 'sentry-test/reactTestingLibrary';
  13. import OrganizationsStore from 'sentry/stores/organizationsStore';
  14. import ProjectsStore from 'sentry/stores/projectsStore';
  15. import {trackAnalytics} from 'sentry/utils/analytics';
  16. import OrganizationGeneralSettings from 'sentry/views/settings/organizationGeneralSettings';
  17. jest.mock('sentry/utils/analytics');
  18. describe('OrganizationGeneralSettings', function () {
  19. const ENDPOINT = '/organizations/org-slug/';
  20. const {organization, router} = initializeOrg();
  21. const defaultProps = {
  22. organization,
  23. router,
  24. location: router.location,
  25. params: {orgId: organization.slug},
  26. routes: router.routes,
  27. route: {},
  28. routeParams: router.params,
  29. };
  30. beforeEach(function () {
  31. OrganizationsStore.addOrReplace(organization);
  32. MockApiClient.addMockResponse({
  33. url: `/organizations/${organization.slug}/auth-provider/`,
  34. method: 'GET',
  35. });
  36. MockApiClient.addMockResponse({
  37. url: `/organizations/${organization.slug}/integrations/?provider_key=github`,
  38. method: 'GET',
  39. body: [TestStubs.GitHubIntegration()],
  40. });
  41. });
  42. it('can enable "early adopter"', async function () {
  43. render(<OrganizationGeneralSettings {...defaultProps} />);
  44. const mock = MockApiClient.addMockResponse({
  45. url: ENDPOINT,
  46. method: 'PUT',
  47. });
  48. await userEvent.click(screen.getByRole('checkbox', {name: /early adopter/i}));
  49. await waitFor(() => {
  50. expect(mock).toHaveBeenCalledWith(
  51. ENDPOINT,
  52. expect.objectContaining({
  53. data: {isEarlyAdopter: true},
  54. })
  55. );
  56. });
  57. });
  58. it('can enable "codecov access"', async function () {
  59. defaultProps.organization.features.push('codecov-integration');
  60. organization.codecovAccess = false;
  61. render(<OrganizationGeneralSettings {...defaultProps} />);
  62. const mock = MockApiClient.addMockResponse({
  63. url: ENDPOINT,
  64. method: 'PUT',
  65. });
  66. await userEvent.click(
  67. screen.getByRole('checkbox', {name: /Enable Code Coverage Insights/i})
  68. );
  69. await waitFor(() => {
  70. expect(mock).toHaveBeenCalledWith(
  71. ENDPOINT,
  72. expect.objectContaining({
  73. data: {codecovAccess: true},
  74. })
  75. );
  76. });
  77. expect(trackAnalytics).toHaveBeenCalled();
  78. });
  79. it('changes org slug and redirects to new slug', async function () {
  80. render(<OrganizationGeneralSettings {...defaultProps} />);
  81. const mock = MockApiClient.addMockResponse({
  82. url: ENDPOINT,
  83. method: 'PUT',
  84. body: {...organization, slug: 'new-slug'},
  85. });
  86. await userEvent.clear(screen.getByRole('textbox', {name: /slug/i}));
  87. await userEvent.type(screen.getByRole('textbox', {name: /slug/i}), 'new-slug');
  88. await userEvent.click(screen.getByLabelText('Save'));
  89. await waitFor(() => {
  90. expect(mock).toHaveBeenCalledWith(
  91. ENDPOINT,
  92. expect.objectContaining({
  93. data: {slug: 'new-slug'},
  94. })
  95. );
  96. expect(browserHistory.replace).toHaveBeenCalledWith('/settings/new-slug/');
  97. });
  98. });
  99. it('changes org slug and redirects to new customer-domain', async function () {
  100. const org = Organization({features: ['customer-domains']});
  101. const updateMock = MockApiClient.addMockResponse({
  102. url: `/organizations/${organization.slug}/`,
  103. method: 'PUT',
  104. body: {...org, slug: 'acme', links: {organizationUrl: 'https://acme.sentry.io'}},
  105. });
  106. render(<OrganizationGeneralSettings {...defaultProps} organization={org} />);
  107. const input = screen.getByRole('textbox', {name: /slug/i});
  108. await userEvent.clear(input);
  109. await userEvent.type(input, 'acme');
  110. await userEvent.click(screen.getByLabelText('Save'));
  111. await waitFor(() => {
  112. expect(updateMock).toHaveBeenCalledWith(
  113. '/organizations/org-slug/',
  114. expect.objectContaining({
  115. data: {
  116. slug: 'acme',
  117. },
  118. })
  119. );
  120. expect(window.location.replace).toHaveBeenCalledWith(
  121. 'https://acme.sentry.io/settings/organization/'
  122. );
  123. });
  124. });
  125. it('disables the entire form if user does not have write access', function () {
  126. const readOnlyOrg = Organization({access: ['org:read']});
  127. render(<OrganizationGeneralSettings {...defaultProps} organization={readOnlyOrg} />, {
  128. organization: readOnlyOrg,
  129. });
  130. const formElements = [
  131. ...screen.getAllByRole('textbox'),
  132. ...screen.getAllByRole('button'),
  133. ...screen.getAllByRole('checkbox'),
  134. ];
  135. for (const formElement of formElements) {
  136. expect(formElement).toBeDisabled();
  137. }
  138. expect(
  139. screen.getByText(
  140. 'These settings can only be edited by users with the organization owner or manager role.'
  141. )
  142. ).toBeInTheDocument();
  143. });
  144. it('does not have remove organization button without org:admin permission', function () {
  145. render(
  146. <OrganizationGeneralSettings
  147. {...defaultProps}
  148. organization={Organization({
  149. access: ['org:write'],
  150. })}
  151. />
  152. );
  153. expect(
  154. screen.queryByRole('button', {name: /remove organization/i})
  155. ).not.toBeInTheDocument();
  156. });
  157. it('can remove organization when org admin', async function () {
  158. act(() => ProjectsStore.loadInitialData([TestStubs.Project({slug: 'project'})]));
  159. render(
  160. <OrganizationGeneralSettings
  161. {...defaultProps}
  162. organization={Organization({access: ['org:admin']})}
  163. />
  164. );
  165. renderGlobalModal();
  166. const mock = MockApiClient.addMockResponse({
  167. url: ENDPOINT,
  168. method: 'DELETE',
  169. });
  170. await userEvent.click(screen.getByRole('button', {name: /remove organization/i}));
  171. const modal = screen.getByRole('dialog');
  172. expect(
  173. within(modal).getByText('This will also remove the following associated projects:')
  174. ).toBeInTheDocument();
  175. expect(within(modal).getByText('project')).toBeInTheDocument();
  176. await userEvent.click(
  177. within(modal).getByRole('button', {name: /remove organization/i})
  178. );
  179. await waitFor(() => {
  180. expect(mock).toHaveBeenCalledWith(
  181. ENDPOINT,
  182. expect.objectContaining({
  183. method: 'DELETE',
  184. })
  185. );
  186. });
  187. });
  188. });