123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284 |
- import {initializeOrg} from 'sentry-test/initializeOrg';
- import {act, render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
- import * as incidentActions from 'sentry/actionCreators/serviceIncidents';
- import SidebarContainer from 'sentry/components/sidebar';
- import ConfigStore from 'sentry/stores/configStore';
- import {PersistedStoreProvider} from 'sentry/stores/persistedStore';
- import {OrganizationContext} from 'sentry/views/organizationContext';
- import {RouteContext} from 'sentry/views/routeContext';
- jest.mock('sentry/actionCreators/serviceIncidents');
- describe('Sidebar', function () {
- const {organization, router} = initializeOrg();
- const broadcast = TestStubs.Broadcast();
- const user = TestStubs.User();
- const apiMocks = {};
- const location = {...router.location, ...{pathname: '/test/'}};
- const getElement = props => (
- <RouteContext.Provider
- value={{
- location,
- params: {},
- router,
- routes: [],
- }}
- >
- <OrganizationContext.Provider value={organization}>
- <PersistedStoreProvider>
- <SidebarContainer organization={organization} location={location} {...props} />
- </PersistedStoreProvider>
- </OrganizationContext.Provider>
- </RouteContext.Provider>
- );
- const renderSidebar = props => render(getElement(props));
- beforeEach(function () {
- apiMocks.broadcasts = MockApiClient.addMockResponse({
- url: `/organizations/${organization.slug}/broadcasts/`,
- body: [broadcast],
- });
- apiMocks.broadcastsMarkAsSeen = MockApiClient.addMockResponse({
- url: '/broadcasts/',
- method: 'PUT',
- });
- apiMocks.sdkUpdates = MockApiClient.addMockResponse({
- url: `/organizations/${organization.slug}/sdk-updates/`,
- body: [],
- });
- MockApiClient.addMockResponse({
- url: `/organizations/${organization.slug}/client-state/`,
- body: {},
- });
- });
- it('renders', async function () {
- const {container} = renderSidebar();
- await waitFor(() => container);
- expect(screen.getByTestId('sidebar-dropdown')).toBeInTheDocument();
- });
- it('renders without org', function () {
- const {container} = renderSidebar({organization: null});
- // no org displays user details
- expect(screen.getByText(user.name)).toBeInTheDocument();
- expect(screen.getByText(user.email)).toBeInTheDocument();
- userEvent.click(screen.getByTestId('sidebar-dropdown'));
- expect(container).toSnapshot();
- });
- it('has can logout', async function () {
- const mock = MockApiClient.addMockResponse({
- url: '/auth/',
- method: 'DELETE',
- status: 204,
- });
- jest.spyOn(window.location, 'assign').mockImplementation(() => {});
- renderSidebar({
- organization: TestStubs.Organization({access: ['member:read']}),
- });
- userEvent.click(screen.getByTestId('sidebar-dropdown'));
- userEvent.click(screen.getByTestId('sidebar-signout'));
- await waitFor(() => expect(mock).toHaveBeenCalled());
- expect(window.location.assign).toHaveBeenCalledWith('/auth/login/');
- window.location.assign.mockRestore();
- });
- it('can toggle help menu', async function () {
- const {container} = renderSidebar();
- await waitFor(() => container);
- userEvent.click(screen.getByText('Help'));
- expect(screen.getByText('Visit Help Center')).toBeInTheDocument();
- expect(container).toSnapshot();
- });
- describe('SidebarDropdown', function () {
- it('can open Sidebar org/name dropdown menu', async function () {
- const {container} = renderSidebar();
- await waitFor(() => container);
- userEvent.click(screen.getByTestId('sidebar-dropdown'));
- const orgSettingsLink = screen.getByText('Organization settings');
- expect(orgSettingsLink).toBeInTheDocument();
- expect(container).toSnapshot();
- });
- it('has link to Members settings with `member:write`', async function () {
- const {container} = renderSidebar({
- organization: TestStubs.Organization({access: ['member:read']}),
- });
- await waitFor(() => container);
- userEvent.click(screen.getByTestId('sidebar-dropdown'));
- expect(screen.getByText('Members')).toBeInTheDocument();
- });
- it('can open "Switch Organization" sub-menu', async function () {
- act(() => void ConfigStore.set('features', new Set(['organizations:create'])));
- const {container} = renderSidebar();
- await waitFor(() => container);
- userEvent.click(screen.getByTestId('sidebar-dropdown'));
- jest.useFakeTimers();
- userEvent.type(screen.getByText('Switch organization'), '{enter}');
- act(() => jest.advanceTimersByTime(500));
- jest.useRealTimers();
- const createOrg = screen.getByText('Create a new organization');
- expect(createOrg).toBeInTheDocument();
- expect(container).toSnapshot();
- });
- });
- describe('SidebarPanel', function () {
- it('hides when path changes', async function () {
- const {rerender} = renderSidebar();
- userEvent.click(screen.getByText("What's new"));
- expect(await screen.findByRole('dialog')).toBeInTheDocument();
- expect(screen.getByText("What's new in Sentry")).toBeInTheDocument();
- rerender(getElement({location: {...router.location, pathname: 'new-path-name'}}));
- expect(screen.queryByText("What's new in Sentry")).not.toBeInTheDocument();
- await tick();
- });
- it('can have onboarding feature', async function () {
- renderSidebar({
- organization: {...organization, features: ['onboarding']},
- });
- const quickStart = screen.getByText('Quick Start');
- expect(quickStart).toBeInTheDocument();
- userEvent.click(quickStart);
- expect(await screen.findByRole('dialog')).toBeInTheDocument();
- expect(screen.getByText('Capture your first error')).toBeInTheDocument();
- userEvent.click(quickStart);
- expect(screen.queryByText('Capture your first error')).not.toBeInTheDocument();
- await tick();
- });
- it('displays empty panel when there are no Broadcasts', async function () {
- MockApiClient.addMockResponse({
- url: `/organizations/${organization.slug}/broadcasts/`,
- body: [],
- });
- renderSidebar();
- userEvent.click(screen.getByText("What's new"));
- expect(await screen.findByRole('dialog')).toBeInTheDocument();
- expect(screen.getByText("What's new in Sentry")).toBeInTheDocument();
- expect(
- screen.getByText('No recent updates from the Sentry team.')
- ).toBeInTheDocument();
- // Close the sidebar
- userEvent.click(screen.getByText("What's new"));
- expect(screen.queryByText("What's new in Sentry")).not.toBeInTheDocument();
- await tick();
- });
- it('can display Broadcasts panel and mark as seen', async function () {
- jest.useFakeTimers();
- const {container} = renderSidebar();
- expect(apiMocks.broadcasts).toHaveBeenCalled();
- userEvent.click(screen.getByText("What's new"));
- expect(await screen.findByRole('dialog')).toBeInTheDocument();
- expect(screen.getByText("What's new in Sentry")).toBeInTheDocument();
- const broadcastTitle = screen.getByText(broadcast.title);
- expect(broadcastTitle).toBeInTheDocument();
- expect(container).toSnapshot();
- // Should mark as seen after a delay
- act(() => jest.advanceTimersByTime(2000));
- expect(apiMocks.broadcastsMarkAsSeen).toHaveBeenCalledWith(
- '/broadcasts/',
- expect.objectContaining({
- data: {hasSeen: '1'},
- query: {id: ['8']},
- })
- );
- jest.useRealTimers();
- // Close the sidebar
- userEvent.click(screen.getByText("What's new"));
- expect(screen.queryByText("What's new in Sentry")).not.toBeInTheDocument();
- await tick();
- });
- it('can unmount Sidebar (and Broadcasts) and kills Broadcast timers', async function () {
- jest.useFakeTimers();
- const {unmount} = renderSidebar();
- // This will start timer to mark as seen
- userEvent.click(screen.getByRole('link', {name: "What's new"}));
- expect(await screen.findByText("What's new in Sentry")).toBeInTheDocument();
- act(() => jest.advanceTimersByTime(500));
- // Unmounting will cancel timers
- unmount();
- // This advances timers enough so that mark as seen should be called if
- // it wasn't unmounted
- act(() => jest.advanceTimersByTime(600));
- expect(apiMocks.broadcastsMarkAsSeen).not.toHaveBeenCalled();
- jest.useRealTimers();
- });
- it('can show Incidents in Sidebar Panel', async function () {
- incidentActions.loadIncidents = jest.fn(() => ({
- incidents: [TestStubs.ServiceIncident()],
- }));
- const {container} = renderSidebar();
- userEvent.click(await screen.findByText('Service status'));
- await screen.findByText('Recent service updates');
- expect(container).toSnapshot();
- });
- });
- it('can toggle collapsed state', async function () {
- const container = renderSidebar();
- await waitFor(() => container);
- expect(screen.getByText(user.name)).toBeInTheDocument();
- expect(screen.getByText(organization.name)).toBeInTheDocument();
- userEvent.click(screen.getByTestId('sidebar-collapse'));
- // Check that the organization name is no longer visible
- expect(screen.queryByText(organization.name)).not.toBeInTheDocument();
- // Un-collapse he sidebar and make sure the org name is visible again
- userEvent.click(screen.getByTestId('sidebar-collapse'));
- expect(await screen.findByText(organization.name)).toBeInTheDocument();
- });
- });
|