index.spec.tsx 6.6 KB

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