organizationContextContainer.spec.tsx 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. import {Organization} from 'sentry-fixture/organization';
  2. import {initializeOrg} from 'sentry-test/initializeOrg';
  3. import {render, screen, waitFor} from 'sentry-test/reactTestingLibrary';
  4. import * as OrganizationActionCreator from 'sentry/actionCreators/organization';
  5. import * as openSudo from 'sentry/actionCreators/sudoModal';
  6. import ConfigStore from 'sentry/stores/configStore';
  7. import OrganizationStore from 'sentry/stores/organizationStore';
  8. import ProjectsStore from 'sentry/stores/projectsStore';
  9. import TeamStore from 'sentry/stores/teamStore';
  10. import useOrganization from 'sentry/utils/useOrganization';
  11. import {OrganizationLegacyContext} from 'sentry/views/organizationContextContainer';
  12. jest.mock('sentry/stores/configStore', () => ({
  13. get: jest.fn(),
  14. }));
  15. describe('OrganizationContextContainer', function () {
  16. const {organization, projects, routerProps} = initializeOrg();
  17. const teams = [TestStubs.Team()];
  18. const api = new MockApiClient();
  19. let getOrgMock: jest.Mock;
  20. let getProjectsMock: jest.Mock;
  21. let getTeamsMock: jest.Mock;
  22. function DisplayOrg() {
  23. const contextOrg = useOrganization();
  24. return <div>{contextOrg.slug}</div>;
  25. }
  26. type Props = Partial<React.ComponentProps<typeof OrganizationLegacyContext>>;
  27. function makeComponent(props?: Props) {
  28. return (
  29. <OrganizationLegacyContext
  30. {...routerProps}
  31. api={api}
  32. params={{orgId: 'org-slug'}}
  33. location={TestStubs.location({query: {}})}
  34. useLastOrganization={false}
  35. organizationsLoading={false}
  36. organizations={[]}
  37. includeSidebar={false}
  38. {...props}
  39. >
  40. <DisplayOrg />
  41. </OrganizationLegacyContext>
  42. );
  43. }
  44. function renderComponent(props?: Props) {
  45. return render(makeComponent(props));
  46. }
  47. beforeEach(function () {
  48. MockApiClient.clearMockResponses();
  49. getOrgMock = MockApiClient.addMockResponse({
  50. url: '/organizations/org-slug/',
  51. body: organization,
  52. });
  53. getProjectsMock = MockApiClient.addMockResponse({
  54. url: '/organizations/org-slug/projects/',
  55. body: projects,
  56. });
  57. getTeamsMock = MockApiClient.addMockResponse({
  58. url: '/organizations/org-slug/teams/',
  59. body: teams,
  60. });
  61. jest.spyOn(TeamStore, 'loadInitialData');
  62. jest.spyOn(ProjectsStore, 'loadInitialData');
  63. jest.spyOn(OrganizationActionCreator, 'fetchOrganizationDetails');
  64. });
  65. afterEach(function () {
  66. OrganizationStore.reset();
  67. jest.restoreAllMocks();
  68. });
  69. it('renders and fetches org, projects, and teams', async function () {
  70. renderComponent();
  71. await waitFor(() => expect(getOrgMock).toHaveBeenCalled());
  72. expect(getProjectsMock).toHaveBeenCalled();
  73. expect(getTeamsMock).toHaveBeenCalled();
  74. expect(screen.queryByRole('loading-indicator')).not.toBeInTheDocument();
  75. expect(screen.getByText(organization.slug)).toBeInTheDocument();
  76. expect(
  77. screen.queryByText('The organization you were looking for was not found.')
  78. ).not.toBeInTheDocument();
  79. expect(TeamStore.loadInitialData).toHaveBeenCalledWith(teams);
  80. expect(ProjectsStore.loadInitialData).toHaveBeenCalledWith(projects);
  81. expect(OrganizationActionCreator.fetchOrganizationDetails).toHaveBeenCalledWith(
  82. api,
  83. 'org-slug',
  84. true,
  85. true
  86. );
  87. });
  88. it('fetches new org when router params change', async function () {
  89. const newOrg = Organization({slug: 'new-slug'});
  90. const {rerender} = renderComponent();
  91. expect(await screen.findByText(organization.slug)).toBeInTheDocument();
  92. const mock = MockApiClient.addMockResponse({
  93. url: '/organizations/new-slug/',
  94. body: newOrg,
  95. });
  96. const projectsMock = MockApiClient.addMockResponse({
  97. url: '/organizations/new-slug/projects/',
  98. body: projects,
  99. });
  100. const teamsMock = MockApiClient.addMockResponse({
  101. url: '/organizations/new-slug/teams/',
  102. body: teams,
  103. });
  104. // Re-render with new org slug
  105. rerender(makeComponent({params: {orgId: newOrg.slug}}));
  106. // Loads new org
  107. expect(screen.getByTestId('loading-indicator')).toBeInTheDocument();
  108. // Renders new org
  109. expect(await screen.findByText(newOrg.slug)).toBeInTheDocument();
  110. expect(mock).toHaveBeenLastCalledWith('/organizations/new-slug/', expect.anything());
  111. expect(projectsMock).toHaveBeenCalled();
  112. expect(teamsMock).toHaveBeenCalled();
  113. });
  114. it('shows loading error for non-superusers on 403s', async function () {
  115. getOrgMock = MockApiClient.addMockResponse({
  116. url: '/organizations/org-slug/',
  117. statusCode: 403,
  118. });
  119. jest.spyOn(console, 'error').mockImplementation(jest.fn()); // eslint-disable-line no-console
  120. renderComponent();
  121. expect(
  122. await screen.findByText('There was an error loading data.')
  123. ).toBeInTheDocument();
  124. // eslint-disable-next-line no-console
  125. expect(console.error).toHaveBeenCalled();
  126. });
  127. it('opens sudo modal for superusers on 403s', async function () {
  128. const openSudoSpy = jest.spyOn(openSudo, 'openSudo');
  129. jest
  130. .mocked(ConfigStore.get)
  131. .mockImplementation(() => TestStubs.Config({isSuperuser: true}));
  132. getOrgMock = MockApiClient.addMockResponse({
  133. url: '/organizations/org-slug/',
  134. statusCode: 403,
  135. });
  136. renderComponent();
  137. await waitFor(() => expect(openSudoSpy).toHaveBeenCalled());
  138. });
  139. it('uses last organization from ConfigStore', function () {
  140. getOrgMock = MockApiClient.addMockResponse({
  141. url: '/organizations/last-org/',
  142. body: organization,
  143. });
  144. MockApiClient.addMockResponse({
  145. url: '/organizations/last-org/projects/',
  146. body: projects,
  147. });
  148. MockApiClient.addMockResponse({
  149. url: '/organizations/last-org/teams/',
  150. body: teams,
  151. });
  152. // mocking `.get('lastOrganization')`
  153. jest.mocked(ConfigStore.get).mockImplementation(() => 'last-org');
  154. renderComponent({useLastOrganization: true, params: {orgId: ''}});
  155. expect(getOrgMock).toHaveBeenLastCalledWith(
  156. '/organizations/last-org/',
  157. expect.anything()
  158. );
  159. });
  160. it('uses last organization from `organizations` prop', async function () {
  161. MockApiClient.addMockResponse({
  162. url: '/organizations/foo/environments/',
  163. body: TestStubs.Environments(),
  164. });
  165. getOrgMock = MockApiClient.addMockResponse({
  166. url: '/organizations/foo/',
  167. body: organization,
  168. });
  169. getProjectsMock = MockApiClient.addMockResponse({
  170. url: '/organizations/foo/projects/',
  171. body: projects,
  172. });
  173. getTeamsMock = MockApiClient.addMockResponse({
  174. url: '/organizations/foo/teams/',
  175. body: teams,
  176. });
  177. jest.mocked(ConfigStore.get).mockImplementation(() => '');
  178. const {rerender} = renderComponent({
  179. params: {orgId: ''},
  180. useLastOrganization: true,
  181. organizationsLoading: true,
  182. organizations: [],
  183. });
  184. expect(screen.getByTestId('loading-indicator')).toBeInTheDocument();
  185. rerender(
  186. makeComponent({
  187. params: {orgId: ''},
  188. useLastOrganization: true,
  189. organizationsLoading: false,
  190. organizations: [Organization({slug: 'foo'}), Organization({slug: 'bar'})],
  191. })
  192. );
  193. expect(await screen.findByText(organization.slug)).toBeInTheDocument();
  194. expect(getOrgMock).toHaveBeenCalled();
  195. expect(getProjectsMock).toHaveBeenCalled();
  196. expect(getTeamsMock).toHaveBeenCalled();
  197. });
  198. it('uses last organization when no orgId in URL - and fetches org details once', async function () {
  199. jest.mocked(ConfigStore.get).mockImplementation(() => 'my-last-org');
  200. getOrgMock = MockApiClient.addMockResponse({
  201. url: '/organizations/my-last-org/',
  202. body: Organization({slug: 'my-last-org'}),
  203. });
  204. getProjectsMock = MockApiClient.addMockResponse({
  205. url: '/organizations/my-last-org/projects/',
  206. body: projects,
  207. });
  208. getTeamsMock = MockApiClient.addMockResponse({
  209. url: '/organizations/my-last-org/teams/',
  210. body: teams,
  211. });
  212. const {rerender} = renderComponent({
  213. params: {orgId: ''},
  214. useLastOrganization: true,
  215. organizations: [],
  216. });
  217. expect(await screen.findByText('my-last-org')).toBeInTheDocument();
  218. expect(getOrgMock).toHaveBeenCalledTimes(1);
  219. // Simulate OrganizationsStore being loaded *after* `OrganizationContext` finishes
  220. // org details fetch
  221. rerender(
  222. makeComponent({
  223. params: {orgId: ''},
  224. useLastOrganization: true,
  225. organizationsLoading: false,
  226. organizations: [Organization({slug: 'foo'}), Organization({slug: 'bar'})],
  227. })
  228. );
  229. expect(getOrgMock).toHaveBeenCalledTimes(1);
  230. expect(getProjectsMock).toHaveBeenCalledTimes(1);
  231. expect(getTeamsMock).toHaveBeenCalledTimes(1);
  232. });
  233. it('fetches org details only once if organizations loading store changes', async function () {
  234. const {rerender} = renderComponent({
  235. params: {orgId: 'org-slug'},
  236. organizationsLoading: true,
  237. organizations: [],
  238. });
  239. expect(await screen.findByText(organization.slug)).toBeInTheDocument();
  240. expect(getOrgMock).toHaveBeenCalledTimes(1);
  241. // Simulate OrganizationsStore being loaded *after* `OrganizationContext` finishes
  242. // org details fetch
  243. rerender(
  244. makeComponent({
  245. params: {orgId: 'org-slug'},
  246. organizationsLoading: false,
  247. organizations: [Organization({slug: 'foo'}), Organization({slug: 'bar'})],
  248. })
  249. );
  250. expect(getOrgMock).toHaveBeenCalledTimes(1);
  251. expect(getProjectsMock).toHaveBeenCalledTimes(1);
  252. expect(getTeamsMock).toHaveBeenCalledTimes(1);
  253. });
  254. });