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();
});
});