index.spec.tsx 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import {DashboardListItemFixture} from 'sentry-fixture/dashboard';
  2. import {OrganizationFixture} from 'sentry-fixture/organization';
  3. import {ProjectFixture} from 'sentry-fixture/project';
  4. import {RouteComponentPropsFixture} from 'sentry-fixture/routeComponentPropsFixture';
  5. import {act, render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  6. import selectEvent from 'sentry-test/selectEvent';
  7. import ProjectsStore from 'sentry/stores/projectsStore';
  8. import localStorage from 'sentry/utils/localStorage';
  9. import {useNavigate} from 'sentry/utils/useNavigate';
  10. import ManageDashboards, {LAYOUT_KEY} from 'sentry/views/dashboards/manage';
  11. import {getPaginationPageLink} from 'sentry/views/organizationStats/utils';
  12. jest.mock('sentry/utils/localStorage');
  13. const FEATURES = [
  14. 'global-views',
  15. 'dashboards-basic',
  16. 'dashboards-edit',
  17. 'discover-query',
  18. ];
  19. jest.mock('sentry/utils/useNavigate', () => ({
  20. useNavigate: jest.fn(),
  21. }));
  22. const mockUseNavigate = jest.mocked(useNavigate);
  23. describe('Dashboards > Detail', function () {
  24. const mockUnauthorizedOrg = OrganizationFixture({
  25. features: ['global-views', 'dashboards-basic', 'discover-query'],
  26. });
  27. const mockAuthorizedOrg = OrganizationFixture({
  28. features: FEATURES,
  29. });
  30. beforeEach(function () {
  31. act(() => ProjectsStore.loadInitialData([ProjectFixture()]));
  32. MockApiClient.addMockResponse({
  33. url: '/organizations/org-slug/projects/',
  34. body: [],
  35. });
  36. MockApiClient.addMockResponse({
  37. url: '/organizations/org-slug/dashboards/',
  38. body: [],
  39. });
  40. MockApiClient.addMockResponse({
  41. url: '/organizations/org-slug/dashboards/?sort=name&per_page=9',
  42. body: [],
  43. });
  44. });
  45. afterEach(function () {
  46. MockApiClient.clearMockResponses();
  47. });
  48. it('renders', async function () {
  49. MockApiClient.addMockResponse({
  50. url: '/organizations/org-slug/dashboards/',
  51. body: [DashboardListItemFixture({title: 'Test Dashboard'})],
  52. });
  53. render(<ManageDashboards />, {
  54. ...RouteComponentPropsFixture(),
  55. organization: mockAuthorizedOrg,
  56. });
  57. expect(await screen.findByText('Dashboards')).toBeInTheDocument();
  58. expect(await screen.findByText('Test Dashboard')).toBeInTheDocument();
  59. expect(screen.queryAllByTestId('loading-placeholder')).toHaveLength(0);
  60. });
  61. it('shows error message when receiving error', async function () {
  62. MockApiClient.addMockResponse({
  63. url: '/organizations/org-slug/dashboards/',
  64. statusCode: 400,
  65. });
  66. render(<ManageDashboards />, {
  67. ...RouteComponentPropsFixture(),
  68. organization: mockAuthorizedOrg,
  69. });
  70. expect(await screen.findByText('Oops! Something went wrong')).toBeInTheDocument();
  71. });
  72. it('denies access on missing feature', async function () {
  73. render(<ManageDashboards />, {
  74. ...RouteComponentPropsFixture(),
  75. organization: mockUnauthorizedOrg,
  76. });
  77. expect(
  78. await screen.findByText("You don't have access to this feature")
  79. ).toBeInTheDocument();
  80. });
  81. it('denies access on no projects', async function () {
  82. act(() => ProjectsStore.loadInitialData([]));
  83. render(<ManageDashboards />, {
  84. ...RouteComponentPropsFixture(),
  85. organization: mockAuthorizedOrg,
  86. });
  87. expect(
  88. await screen.findByText('You need at least one project to use this view')
  89. ).toBeInTheDocument();
  90. });
  91. it('creates new dashboard', async function () {
  92. const org = OrganizationFixture({features: FEATURES});
  93. const mockNavigate = jest.fn();
  94. mockUseNavigate.mockReturnValue(mockNavigate);
  95. render(<ManageDashboards />, {
  96. ...RouteComponentPropsFixture(),
  97. organization: org,
  98. });
  99. await userEvent.click(await screen.findByTestId('dashboard-create'));
  100. expect(mockNavigate).toHaveBeenCalledWith({
  101. pathname: '/organizations/org-slug/dashboards/new/',
  102. query: {},
  103. });
  104. });
  105. it('can sort', async function () {
  106. const org = OrganizationFixture({features: FEATURES});
  107. const mockNavigate = jest.fn();
  108. mockUseNavigate.mockReturnValue(mockNavigate);
  109. render(<ManageDashboards />, {
  110. ...RouteComponentPropsFixture(),
  111. organization: org,
  112. });
  113. await selectEvent.select(
  114. await screen.findByRole('button', {name: /sort by/i}),
  115. 'Dashboard Name (A-Z)'
  116. );
  117. expect(mockNavigate).toHaveBeenCalledWith(
  118. expect.objectContaining({query: {sort: 'title'}})
  119. );
  120. });
  121. it('can search', async function () {
  122. const org = OrganizationFixture({features: FEATURES});
  123. const mockNavigate = jest.fn();
  124. mockUseNavigate.mockReturnValue(mockNavigate);
  125. render(<ManageDashboards />, {
  126. ...RouteComponentPropsFixture(),
  127. organization: org,
  128. });
  129. await userEvent.click(await screen.findByPlaceholderText('Search Dashboards'));
  130. await userEvent.keyboard('dash');
  131. await userEvent.keyboard('[Enter]');
  132. expect(mockNavigate).toHaveBeenCalledWith(
  133. expect.objectContaining({query: {query: 'dash'}})
  134. );
  135. });
  136. it('uses pagination correctly', async function () {
  137. const mockNavigate = jest.fn();
  138. mockUseNavigate.mockReturnValue(mockNavigate);
  139. MockApiClient.addMockResponse({
  140. url: '/organizations/org-slug/dashboards/',
  141. body: [DashboardListItemFixture({title: 'Test Dashboard 1'})],
  142. headers: {Link: getPaginationPageLink({numRows: 15, pageSize: 9, offset: 0})},
  143. });
  144. render(<ManageDashboards />, {
  145. ...RouteComponentPropsFixture(),
  146. organization: mockAuthorizedOrg,
  147. });
  148. expect(await screen.findByText('Test Dashboard 1')).toBeInTheDocument();
  149. await userEvent.click(await screen.findByLabelText('Next'));
  150. expect(mockNavigate).toHaveBeenCalledWith(
  151. expect.objectContaining({
  152. query: {
  153. cursor: '0:9:0',
  154. },
  155. })
  156. );
  157. });
  158. it('disables pagination correctly', async function () {
  159. const mockNavigate = jest.fn();
  160. mockUseNavigate.mockReturnValue(mockNavigate);
  161. MockApiClient.addMockResponse({
  162. url: '/organizations/org-slug/dashboards/',
  163. body: [DashboardListItemFixture({title: 'Test Dashboard 1'})],
  164. headers: {Link: getPaginationPageLink({numRows: 15, pageSize: 9, offset: 0})},
  165. });
  166. render(<ManageDashboards />, {
  167. ...RouteComponentPropsFixture(),
  168. organization: mockAuthorizedOrg,
  169. });
  170. expect(await screen.findByText('Test Dashboard 1')).toBeInTheDocument();
  171. await userEvent.click(await screen.findByLabelText('Previous'));
  172. expect(mockNavigate).not.toHaveBeenCalled();
  173. });
  174. it('toggles between grid and list view', async function () {
  175. MockApiClient.addMockResponse({
  176. url: '/organizations/org-slug/dashboards/',
  177. body: [DashboardListItemFixture({title: 'Test Dashboard 1'})],
  178. headers: {Link: getPaginationPageLink({numRows: 15, pageSize: 9, offset: 0})},
  179. });
  180. render(<ManageDashboards />, {
  181. ...RouteComponentPropsFixture(),
  182. organization: {
  183. ...mockAuthorizedOrg,
  184. features: [...FEATURES, 'dashboards-table-view'],
  185. },
  186. });
  187. expect(await screen.findByTestId('list')).toBeInTheDocument();
  188. await userEvent.click(await screen.findByTestId('list'));
  189. expect(localStorage.setItem).toHaveBeenCalledWith(LAYOUT_KEY, '"list"');
  190. expect(await screen.findByTestId('grid-editable')).toBeInTheDocument();
  191. expect(await screen.findByTestId('grid')).toBeInTheDocument();
  192. await userEvent.click(await screen.findByTestId('grid'));
  193. expect(localStorage.setItem).toHaveBeenCalledWith(LAYOUT_KEY, '"grid"');
  194. expect(await screen.findByTestId('dashboard-grid')).toBeInTheDocument();
  195. });
  196. });