import moment from 'moment'; import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary'; import {InnerGlobalSdkUpdateAlert} from 'sentry/components/globalSdkUpdateAlert'; import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters'; import {PageFilters, ProjectSdkUpdates} from 'sentry/types'; import {DEFAULT_SNOOZE_PROMPT_DAYS} from 'sentry/utils/promptIsDismissed'; import importedUsePageFilters from 'sentry/utils/usePageFilters'; jest.mock('sentry/utils/usePageFilters'); const usePageFilters = importedUsePageFilters as jest.MockedFunction< typeof importedUsePageFilters >; const makeFilterProps = ( filters: Partial ): ReturnType => { return { isReady: true, desyncedFilters: new Set(), pinnedFilters: new Set(), selection: { projects: [1], environments: ['prod'], datetime: {start: new Date(), end: new Date(), period: '14d', utc: true}, ...filters, }, }; }; const makeSdkUpdateProps = ( sdkUpdateProps: Partial ): ProjectSdkUpdates[] => { return [ { projectId: String(1), sdkName: 'sentry-javascript', sdkVersion: '1.0.0.', suggestions: [ { enables: [], newSdkVersion: '1.1.0', sdkName: 'sentry-javascript', type: 'updateSdk', }, ], ...sdkUpdateProps, }, ]; }; describe('GlobalSDKUpdateAlert', () => { beforeEach(() => { jest.clearAllMocks(); MockApiClient.clearMockResponses(); usePageFilters.mockClear(); }); it('does not shows prompt if projects do not match', async () => { // We have matching projectId, so updates should be show usePageFilters.mockImplementation(() => makeFilterProps({projects: [1]})); const sdkUpdates = makeSdkUpdateProps({projectId: String(1)}); const promptResponse = { dismissed_ts: undefined, snoozed_ts: undefined, }; MockApiClient.addMockResponse({ url: '/prompts-activity/', body: promptResponse, }); const {rerender} = render(, { organization: TestStubs.Organization(), }); expect( await screen.findByText(/You have outdated SDKs in your projects/) ).toBeInTheDocument(); usePageFilters.mockImplementation(() => makeFilterProps({projects: [2]})); // ProjectId no longer matches, so updates should not be shown anymore rerender(); expect( screen.queryByText(/You have outdated SDKs in your projects/) ).not.toBeInTheDocument(); }); it('shows prompt if it has never been dismissed', async () => { usePageFilters.mockImplementation(() => makeFilterProps({projects: [0]})); const sdkUpdates = makeSdkUpdateProps({projectId: String(0)}); const promptResponse = { dismissed_ts: undefined, snoozed_ts: undefined, }; MockApiClient.addMockResponse({ url: '/prompts-activity/', body: {data: promptResponse}, }); render(, { organization: TestStubs.Organization(), }); expect( await screen.findByText(/You have outdated SDKs in your projects/) ).toBeInTheDocument(); }); it('never shows prompt if it has been dismissed', async () => { usePageFilters.mockImplementation(() => makeFilterProps({projects: [0]})); const sdkUpdates = makeSdkUpdateProps({projectId: String(0)}); const promptResponse = { dismissed_ts: moment .utc() .subtract(DEFAULT_SNOOZE_PROMPT_DAYS - 5, 'days') .unix(), snoozed_ts: undefined, }; MockApiClient.addMockResponse({ url: '/prompts-activity/', body: {data: promptResponse}, }); render(, { organization: TestStubs.Organization(), }); await waitFor(() => expect( screen.queryByText(/You have outdated SDKs in your projects/) ).not.toBeInTheDocument() ); }); it('shows prompt if snoozed_ts days is longer than threshold', async () => { usePageFilters.mockImplementation(() => makeFilterProps({projects: [0]})); const sdkUpdates = makeSdkUpdateProps({projectId: String(0)}); const promptResponse = { dismissed_ts: undefined, snoozed_ts: moment .utc() .subtract(DEFAULT_SNOOZE_PROMPT_DAYS + 1, 'days') .unix(), }; MockApiClient.addMockResponse({ url: '/prompts-activity/', body: {data: promptResponse}, }); render(, { organization: TestStubs.Organization(), }); expect( await screen.findByText(/You have outdated SDKs in your projects/) ).toBeInTheDocument(); }); it('shows prompt if snoozed_ts is shorter than threshold', async () => { usePageFilters.mockImplementation(() => makeFilterProps({projects: [0]})); const sdkUpdates = makeSdkUpdateProps({projectId: String(0)}); const promptResponse = { dismissed_ts: undefined, snoozed_ts: moment .utc() .subtract(DEFAULT_SNOOZE_PROMPT_DAYS - 2, 'days') .unix(), }; MockApiClient.addMockResponse({ url: '/prompts-activity/', body: {data: promptResponse}, }); render(, { organization: TestStubs.Organization(), }); await waitFor(() => expect( screen.queryByText(/You have outdated SDKs in your projects/) ).not.toBeInTheDocument() ); }); it('shows prompt for all projects when project matches ALL_ACCESS_PROJECTS', async () => { // We intentionally missmatch ALL_ACCESS_PROJECTS with projectId in sdkUpdates usePageFilters.mockImplementation(() => makeFilterProps({projects: [ALL_ACCESS_PROJECTS]}) ); const sdkUpdates = makeSdkUpdateProps({projectId: String(0)}); const promptResponse = { dismissed_ts: undefined, snoozed_ts: undefined, }; MockApiClient.addMockResponse({ url: '/prompts-activity/', body: promptResponse, }); render(, { organization: TestStubs.Organization(), }); expect( await screen.findByText(/You have outdated SDKs in your projects/) ).toBeInTheDocument(); }); it('dimisses prompt', async () => { usePageFilters.mockImplementation(() => makeFilterProps({projects: [0]})); const sdkUpdates = makeSdkUpdateProps({projectId: String(0)}); const promptResponse = { dismissed_ts: undefined, snoozed_ts: undefined, }; const promptsActivityMock = MockApiClient.addMockResponse({ url: '/prompts-activity/', body: {data: promptResponse}, }); render(, { organization: TestStubs.Organization(), }); userEvent.click(await screen.findByText(/Remind me later/)); expect(promptsActivityMock).toHaveBeenCalledWith( '/prompts-activity/', expect.objectContaining({ query: expect.objectContaining({ feature: 'sdk_updates', organization_id: '3', }), }) ); expect( screen.queryByText(/You have outdated SDKs in your projects/) ).not.toBeInTheDocument(); }); });