index.spec.jsx 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {act, render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
  3. import * as incidentActions from 'sentry/actionCreators/serviceIncidents';
  4. import SidebarContainer from 'sentry/components/sidebar';
  5. import ConfigStore from 'sentry/stores/configStore';
  6. import {PersistedStoreProvider} from 'sentry/stores/persistedStore';
  7. import {OrganizationContext} from 'sentry/views/organizationContext';
  8. jest.mock('sentry/actionCreators/serviceIncidents');
  9. describe('Sidebar', function () {
  10. const {organization, router} = initializeOrg();
  11. const broadcast = TestStubs.Broadcast();
  12. const user = TestStubs.User();
  13. const apiMocks = {};
  14. const location = {...router.location, ...{pathname: '/test/'}};
  15. const getElement = props => (
  16. <SidebarContainer organization={organization} location={location} {...props} />
  17. );
  18. const renderSidebar = props =>
  19. render(
  20. <OrganizationContext.Provider value={organization}>
  21. <PersistedStoreProvider>{getElement(props)}</PersistedStoreProvider>
  22. </OrganizationContext.Provider>
  23. );
  24. beforeEach(function () {
  25. apiMocks.broadcasts = MockApiClient.addMockResponse({
  26. url: `/organizations/${organization.slug}/broadcasts/`,
  27. body: [broadcast],
  28. });
  29. apiMocks.broadcastsMarkAsSeen = MockApiClient.addMockResponse({
  30. url: '/broadcasts/',
  31. method: 'PUT',
  32. });
  33. apiMocks.sdkUpdates = MockApiClient.addMockResponse({
  34. url: `/organizations/${organization.slug}/sdk-updates/`,
  35. body: [],
  36. });
  37. MockApiClient.addMockResponse({
  38. url: `/organizations/${organization.slug}/client-state/`,
  39. body: {},
  40. });
  41. });
  42. it('renders', async function () {
  43. const {container} = renderSidebar();
  44. await waitFor(() => container);
  45. expect(screen.getByTestId('sidebar-dropdown')).toBeInTheDocument();
  46. });
  47. it('renders without org', function () {
  48. const {container} = renderSidebar({organization: null});
  49. // no org displays user details
  50. expect(screen.getByText(user.name)).toBeInTheDocument();
  51. expect(screen.getByText(user.email)).toBeInTheDocument();
  52. userEvent.click(screen.getByTestId('sidebar-dropdown'));
  53. expect(container).toSnapshot();
  54. });
  55. it('has can logout', async function () {
  56. const mock = MockApiClient.addMockResponse({
  57. url: '/auth/',
  58. method: 'DELETE',
  59. status: 204,
  60. });
  61. jest.spyOn(window.location, 'assign').mockImplementation(() => {});
  62. renderSidebar({
  63. organization: TestStubs.Organization({access: ['member:read']}),
  64. });
  65. userEvent.click(screen.getByTestId('sidebar-dropdown'));
  66. userEvent.click(screen.getByTestId('sidebar-signout'));
  67. await waitFor(() => expect(mock).toHaveBeenCalled());
  68. expect(window.location.assign).toHaveBeenCalledWith('/auth/login/');
  69. window.location.assign.mockRestore();
  70. });
  71. it('can toggle help menu', async function () {
  72. const {container} = renderSidebar();
  73. await waitFor(() => container);
  74. userEvent.click(screen.getByText('Help'));
  75. expect(screen.getByText('Visit Help Center')).toBeInTheDocument();
  76. expect(container).toSnapshot();
  77. });
  78. describe('SidebarDropdown', function () {
  79. it('can open Sidebar org/name dropdown menu', async function () {
  80. const {container} = renderSidebar();
  81. await waitFor(() => container);
  82. userEvent.click(screen.getByTestId('sidebar-dropdown'));
  83. const orgSettingsLink = screen.getByText('Organization settings');
  84. expect(orgSettingsLink).toBeInTheDocument();
  85. expect(container).toSnapshot();
  86. });
  87. it('has link to Members settings with `member:write`', async function () {
  88. const {container} = renderSidebar({
  89. organization: TestStubs.Organization({access: ['member:read']}),
  90. });
  91. await waitFor(() => container);
  92. userEvent.click(screen.getByTestId('sidebar-dropdown'));
  93. expect(screen.getByText('Members')).toBeInTheDocument();
  94. });
  95. it('can open "Switch Organization" sub-menu', async function () {
  96. act(() => void ConfigStore.set('features', new Set(['organizations:create'])));
  97. const {container} = renderSidebar();
  98. await waitFor(() => container);
  99. userEvent.click(screen.getByTestId('sidebar-dropdown'));
  100. jest.useFakeTimers();
  101. userEvent.type(screen.getByText('Switch organization'), '{enter}');
  102. act(() => jest.advanceTimersByTime(500));
  103. jest.useRealTimers();
  104. const createOrg = screen.getByText('Create a new organization');
  105. expect(createOrg).toBeInTheDocument();
  106. expect(container).toSnapshot();
  107. });
  108. });
  109. describe('SidebarPanel', function () {
  110. it('hides when path changes', async function () {
  111. const {rerender} = renderSidebar();
  112. userEvent.click(screen.getByText("What's new"));
  113. expect(await screen.findByRole('dialog')).toBeInTheDocument();
  114. expect(screen.getByText("What's new in Sentry")).toBeInTheDocument();
  115. rerender(getElement({location: {...router.location, pathname: 'new-path-name'}}));
  116. expect(screen.queryByText("What's new in Sentry")).not.toBeInTheDocument();
  117. await tick();
  118. });
  119. it('can have onboarding feature', async function () {
  120. renderSidebar({
  121. organization: {...organization, features: ['onboarding']},
  122. });
  123. const quickStart = screen.getByText('Quick Start');
  124. expect(quickStart).toBeInTheDocument();
  125. userEvent.click(quickStart);
  126. expect(await screen.findByRole('dialog')).toBeInTheDocument();
  127. expect(screen.getByText('Capture your first error')).toBeInTheDocument();
  128. userEvent.click(quickStart);
  129. expect(screen.queryByText('Capture your first error')).not.toBeInTheDocument();
  130. await tick();
  131. });
  132. it('displays empty panel when there are no Broadcasts', async function () {
  133. MockApiClient.addMockResponse({
  134. url: `/organizations/${organization.slug}/broadcasts/`,
  135. body: [],
  136. });
  137. renderSidebar();
  138. userEvent.click(screen.getByText("What's new"));
  139. expect(await screen.findByRole('dialog')).toBeInTheDocument();
  140. expect(screen.getByText("What's new in Sentry")).toBeInTheDocument();
  141. expect(
  142. screen.getByText('No recent updates from the Sentry team.')
  143. ).toBeInTheDocument();
  144. // Close the sidebar
  145. userEvent.click(screen.getByText("What's new"));
  146. expect(screen.queryByText("What's new in Sentry")).not.toBeInTheDocument();
  147. await tick();
  148. });
  149. it('can display Broadcasts panel and mark as seen', async function () {
  150. jest.useFakeTimers();
  151. const {container} = renderSidebar();
  152. expect(apiMocks.broadcasts).toHaveBeenCalled();
  153. userEvent.click(screen.getByText("What's new"));
  154. expect(await screen.findByRole('dialog')).toBeInTheDocument();
  155. expect(screen.getByText("What's new in Sentry")).toBeInTheDocument();
  156. const broadcastTitle = screen.getByText(broadcast.title);
  157. expect(broadcastTitle).toBeInTheDocument();
  158. expect(container).toSnapshot();
  159. // Should mark as seen after a delay
  160. act(() => jest.advanceTimersByTime(2000));
  161. expect(apiMocks.broadcastsMarkAsSeen).toHaveBeenCalledWith(
  162. '/broadcasts/',
  163. expect.objectContaining({
  164. data: {hasSeen: '1'},
  165. query: {id: ['8']},
  166. })
  167. );
  168. jest.useRealTimers();
  169. // Close the sidebar
  170. userEvent.click(screen.getByText("What's new"));
  171. expect(screen.queryByText("What's new in Sentry")).not.toBeInTheDocument();
  172. await tick();
  173. });
  174. it('can unmount Sidebar (and Broadcasts) and kills Broadcast timers', async function () {
  175. jest.useFakeTimers();
  176. const {unmount} = renderSidebar();
  177. // This will start timer to mark as seen
  178. userEvent.click(screen.getByRole('link', {name: "What's new"}));
  179. expect(await screen.findByText("What's new in Sentry")).toBeInTheDocument();
  180. act(() => jest.advanceTimersByTime(500));
  181. // Unmounting will cancel timers
  182. unmount();
  183. // This advances timers enough so that mark as seen should be called if
  184. // it wasn't unmounted
  185. act(() => jest.advanceTimersByTime(600));
  186. expect(apiMocks.broadcastsMarkAsSeen).not.toHaveBeenCalled();
  187. jest.useRealTimers();
  188. });
  189. it('can show Incidents in Sidebar Panel', async function () {
  190. incidentActions.loadIncidents = jest.fn(() => ({
  191. incidents: [TestStubs.ServiceIncident()],
  192. }));
  193. const {container} = renderSidebar();
  194. userEvent.click(await screen.findByText('Service status'));
  195. expect(container).toSnapshot();
  196. });
  197. });
  198. it('can toggle collapsed state', async function () {
  199. const container = renderSidebar();
  200. await waitFor(() => container);
  201. expect(screen.getByText(user.name)).toBeInTheDocument();
  202. expect(screen.getByText(organization.name)).toBeInTheDocument();
  203. userEvent.click(screen.getByTestId('sidebar-collapse'));
  204. // Check that the organization name is no longer visible
  205. expect(screen.queryByText(organization.name)).not.toBeInTheDocument();
  206. // Un-collapse he sidebar and make sure the org name is visible again
  207. userEvent.click(screen.getByTestId('sidebar-collapse'));
  208. expect(await screen.findByText(organization.name)).toBeInTheDocument();
  209. });
  210. });