11import React from 'react' ;
22import { fireEvent , render , screen , waitFor } from '@testing-library/react' ;
33import { HighlightCardOptions } from './HighlightCardOptions' ;
4+ import type { MenuItemProps } from '../../dropdown/common' ;
5+ import {
6+ HighlightsPlacement ,
7+ SidebarSettingsFlags ,
8+ } from '../../../graphql/settings' ;
49
510const mockSubscribe = jest . fn ( ) . mockResolvedValue ( undefined ) ;
611const mockUnsubscribe = jest . fn ( ) . mockResolvedValue ( undefined ) ;
@@ -9,15 +14,37 @@ const mockUseAuth = jest.fn();
914const mockUseConditionalFeature = jest . fn ( ) ;
1015const mockUseMajorHeadlinesSubscription = jest . fn ( ) ;
1116const mockRouterPush = jest . fn ( ) ;
17+ const mockUpdateFlag = jest . fn ( ) . mockResolvedValue ( undefined ) ;
18+ const mockUseSettingsContext = jest . fn ( ) ;
19+ const mockLogEvent = jest . fn ( ) ;
20+ const mockInvalidateQueries = jest . fn ( ) . mockResolvedValue ( undefined ) ;
21+ const mockUseActiveFeedContext = jest . fn ( ) ;
1222
1323jest . mock ( 'next/router' , ( ) => ( {
1424 useRouter : ( ) => ( { push : mockRouterPush } ) ,
1525} ) ) ;
1626
27+ jest . mock ( '@tanstack/react-query' , ( ) => ( {
28+ ...( jest . requireActual ( '@tanstack/react-query' ) as Iterable < unknown > ) ,
29+ useQueryClient : ( ) => ( { invalidateQueries : mockInvalidateQueries } ) ,
30+ } ) ) ;
31+
32+ jest . mock ( '../../../contexts/ActiveFeedContext' , ( ) => ( {
33+ useActiveFeedContext : ( ) => mockUseActiveFeedContext ( ) ,
34+ } ) ) ;
35+
1736jest . mock ( '../../../contexts/AuthContext' , ( ) => ( {
1837 useAuthContext : ( ) => mockUseAuth ( ) ,
1938} ) ) ;
2039
40+ jest . mock ( '../../../contexts/SettingsContext' , ( ) => ( {
41+ useSettingsContext : ( ) => mockUseSettingsContext ( ) ,
42+ } ) ) ;
43+
44+ jest . mock ( '../../../contexts/LogContext' , ( ) => ( {
45+ useLogContext : ( ) => ( { logEvent : mockLogEvent } ) ,
46+ } ) ) ;
47+
2148jest . mock ( '../../../hooks/useConditionalFeature' , ( ) => ( {
2249 useConditionalFeature : ( ) => mockUseConditionalFeature ( ) ,
2350} ) ) ;
@@ -30,8 +57,24 @@ jest.mock('../../../hooks/useToastNotification', () => ({
3057 useToastNotification : ( ) => ( { displayToast : mockDisplayToast } ) ,
3158} ) ) ;
3259
33- jest . mock ( '../../tooltip/Tooltip' , ( ) => ( {
34- Tooltip : ( { children } : { children : React . ReactNode } ) => < > { children } </ > ,
60+ jest . mock ( '../../dropdown/DropdownMenu' , ( ) => ( {
61+ DropdownMenu : ( { children } : { children : React . ReactNode } ) => (
62+ < div > { children } </ div >
63+ ) ,
64+ DropdownMenuTrigger : ( { children } : { children : React . ReactNode } ) =>
65+ children ,
66+ DropdownMenuContent : ( { children } : { children : React . ReactNode } ) => (
67+ < div > { children } </ div >
68+ ) ,
69+ DropdownMenuOptions : ( { options } : { options : MenuItemProps [ ] } ) => (
70+ < div >
71+ { options . map ( ( { label, action, disabled } ) => (
72+ < button key = { label } type = "button" onClick = { action } disabled = { disabled } >
73+ { label }
74+ </ button >
75+ ) ) }
76+ </ div >
77+ ) ,
3578} ) ) ;
3679
3780const renderComponent = ( ) => render ( < HighlightCardOptions /> ) ;
@@ -47,11 +90,23 @@ describe('HighlightCardOptions', () => {
4790 subscribe : mockSubscribe ,
4891 unsubscribe : mockUnsubscribe ,
4992 } ) ;
93+ mockUseSettingsContext . mockReturnValue ( {
94+ flags : { highlightsPlacement : HighlightsPlacement . Default } ,
95+ updateFlag : mockUpdateFlag ,
96+ } ) ;
97+ mockUseActiveFeedContext . mockReturnValue ( {
98+ queryKey : [ 'feed' , 'main' ] ,
99+ items : [ ] ,
100+ } ) ;
50101 } ) ;
51102
52- it ( 'should render bell button when feature is on and user is logged in' , ( ) => {
103+ it ( 'should render the options menu with all items when feature is on and user is logged in' , ( ) => {
53104 renderComponent ( ) ;
54105
106+ expect (
107+ screen . getByRole ( 'button' , { name : 'Pin to top' } ) ,
108+ ) . toBeInTheDocument ( ) ;
109+ expect ( screen . getByRole ( 'button' , { name : 'Disable' } ) ) . toBeInTheDocument ( ) ;
55110 expect (
56111 screen . getByRole ( 'button' , { name : 'Get real-time alerts' } ) ,
57112 ) . toBeInTheDocument ( ) ;
@@ -77,6 +132,71 @@ describe('HighlightCardOptions', () => {
77132 ) . not . toBeInTheDocument ( ) ;
78133 } ) ;
79134
135+ it ( 'should pin to top by updating the placement flag and invalidating the feed' , async ( ) => {
136+ renderComponent ( ) ;
137+
138+ fireEvent . click ( screen . getByRole ( 'button' , { name : 'Pin to top' } ) ) ;
139+
140+ await waitFor ( ( ) => {
141+ expect ( mockUpdateFlag ) . toHaveBeenCalledWith (
142+ SidebarSettingsFlags . Highlights ,
143+ HighlightsPlacement . Pinned ,
144+ ) ;
145+ } ) ;
146+ await waitFor ( ( ) => {
147+ expect ( mockInvalidateQueries ) . toHaveBeenCalledWith ( {
148+ queryKey : [ 'feed' , 'main' ] ,
149+ } ) ;
150+ } ) ;
151+ expect ( mockDisplayToast ) . toHaveBeenCalledWith (
152+ 'Happening Now placement preference applied to all your feeds' ,
153+ ) ;
154+ } ) ;
155+
156+ it ( 'should skip feed invalidation when no active feed query key is set' , async ( ) => {
157+ mockUseActiveFeedContext . mockReturnValue ( { items : [ ] } ) ;
158+
159+ renderComponent ( ) ;
160+
161+ fireEvent . click ( screen . getByRole ( 'button' , { name : 'Pin to top' } ) ) ;
162+
163+ await waitFor ( ( ) => {
164+ expect ( mockUpdateFlag ) . toHaveBeenCalled ( ) ;
165+ } ) ;
166+ expect ( mockInvalidateQueries ) . not . toHaveBeenCalled ( ) ;
167+ } ) ;
168+
169+ it ( 'should unpin by setting placement back to Default' , async ( ) => {
170+ mockUseSettingsContext . mockReturnValue ( {
171+ flags : { highlightsPlacement : HighlightsPlacement . Pinned } ,
172+ updateFlag : mockUpdateFlag ,
173+ } ) ;
174+
175+ renderComponent ( ) ;
176+
177+ fireEvent . click ( screen . getByRole ( 'button' , { name : 'Unpin from top' } ) ) ;
178+
179+ await waitFor ( ( ) => {
180+ expect ( mockUpdateFlag ) . toHaveBeenCalledWith (
181+ SidebarSettingsFlags . Highlights ,
182+ HighlightsPlacement . Default ,
183+ ) ;
184+ } ) ;
185+ } ) ;
186+
187+ it ( 'should disable the card by setting placement to Disabled' , async ( ) => {
188+ renderComponent ( ) ;
189+
190+ fireEvent . click ( screen . getByRole ( 'button' , { name : 'Disable' } ) ) ;
191+
192+ await waitFor ( ( ) => {
193+ expect ( mockUpdateFlag ) . toHaveBeenCalledWith (
194+ SidebarSettingsFlags . Highlights ,
195+ HighlightsPlacement . Disabled ,
196+ ) ;
197+ } ) ;
198+ } ) ;
199+
80200 it ( 'should subscribe and show toast with settings action when not subscribed' , async ( ) => {
81201 renderComponent ( ) ;
82202
0 commit comments