@@ -8,17 +8,24 @@ import { Header } from "./Header";
88
99import { TestWrapper } from "TestWrapper" ;
1010
11- // Mock ServerContext
11+ // Mock ServerContext - configurable per test
12+ const defaultServerContext = {
13+ title : "Airone" ,
14+ version : "1.0.0" ,
15+ headerColor : "#1976d2" ,
16+ legacyUiDisabled : true ,
17+ extendedHeaderMenus : [ ] as Array < {
18+ name : string ;
19+ children : Array < { name : string ; url : string } > ;
20+ } > ,
21+ user : { id : 1 , username : "testuser" } ,
22+ } ;
23+
24+ let mockServerContext = { ...defaultServerContext } ;
25+
1226jest . mock ( "../../services/ServerContext" , ( ) => ( {
1327 ServerContext : {
14- getInstance : ( ) => ( {
15- title : "Airone" ,
16- version : "1.0.0" ,
17- headerColor : "#1976d2" ,
18- legacyUiDisabled : true ,
19- extendedHeaderMenus : [ ] ,
20- user : { id : 1 , username : "testuser" } ,
21- } ) ,
28+ getInstance : ( ) => mockServerContext ,
2229 } ,
2330} ) ) ;
2431
@@ -61,12 +68,16 @@ jest.mock("../../repository/AironeApiClient", () => ({
6168 } ,
6269} ) ) ;
6370
64- // Mock react-use useInterval
65- jest . mock ( "react-use " , ( ) => ( {
71+ // Mock useInterval
72+ jest . mock ( "../../hooks/useInterval " , ( ) => ( {
6673 useInterval : jest . fn ( ) ,
6774} ) ) ;
6875
6976describe ( "Header" , ( ) => {
77+ beforeEach ( ( ) => {
78+ mockServerContext = { ...defaultServerContext , extendedHeaderMenus : [ ] } ;
79+ } ) ;
80+
7081 describe ( "rendering" , ( ) => {
7182 test ( "should render header with title" , ( ) => {
7283 render ( < Header /> , { wrapper : TestWrapper } ) ;
@@ -149,6 +160,110 @@ describe("Header", () => {
149160 expect ( screen . getByText ( "Triggers" ) ) . toBeInTheDocument ( ) ;
150161 } ) ;
151162 } ) ;
163+
164+ test ( "should close management submenu on mouse leave" , async ( ) => {
165+ render ( < Header /> , { wrapper : TestWrapper } ) ;
166+
167+ const managementButton = screen . getByText ( "Management" ) ;
168+ fireEvent . mouseEnter ( managementButton ) ;
169+
170+ await waitFor ( ( ) => {
171+ expect ( screen . getByText ( "Users" ) ) . toBeInTheDocument ( ) ;
172+ } ) ;
173+
174+ // Find the menu list and trigger mouseLeave
175+ const menuItems = screen . getByText ( "Users" ) . closest ( "ul" ) ;
176+ if ( menuItems ) {
177+ fireEvent . mouseLeave ( menuItems ) ;
178+ }
179+
180+ await waitFor ( ( ) => {
181+ expect ( screen . queryByText ( "Users" ) ) . not . toBeVisible ( ) ;
182+ } ) ;
183+ } ) ;
184+ } ) ;
185+
186+ describe ( "extended header menus" , ( ) => {
187+ test ( "should show extended menu items on hover" , async ( ) => {
188+ mockServerContext = {
189+ ...defaultServerContext ,
190+ extendedHeaderMenus : [
191+ {
192+ name : "External Tools" ,
193+ children : [
194+ { name : "Tool A" , url : "https://tool-a.example.com" } ,
195+ { name : "Tool B" , url : "https://tool-b.example.com" } ,
196+ ] ,
197+ } ,
198+ ] ,
199+ } ;
200+
201+ render ( < Header /> , { wrapper : TestWrapper } ) ;
202+
203+ const extendedMenuButton = screen . getByText ( "External Tools" ) ;
204+ expect ( extendedMenuButton ) . toBeInTheDocument ( ) ;
205+
206+ fireEvent . mouseEnter ( extendedMenuButton ) ;
207+
208+ await waitFor ( ( ) => {
209+ expect ( screen . getByText ( "Tool A" ) ) . toBeInTheDocument ( ) ;
210+ expect ( screen . getByText ( "Tool B" ) ) . toBeInTheDocument ( ) ;
211+ } ) ;
212+ } ) ;
213+
214+ test ( "should close extended menu on mouse leave" , async ( ) => {
215+ mockServerContext = {
216+ ...defaultServerContext ,
217+ extendedHeaderMenus : [
218+ {
219+ name : "External Tools" ,
220+ children : [ { name : "Tool A" , url : "https://tool-a.example.com" } ] ,
221+ } ,
222+ ] ,
223+ } ;
224+
225+ render ( < Header /> , { wrapper : TestWrapper } ) ;
226+
227+ const extendedMenuButton = screen . getByText ( "External Tools" ) ;
228+ fireEvent . mouseEnter ( extendedMenuButton ) ;
229+
230+ await waitFor ( ( ) => {
231+ expect ( screen . getByText ( "Tool A" ) ) . toBeInTheDocument ( ) ;
232+ } ) ;
233+
234+ const menuItems = screen . getByText ( "Tool A" ) . closest ( "ul" ) ;
235+ if ( menuItems ) {
236+ fireEvent . mouseLeave ( menuItems ) ;
237+ }
238+
239+ await waitFor ( ( ) => {
240+ expect ( screen . queryByText ( "Tool A" ) ) . not . toBeVisible ( ) ;
241+ } ) ;
242+ } ) ;
243+
244+ test ( "should render links with correct href in extended menu" , async ( ) => {
245+ mockServerContext = {
246+ ...defaultServerContext ,
247+ extendedHeaderMenus : [
248+ {
249+ name : "External Tools" ,
250+ children : [ { name : "Tool A" , url : "https://tool-a.example.com" } ] ,
251+ } ,
252+ ] ,
253+ } ;
254+
255+ render ( < Header /> , { wrapper : TestWrapper } ) ;
256+
257+ fireEvent . mouseEnter ( screen . getByText ( "External Tools" ) ) ;
258+
259+ await waitFor ( ( ) => {
260+ const toolLink = screen . getByText ( "Tool A" ) ;
261+ expect ( toolLink . closest ( "a" ) ) . toHaveAttribute (
262+ "href" ,
263+ "https://tool-a.example.com" ,
264+ ) ;
265+ } ) ;
266+ } ) ;
152267 } ) ;
153268
154269 describe ( "search functionality" , ( ) => {
0 commit comments