Skip to content

Commit

Permalink
fix(core): update useSeenAnnouncements to handle state reset
Browse files Browse the repository at this point in the history
  • Loading branch information
pedrobonamin committed Sep 17, 2024
1 parent e570c20 commit 4d740f2
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 114 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export function StudioAnnouncementsProvider({children}: StudioAnnouncementsProvi
}
return unseen
}, [seenAnnouncements, studioAnnouncements, telemetry])

useEffect(() => {
// TODO: Replace for internal api
const client = createClient({projectId: '3do82whm', dataset: 'next'})
Expand Down Expand Up @@ -86,8 +87,8 @@ export function StudioAnnouncementsProvider({children}: StudioAnnouncementsProvi
saveSeenAnnouncements()
setIsCardDismissed(true)
telemetry.log(ProductAnnouncementCardDismissed, {
announcement_id: unseenAnnouncements[0]._id,
announcement_title: unseenAnnouncements[0].title,
announcement_id: unseenAnnouncements[0]?._id,
announcement_title: unseenAnnouncements[0]?.title,
source: 'studio',
studio_version: SANITY_VERSION,
})
Expand All @@ -96,25 +97,28 @@ export function StudioAnnouncementsProvider({children}: StudioAnnouncementsProvi
const handleCardClick = useCallback(() => {
handleOpenDialog('card')
telemetry.log(ProductAnnouncementCardClicked, {
announcement_id: unseenAnnouncements[0]._id,
announcement_title: unseenAnnouncements[0].title,
announcement_id: unseenAnnouncements[0]?._id,
announcement_title: unseenAnnouncements[0]?.title,
source: 'studio',
studio_version: SANITY_VERSION,
})
}, [handleOpenDialog, telemetry, unseenAnnouncements])

const handleDialogClose = useCallback(() => {
const firstAnnouncement =
dialogMode === 'help_menu' ? studioAnnouncements[0] : unseenAnnouncements[0]

telemetry.log(ProductAnnouncementModalDismissed, {
announcement_id: unseenAnnouncements[0]._id,
announcement_title: unseenAnnouncements[0].title,
announcement_id: firstAnnouncement?._id,
announcement_title: firstAnnouncement?.title,
source: 'studio',
studio_version: SANITY_VERSION,
origin: dialogMode ?? 'card',
})

setDialogMode(null)
saveSeenAnnouncements()
}, [saveSeenAnnouncements, telemetry, unseenAnnouncements, dialogMode])
}, [dialogMode, studioAnnouncements, unseenAnnouncements, telemetry, saveSeenAnnouncements])

const contextValue: StudioAnnouncementsContextValue = useMemo(
() => ({
Expand All @@ -129,26 +133,20 @@ export function StudioAnnouncementsProvider({children}: StudioAnnouncementsProvi
<StudioAnnouncementContext.Provider value={contextValue}>
{children}
{unseenAnnouncements.length > 0 && (
<>
{!isCardDismissed && (
<StudioAnnouncementsCard
title={unseenAnnouncements[0].title}
announcementType={unseenAnnouncements[0].announcementType}
onCardClick={handleCardClick}
isOpen={!isCardDismissed}
onCardDismiss={handleCardDismiss}
/>
)}
{dialogMode && (
<StudioAnnouncementsDialog
mode={dialogMode}
unseenDocuments={
dialogMode === 'help_menu' ? studioAnnouncements : unseenAnnouncements
}
onClose={handleDialogClose}
/>
)}
</>
<StudioAnnouncementsCard
title={unseenAnnouncements[0].title}
announcementType={unseenAnnouncements[0].announcementType}
onCardClick={handleCardClick}
isOpen={!isCardDismissed}
onCardDismiss={handleCardDismiss}
/>
)}
{dialogMode && (
<StudioAnnouncementsDialog
mode={dialogMode}
unseenDocuments={dialogMode === 'help_menu' ? studioAnnouncements : unseenAnnouncements}
onClose={handleDialogClose}
/>
)}
</StudioAnnouncementContext.Provider>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable camelcase */
import {beforeAll, beforeEach, describe, expect, jest, test} from '@jest/globals'
import {fireEvent, render, renderHook} from '@testing-library/react'
import {fireEvent, render, renderHook, waitFor} from '@testing-library/react'
import {type ReactNode} from 'react'
import {defineConfig} from 'sanity'

Expand Down Expand Up @@ -178,7 +178,7 @@ describe('StudioAnnouncementsProvider', () => {
},
})
})
test('clicks on card, it opens dialog and card is hidden, shows only the unseen announcements', () => {
test('clicks on card, it opens dialog and card is hidden, shows only the unseen announcements', async () => {
const mockLog = jest.fn()
const {useTelemetry} = require('@sanity/telemetry/react')
useTelemetry.mockReturnValue({log: mockLog})
Expand All @@ -190,7 +190,9 @@ describe('StudioAnnouncementsProvider', () => {
const cardButton = getByLabelText('Open announcements')
fireEvent.click(cardButton)

expect(queryByText("What's new")).toBeNull()
await waitFor(() => {
expect(queryByText("What's new")).toBeNull()
})
// The first announcement is seen, so it's not rendered
expect(queryByText(mockAnnouncements[0].title)).toBeNull()
// The second announcement is unseen, so it's rendered
Expand Down Expand Up @@ -230,7 +232,7 @@ describe('StudioAnnouncementsProvider', () => {
)
})

test("dismisses card, then it's hidden, dialog doesn't render", () => {
test("dismisses card, then it's hidden, dialog doesn't render", async () => {
const mockLog = jest.fn()
const {useTelemetry} = require('@sanity/telemetry/react')
useTelemetry.mockReturnValue({log: mockLog})
Expand All @@ -241,7 +243,9 @@ describe('StudioAnnouncementsProvider', () => {
expect(queryByText(mockAnnouncements[1].title)).toBeInTheDocument()
const closeButton = getByLabelText('Dismiss announcements')
fireEvent.click(closeButton)
expect(queryByText("What's new")).toBeNull()
await waitFor(() => {
expect(queryByText("What's new")).toBeNull()
})
expect(queryByText(mockAnnouncements[1].title)).toBeNull()

// Dismissing the card calls telemetry with the seen and dismiss logs
Expand Down Expand Up @@ -278,7 +282,7 @@ describe('StudioAnnouncementsProvider', () => {
)
})

test('dismisses dialog, card and dialog are hidden', () => {
test('dismisses dialog, card and dialog are hidden', async () => {
const mockLog = jest.fn()
const {useTelemetry} = require('@sanity/telemetry/react')
useTelemetry.mockReturnValue({log: mockLog})
Expand All @@ -289,7 +293,9 @@ describe('StudioAnnouncementsProvider', () => {
expect(queryByText(mockAnnouncements[1].title)).toBeInTheDocument()
const cardButton = getByLabelText('Open announcements')
fireEvent.click(cardButton)
expect(queryByText("What's new")).toBeNull()
await waitFor(() => {
expect(queryByText("What's new")).toBeNull()
})
expect(queryByText(mockAnnouncements[1].title)).toBeInTheDocument()

const closeButton = getByLabelText('Close dialog')
Expand Down Expand Up @@ -345,7 +351,7 @@ describe('StudioAnnouncementsProvider', () => {
},
)
})
test('opens the dialog from outside the card, so it shows all unseen', () => {
test('opens the dialog from outside the card, so it shows all unseen', async () => {
const Component = () => {
const {onDialogOpen} = useStudioAnnouncements()
return (
Expand All @@ -365,7 +371,9 @@ describe('StudioAnnouncementsProvider', () => {
fireEvent.click(openDialogButton)

// The card closes even if we open it from somewhere else
expect(queryByText("What's new")).toBeNull()
await waitFor(() => {
expect(queryByText("What's new")).toBeNull()
})
// The first announcement is seen, it's rendered because it's showing all
expect(queryByText(mockAnnouncements[0].title)).toBeInTheDocument()
// The second announcement is unseen, so it's rendered
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {beforeEach, describe, expect, jest, test} from '@jest/globals'
import {act, renderHook, waitFor} from '@testing-library/react'
import {of, Subject} from 'rxjs'
import {useRouter} from 'sanity/router'

import {useKeyValueStore} from '../../../store/_legacy/datastores'
import {useSeenAnnouncements} from '../useSeenAnnouncements'
Expand All @@ -10,6 +11,10 @@ jest.mock('../../../store/_legacy/datastores', () => ({
}))

const useKeyValueStoreMock = useKeyValueStore as jest.Mock
jest.mock('sanity/router', () => ({
useRouter: jest.fn().mockReturnValue({state: {}}),
}))
const useRouterMock = useRouter as jest.Mock

describe('useSeenAnnouncements', () => {
beforeEach(() => {
Expand Down Expand Up @@ -64,4 +69,52 @@ describe('useSeenAnnouncements', () => {

expect(setKeyMock).toHaveBeenCalledWith('studio.announcement.seen', newSeenAnnouncements)
})
describe('should reset states when the param is provided', () => {
test('when a reset value is provided', async () => {
useRouterMock.mockReturnValue({
state: {_searchParams: [['reset-announcements', 'foo,bar']]},
})
const getKeyMock = jest.fn().mockImplementation(() => of([]))
const setKeyMock = jest.fn().mockReturnValue(of([]))

useKeyValueStoreMock.mockReturnValue({getKey: getKeyMock, setKey: setKeyMock})
renderHook(() => useSeenAnnouncements())

// Call the setSeenAnnouncements function
await waitFor(() => {
expect(setKeyMock).toHaveBeenCalledWith('studio.announcement.seen', ['foo', 'bar'])
})
})
test('when no reset value is provided', async () => {
useRouterMock.mockReturnValue({
state: {_searchParams: [['reset-announcements', '']]},
})
const getKeyMock = jest.fn().mockImplementation(() => of([]))
const setKeyMock = jest.fn().mockReturnValue(of([]))

useKeyValueStoreMock.mockReturnValue({getKey: getKeyMock, setKey: setKeyMock})
renderHook(() => useSeenAnnouncements())

// Call the setSeenAnnouncements function
await waitFor(() => {
expect(setKeyMock).toHaveBeenCalledWith('studio.announcement.seen', [])
})
})

test('when the key is not provided', async () => {
useRouterMock.mockReturnValue({
state: {_searchParams: []},
})
const getKeyMock = jest.fn().mockImplementation(() => of([]))
const setKeyMock = jest.fn().mockReturnValue(of([]))

useKeyValueStoreMock.mockReturnValue({getKey: getKeyMock, setKey: setKeyMock})
renderHook(() => useSeenAnnouncements())

// Call the setSeenAnnouncements function
await waitFor(() => {
expect(setKeyMock).not.toHaveBeenCalled()
})
})
})
})
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import {useCallback, useMemo} from 'react'
import {useCallback, useEffect, useMemo} from 'react'
import {useObservable} from 'react-rx'
import {type Observable} from 'rxjs'
import {useRouter} from 'sanity/router'

import {useKeyValueStore} from '../../store/_legacy/datastores'

const KEY = 'studio.announcement.seen'
const RESET_PARAM = 'reset-announcements'

/**
* TODO: This is not functional yet, the API is not accepting the new key
*/
export function useSeenAnnouncements(): [string[] | null | 'loading', (seen: string[]) => void] {
const router = useRouter()
// Handles the communication with the key value store
const keyValueStore = useKeyValueStore()
const seenAnnouncements$ = useMemo(
Expand All @@ -20,11 +20,22 @@ export function useSeenAnnouncements(): [string[] | null | 'loading', (seen: str

const setSeenAnnouncements = useCallback(
(seen: string[]) => {
// eslint-disable-next-line no-console
keyValueStore.setKey(KEY, seen)
},
[keyValueStore],
)
const params = new URLSearchParams(router.state._searchParams)
const resetAnnouncementsParams = params?.get(RESET_PARAM)

useEffect(() => {
// For testing purposes, reset the seen params.
// e.g. /structure?reset-announcements=foo,bar
// Will reset the values of the seen announcement to: ['foo', 'bar']
if (resetAnnouncementsParams !== null) {
const resetValue = resetAnnouncementsParams ? resetAnnouncementsParams.split(',') : []
setSeenAnnouncements(resetValue)
}
}, [resetAnnouncementsParams, setSeenAnnouncements])

return [seenAnnouncements, setSeenAnnouncements]
}

0 comments on commit 4d740f2

Please sign in to comment.