import {OrganizationFixture} from 'sentry-fixture/organization'; import {RouterContextFixture} from 'sentry-fixture/routerContextFixture'; import {render, screen, waitFor} from 'sentry-test/reactTestingLibrary'; import type {Organization as TOrganization} from 'sentry/types'; import useDeadRageSelectors from 'sentry/utils/replays/hooks/useDeadRageSelectors'; import { useHaveSelectedProjectsSentAnyReplayEvents, useReplayOnboardingSidebarPanel, } from 'sentry/utils/replays/hooks/useReplayOnboarding'; import useOrganization from 'sentry/utils/useOrganization'; import useProjectSdkNeedsUpdate from 'sentry/utils/useProjectSdkNeedsUpdate'; import ListPage from 'sentry/views/replays/list/listContent'; jest.mock('sentry/utils/replays/hooks/useDeadRageSelectors'); jest.mock('sentry/utils/replays/hooks/useReplayOnboarding'); jest.mock('sentry/utils/replays/hooks/useReplayPageview'); jest.mock('sentry/utils/useOrganization'); jest.mock('sentry/utils/useProjectSdkNeedsUpdate'); const mockUseDeadRageSelectors = jest.mocked(useDeadRageSelectors); const mockUseHaveSelectedProjectsSentAnyReplayEvents = jest.mocked( useHaveSelectedProjectsSentAnyReplayEvents ); const mockUseProjectSdkNeedsUpdate = jest.mocked(useProjectSdkNeedsUpdate); const mockUseReplayOnboardingSidebarPanel = jest.mocked(useReplayOnboardingSidebarPanel); mockUseReplayOnboardingSidebarPanel.mockReturnValue({activateSidebar: jest.fn()}); const AM1_FEATURES = []; const AM2_FEATURES = ['session-replay']; function getMockOrganizationFixture({features}: {features: string[]}) { const mockOrg = OrganizationFixture({ features, access: [], }); jest.mocked(useOrganization).mockReturnValue(mockOrg); return mockOrg; } function getMockContext(mockOrg: TOrganization) { return RouterContextFixture([{organization: mockOrg}]); } describe('ReplayList', () => { let mockFetchReplayListRequest; beforeEach(() => { mockUseHaveSelectedProjectsSentAnyReplayEvents.mockClear(); mockUseProjectSdkNeedsUpdate.mockClear(); mockUseDeadRageSelectors.mockClear(); MockApiClient.clearMockResponses(); MockApiClient.addMockResponse({ url: '/organizations/org-slug/tags/', body: [], }); mockFetchReplayListRequest = MockApiClient.addMockResponse({ url: `/organizations/org-slug/replays/`, body: {}, }); }); it('should render the onboarding panel when the org is on AM1', async () => { const mockOrg = getMockOrganizationFixture({features: AM1_FEATURES}); mockUseHaveSelectedProjectsSentAnyReplayEvents.mockReturnValue({ fetching: false, hasSentOneReplay: false, }); mockUseProjectSdkNeedsUpdate.mockReturnValue({ isError: false, isFetching: false, needsUpdate: false, }); render(, { context: getMockContext(mockOrg), }); await waitFor(() => expect(screen.getByText('Get to the root cause faster')).toBeInTheDocument() ); expect(mockFetchReplayListRequest).not.toHaveBeenCalled(); }); it('should render the onboarding panel when the org is on AM1 and has sent some replays', async () => { const mockOrg = getMockOrganizationFixture({features: AM1_FEATURES}); mockUseHaveSelectedProjectsSentAnyReplayEvents.mockReturnValue({ fetching: false, hasSentOneReplay: true, }); mockUseProjectSdkNeedsUpdate.mockReturnValue({ isError: false, isFetching: false, needsUpdate: false, }); render(, { context: getMockContext(mockOrg), }); await waitFor(() => expect(screen.getByText('Get to the root cause faster')).toBeInTheDocument() ); expect(mockFetchReplayListRequest).not.toHaveBeenCalled(); }); it('should render the onboarding panel when the org is on AM2 and has never sent a replay', async () => { const mockOrg = getMockOrganizationFixture({features: AM2_FEATURES}); mockUseHaveSelectedProjectsSentAnyReplayEvents.mockReturnValue({ fetching: false, hasSentOneReplay: false, }); mockUseProjectSdkNeedsUpdate.mockReturnValue({ isError: false, isFetching: false, needsUpdate: false, }); render(, { context: getMockContext(mockOrg), }); await waitFor(() => expect(screen.getByText('Get to the root cause faster')).toBeInTheDocument() ); expect(mockFetchReplayListRequest).not.toHaveBeenCalled(); }); it('should render the rage-click sdk update banner when the org is AM2, has sent replays, but the sdk version is low', async () => { const mockOrg = getMockOrganizationFixture({features: AM2_FEATURES}); mockUseHaveSelectedProjectsSentAnyReplayEvents.mockReturnValue({ fetching: false, hasSentOneReplay: true, }); mockUseProjectSdkNeedsUpdate.mockReturnValue({ isError: false, isFetching: false, needsUpdate: true, }); render(, { context: getMockContext(mockOrg), }); await waitFor(() => { expect(screen.queryByText('Introducing Rage and Dead Clicks')).toBeInTheDocument(); expect(screen.queryByTestId('replay-table')).toBeInTheDocument(); }); expect(mockFetchReplayListRequest).toHaveBeenCalled(); }); it('should fetch the replay table and show selector tables when the org is on AM2, has sent some replays, and has a newer SDK version', async () => { const mockOrg = getMockOrganizationFixture({features: AM2_FEATURES}); mockUseHaveSelectedProjectsSentAnyReplayEvents.mockReturnValue({ fetching: false, hasSentOneReplay: true, }); mockUseProjectSdkNeedsUpdate.mockReturnValue({ isError: false, isFetching: false, needsUpdate: false, }); mockUseDeadRageSelectors.mockReturnValue({ isLoading: false, isError: false, data: [], pageLinks: undefined, }); render(, { context: getMockContext(mockOrg), }); await waitFor(() => expect(screen.queryAllByTestId('replay-table')).toHaveLength(1)); await waitFor(() => expect(screen.queryAllByTestId('selector-widget')).toHaveLength(2) ); expect(mockFetchReplayListRequest).toHaveBeenCalled(); }); });