import {DashboardListItemFixture} from 'sentry-fixture/dashboard'; import {OrganizationFixture} from 'sentry-fixture/organization'; import {ProjectFixture} from 'sentry-fixture/project'; import {RouteComponentPropsFixture} from 'sentry-fixture/routeComponentPropsFixture'; import {act, render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; import selectEvent from 'sentry-test/selectEvent'; import ProjectsStore from 'sentry/stores/projectsStore'; import {useNavigate} from 'sentry/utils/useNavigate'; import ManageDashboards from 'sentry/views/dashboards/manage'; import {getPaginationPageLink} from 'sentry/views/organizationStats/utils'; const FEATURES = [ 'global-views', 'dashboards-basic', 'dashboards-edit', 'discover-query', ]; jest.mock('sentry/utils/useNavigate', () => ({ useNavigate: jest.fn(), })); const mockUseNavigate = jest.mocked(useNavigate); describe('Dashboards > Detail', function () { const mockUnauthorizedOrg = OrganizationFixture({ features: ['global-views', 'dashboards-basic', 'discover-query'], }); const mockAuthorizedOrg = OrganizationFixture({ features: FEATURES, }); beforeEach(function () { act(() => ProjectsStore.loadInitialData([ProjectFixture()])); MockApiClient.addMockResponse({ url: '/organizations/org-slug/projects/', body: [], }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/dashboards/', body: [], }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/dashboards/?sort=name&per_page=9', body: [], }); }); afterEach(function () { MockApiClient.clearMockResponses(); }); it('renders', async function () { MockApiClient.addMockResponse({ url: '/organizations/org-slug/dashboards/', body: [DashboardListItemFixture({title: 'Test Dashboard'})], }); render(, { ...RouteComponentPropsFixture(), organization: mockAuthorizedOrg, }); expect(await screen.findByText('Dashboards')).toBeInTheDocument(); expect(await screen.findByText('Test Dashboard')).toBeInTheDocument(); expect(screen.queryAllByTestId('loading-placeholder')).toHaveLength(0); }); it('shows error message when receiving error', async function () { MockApiClient.addMockResponse({ url: '/organizations/org-slug/dashboards/', statusCode: 400, }); render(, { ...RouteComponentPropsFixture(), organization: mockAuthorizedOrg, }); expect(await screen.findByText('Oops! Something went wrong')).toBeInTheDocument(); }); it('denies access on missing feature', async function () { render(, { ...RouteComponentPropsFixture(), organization: mockUnauthorizedOrg, }); expect( await screen.findByText("You don't have access to this feature") ).toBeInTheDocument(); }); it('denies access on no projects', async function () { act(() => ProjectsStore.loadInitialData([])); render(, { ...RouteComponentPropsFixture(), organization: mockAuthorizedOrg, }); expect( await screen.findByText('You need at least one project to use this view') ).toBeInTheDocument(); }); it('creates new dashboard', async function () { const org = OrganizationFixture({features: FEATURES}); const mockNavigate = jest.fn(); mockUseNavigate.mockReturnValue(mockNavigate); render(, { ...RouteComponentPropsFixture(), organization: org, }); await userEvent.click(await screen.findByTestId('dashboard-create')); expect(mockNavigate).toHaveBeenCalledWith({ pathname: '/organizations/org-slug/dashboards/new/', query: {}, }); }); it('can sort', async function () { const org = OrganizationFixture({features: FEATURES}); const mockNavigate = jest.fn(); mockUseNavigate.mockReturnValue(mockNavigate); render(, { ...RouteComponentPropsFixture(), organization: org, }); await selectEvent.select( await screen.findByRole('button', {name: /sort by/i}), 'Dashboard Name (A-Z)' ); expect(mockNavigate).toHaveBeenCalledWith( expect.objectContaining({query: {sort: 'title'}}) ); }); it('can search', async function () { const org = OrganizationFixture({features: FEATURES}); const mockNavigate = jest.fn(); mockUseNavigate.mockReturnValue(mockNavigate); render(, { ...RouteComponentPropsFixture(), organization: org, }); await userEvent.click(await screen.findByPlaceholderText('Search Dashboards')); await userEvent.keyboard('dash'); await userEvent.keyboard('[Enter]'); expect(mockNavigate).toHaveBeenCalledWith( expect.objectContaining({query: {query: 'dash'}}) ); }); it('uses pagination correctly', async function () { const mockNavigate = jest.fn(); mockUseNavigate.mockReturnValue(mockNavigate); MockApiClient.addMockResponse({ url: '/organizations/org-slug/dashboards/', body: [DashboardListItemFixture({title: 'Test Dashboard 1'})], headers: {Link: getPaginationPageLink({numRows: 15, pageSize: 9, offset: 0})}, }); render(, { ...RouteComponentPropsFixture(), organization: mockAuthorizedOrg, }); expect(await screen.findByText('Test Dashboard 1')).toBeInTheDocument(); await userEvent.click(await screen.findByLabelText('Next')); expect(mockNavigate).toHaveBeenCalledWith( expect.objectContaining({ query: { cursor: '0:9:0', }, }) ); }); it('disables pagination correctly', async function () { const mockNavigate = jest.fn(); mockUseNavigate.mockReturnValue(mockNavigate); MockApiClient.addMockResponse({ url: '/organizations/org-slug/dashboards/', body: [DashboardListItemFixture({title: 'Test Dashboard 1'})], headers: {Link: getPaginationPageLink({numRows: 15, pageSize: 9, offset: 0})}, }); render(, { ...RouteComponentPropsFixture(), organization: mockAuthorizedOrg, }); expect(await screen.findByText('Test Dashboard 1')).toBeInTheDocument(); await userEvent.click(await screen.findByLabelText('Previous')); expect(mockNavigate).not.toHaveBeenCalled(); }); });