11import { fireEvent , render , screen } from '@testing-library/react' ;
2+ import '@testing-library/jest-dom' ;
23import React from 'react' ;
34
45import { SearchResultsHeader } from '../SearchResults' ;
@@ -11,16 +12,33 @@ jest.mock('../../../context');
1112jest . mock ( '../../../store' ) ;
1213
1314describe ( 'SearchResultsHeader' , ( ) => {
15+ const mockSources = {
16+ channels : { items : [ ] , search : jest . fn ( ) , state : { } , type : 'channels' } ,
17+ messages : { items : [ 'message1' ] , search : jest . fn ( ) , state : { } , type : 'messages' } ,
18+ users : { items : [ ] , search : jest . fn ( ) , state : { } , type : 'users' } ,
19+ } ;
20+
1421 const mockSearchController = {
1522 activateSource : jest . fn ( ) ,
1623 deactivateSource : jest . fn ( ) ,
17- searchSourceTypes : [ 'users' , 'channels' , 'messages' ] ,
18- state : { } ,
24+ searchQuery : 'test query' ,
25+ get sources ( ) {
26+ return Object . entries ( mockSources ) . map ( ( [ type , source ] ) => ( {
27+ type,
28+ ...source ,
29+ } ) ) ;
30+ } ,
1931 } ;
2032
2133 beforeEach ( ( ) => {
2234 jest . clearAllMocks ( ) ;
2335
36+ // Reset mock sources
37+ Object . values ( mockSources ) . forEach ( ( source ) => {
38+ source . items = source . type === 'messages' ? [ 'message1' ] : [ ] ;
39+ source . search . mockClear ( ) ;
40+ } ) ;
41+
2442 useSearchContext . mockReturnValue ( {
2543 searchController : mockSearchController ,
2644 } ) ;
@@ -29,100 +47,114 @@ describe('SearchResultsHeader', () => {
2947 t : ( key ) => key ,
3048 } ) ;
3149
32- useStateStore . mockReturnValue ( {
33- activeSourceTypes : [ 'users' , 'messages' ] ,
34- } ) ;
35- } ) ;
36-
37- it ( 'renders filter source buttons for each source type' , ( ) => {
38- render ( < SearchResultsHeader /> ) ;
39- expect (
40- screen . getAllByRole ( 'button' , {
41- name : 'aria/Search results header filter button' ,
42- } ) ,
43- ) . toHaveLength ( 3 ) ;
50+ useStateStore . mockReturnValue ( { isActive : false } ) ;
4451 } ) ;
4552
46- it ( 'applies active class to active source type buttons' , ( ) => {
47- render ( < SearchResultsHeader /> ) ;
48-
49- const buttons = screen . getAllByRole ( 'button' ) ;
50-
51- buttons . forEach ( ( button ) => {
52- const buttonClasses = button . className ;
53- if (
54- button . textContent === 'search-results-header-filter-source-button-label--users' ||
55- button . textContent === 'search-results-header-filter-source-button-label--messages'
56- ) {
57- expect ( buttonClasses ) . toContain (
58- 'str-chat__search-results-header__filter-source-button--active' ,
59- ) ;
60- } else {
61- expect ( buttonClasses ) . not . toContain (
62- 'str-chat__search-results-header__filter-source-button--active' ,
63- ) ;
64- }
53+ describe ( 'rendering' , ( ) => {
54+ it ( 'renders container with correct classes and structure' , ( ) => {
55+ render ( < SearchResultsHeader /> ) ;
56+ expect ( screen . getByTestId ( 'search-results-header' ) ) . toHaveClass (
57+ 'str-chat__search-results-header' ,
58+ ) ;
59+ expect ( screen . getByTestId ( 'filter-source-buttons' ) ) . toHaveClass (
60+ 'str-chat__search-results-header__filter-source-buttons' ,
61+ ) ;
6562 } ) ;
66- } ) ;
6763
68- it ( 'deactivates source when clicking active source button' , ( ) => {
69- render ( < SearchResultsHeader /> ) ;
70-
71- const usersButton = screen . getByText ( 'search-results-header-filter-source-button-label--users' ) ;
72- fireEvent . click ( usersButton ) ;
64+ it ( 'renders a button for each source type' , ( ) => {
65+ render ( < SearchResultsHeader /> ) ;
66+ const buttons = screen . getAllByRole ( 'button' ) ;
67+ expect ( buttons ) . toHaveLength ( 3 ) ;
68+
69+ expect (
70+ screen . getByText ( 'search-results-header-filter-source-button-label--channels' ) ,
71+ ) . toBeInTheDocument ( ) ;
72+ expect (
73+ screen . getByText ( 'search-results-header-filter-source-button-label--messages' ) ,
74+ ) . toBeInTheDocument ( ) ;
75+ expect (
76+ screen . getByText ( 'search-results-header-filter-source-button-label--users' ) ,
77+ ) . toBeInTheDocument ( ) ;
78+ } ) ;
7379
74- expect ( mockSearchController . deactivateSource ) . toHaveBeenCalledWith ( 'users' ) ;
75- expect ( mockSearchController . activateSource ) . not . toHaveBeenCalled ( ) ;
80+ it ( 'applies correct aria-labels to all buttons' , ( ) => {
81+ render ( < SearchResultsHeader /> ) ;
82+ const buttons = screen . getAllByRole ( 'button' ) ;
83+ buttons . forEach ( ( button ) => {
84+ expect ( button ) . toHaveAttribute ( 'aria-label' , 'aria/Search results header filter button' ) ;
85+ } ) ;
86+ } ) ;
7687 } ) ;
7788
78- it ( 'activates source when clicking inactive source button' , ( ) => {
79- render ( < SearchResultsHeader /> ) ;
89+ describe ( 'button states and styling' , ( ) => {
90+ it ( 'applies active class to button when source is active' , ( ) => {
91+ useStateStore . mockReturnValue ( { isActive : true } ) ;
92+ render ( < SearchResultsHeader /> ) ;
8093
81- const channelsButton = screen . getByText (
82- 'search-results-header-filter-source-button-label--channels' ,
83- ) ;
84- fireEvent . click ( channelsButton ) ;
94+ const button = screen . getByText ( 'search-results-header-filter-source-button-label--messages' ) ;
95+ expect ( button ) . toHaveClass ( 'str-chat__search-results-header__filter-source-button--active' ) ;
96+ } ) ;
97+
98+ it ( 'does not apply active class when source is inactive' , ( ) => {
99+ useStateStore . mockReturnValue ( { isActive : false } ) ;
100+ render ( < SearchResultsHeader /> ) ;
85101
86- expect ( mockSearchController . activateSource ) . toHaveBeenCalledWith ( 'channels' ) ;
87- expect ( mockSearchController . deactivateSource ) . not . toHaveBeenCalled ( ) ;
102+ const button = screen . getByText ( 'search-results-header-filter-source-button-label--messages' ) ;
103+ expect ( button ) . not . toHaveClass (
104+ 'str-chat__search-results-header__filter-source-button--active' ,
105+ ) ;
106+ } ) ;
88107 } ) ;
89108
90- it ( 'translates button labels correctly' , ( ) => {
91- const mockTranslate = jest . fn ( ( key ) => `Translated ${ key } ` ) ;
92- useTranslationContext . mockReturnValue ( { t : mockTranslate } ) ;
109+ describe ( 'button interactions' , ( ) => {
110+ it ( 'deactivates source when clicking active source button' , ( ) => {
111+ Object . values ( mockSources ) . forEach ( ( source ) => {
112+ if ( source . type !== 'messages' ) return ;
113+ source . isActive = true ;
114+ } ) ;
115+ render ( < SearchResultsHeader /> ) ;
116+
117+ fireEvent . click (
118+ screen . getByText ( 'search-results-header-filter-source-button-label--messages' ) ,
119+ ) ;
120+ expect ( mockSearchController . deactivateSource ) . toHaveBeenCalledWith ( 'messages' ) ;
121+ expect ( mockSearchController . activateSource ) . not . toHaveBeenCalled ( ) ;
93122
94- render ( < SearchResultsHeader /> ) ;
123+ Object . values ( mockSources ) . forEach ( ( source ) => {
124+ if ( source . type !== 'messages' ) return ;
125+ source . isActive = undefined ;
126+ } ) ;
127+ } ) ;
95128
96- expect ( mockTranslate ) . toHaveBeenCalledWith ( 'aria/Search results header filter button' ) ;
97- mockSearchController . searchSourceTypes . forEach ( ( sourceType ) => {
98- expect ( mockTranslate ) . toHaveBeenCalledWith (
99- ` search-results-header-filter-source-button-label--${ sourceType } ` ,
129+ it ( 'activates and searches source with no items' , ( ) => {
130+ render ( < SearchResultsHeader /> ) ;
131+ fireEvent . click (
132+ screen . getByText ( ' search-results-header-filter-source-button-label--channels' ) ,
100133 ) ;
134+
135+ expect ( mockSearchController . activateSource ) . toHaveBeenCalledWith ( 'channels' ) ;
136+ expect ( mockSources . channels . search ) . toHaveBeenCalledWith ( 'test query' ) ;
101137 } ) ;
102- } ) ;
103138
104- it ( 'handles state updates correctly' , ( ) => {
105- const { rerender } = render ( < SearchResultsHeader /> ) ;
139+ it ( 'only performs search upon activation if it does not have items loaded' , ( ) => {
140+ render ( < SearchResultsHeader /> ) ;
141+ fireEvent . click (
142+ screen . getByText ( 'search-results-header-filter-source-button-label--messages' ) ,
143+ ) ;
106144
107- // Update active sources
108- useStateStore . mockReturnValue ( {
109- activeSourceTypes : [ 'channels' ] ,
145+ expect ( mockSearchController . activateSource ) . toHaveBeenCalledWith ( 'messages' ) ;
146+ expect ( mockSources . messages . search ) . not . toHaveBeenCalled ( ) ;
110147 } ) ;
111148
112- rerender ( < SearchResultsHeader /> ) ;
113-
114- const buttons = screen . getAllByRole ( 'button' ) ;
115- buttons . forEach ( ( button ) => {
116- const buttonClasses = button . className ;
117- if ( button . textContent === 'search-results-header-filter-source-button-label--channels' ) {
118- expect ( buttonClasses ) . toContain (
119- 'str-chat__search-results-header__filter-source-button--active' ,
120- ) ;
121- } else {
122- expect ( buttonClasses ) . not . toContain (
123- 'str-chat__search-results-header__filter-source-button--active' ,
124- ) ;
125- }
149+ it ( 'does not perform search upon activation if it search query is empty' , ( ) => {
150+ mockSearchController . searchQuery = '' ;
151+ render ( < SearchResultsHeader /> ) ;
152+
153+ fireEvent . click (
154+ screen . getByText ( 'search-results-header-filter-source-button-label--channels' ) ,
155+ ) ;
156+ expect ( mockSearchController . activateSource ) . toHaveBeenCalledWith ( 'channels' ) ;
157+ expect ( mockSources . channels . search ) . not . toHaveBeenCalled ( ) ;
126158 } ) ;
127159 } ) ;
128160} ) ;
0 commit comments