@@ -20,8 +20,21 @@ import Node from '../../components/Node/Node'
2020import NodeInfo from '../../models/node-info'
2121import OsInfo from '../../models/os-info'
2222import StereotypeInfo from '../../models/stereotype-info'
23- import { render , screen } from '@testing-library/react'
23+ import { render , screen , within } from '@testing-library/react'
2424import userEvent from '@testing-library/user-event'
25+ import '@testing-library/jest-dom'
26+
27+ jest . mock ( '../../components/LiveView/LiveView' , ( ) => {
28+ return {
29+ __esModule : true ,
30+ default : React . forwardRef ( ( props : { url : string , scaleViewport ?: boolean , onClose ?: ( ) => void } , ref ) => {
31+ React . useImperativeHandle ( ref , ( ) => ( {
32+ disconnect : jest . fn ( )
33+ } ) )
34+ return < div data-testid = "mock-live-view" data-url = { props . url } > LiveView Mock</ div >
35+ } )
36+ }
37+ } )
2538
2639const osInfo : OsInfo = {
2740 name : 'Mac OS X' ,
@@ -49,24 +62,135 @@ const node: NodeInfo = {
4962 slotStereotypes : [ slotStereotype ]
5063}
5164
52- it ( 'renders basic node information' , ( ) => {
53- render ( < Node node = { node } /> )
54- expect ( screen . getByText ( node . uri ) ) . toBeInTheDocument ( )
55- expect (
56- screen . getByText ( `Sessions: ${ node . sessionCount } ` ) ) . toBeInTheDocument ( )
57- expect ( screen . getByText (
58- `Max. Concurrency: ${ node . maxSession } ` ) ) . toBeInTheDocument ( )
59- } )
65+ const sessionWithVnc = {
66+ id : 'session-with-vnc' ,
67+ capabilities : JSON . stringify ( {
68+ 'browserName' : 'chrome' ,
69+ 'browserVersion' : '88.0' ,
70+ 'se:vnc' : 'ws://192.168.1.7:5900/websockify'
71+ } ) ,
72+ nodeId : node . id
73+ }
74+
75+ const sessionWithoutVnc = {
76+ id : 'session-without-vnc' ,
77+ capabilities : JSON . stringify ( {
78+ 'browserName' : 'chrome' ,
79+ 'browserVersion' : '88.0'
80+ } ) ,
81+ nodeId : node . id
82+ }
83+
84+ describe ( 'Node component' , ( ) => {
85+ it ( 'renders basic node information' , ( ) => {
86+ render ( < Node node = { node } /> )
87+ expect ( screen . getByText ( node . uri ) ) . toBeInTheDocument ( )
88+ expect (
89+ screen . getByText ( `Sessions: ${ node . sessionCount } ` ) ) . toBeInTheDocument ( )
90+ expect ( screen . getByText (
91+ `Max. Concurrency: ${ node . maxSession } ` ) ) . toBeInTheDocument ( )
92+ } )
93+
94+ it ( 'renders detailed node information' , async ( ) => {
95+ render ( < Node node = { node } /> )
96+ const user = userEvent . setup ( )
97+ await user . click ( screen . getByRole ( 'button' ) )
98+ expect ( screen . getByText ( `Node Id: ${ node . id } ` ) ) . toBeInTheDocument ( )
99+ expect (
100+ screen . getByText ( `Total slots: ${ node . slotCount } ` ) ) . toBeInTheDocument ( )
101+ expect ( screen . getByText ( `OS Arch: ${ node . osInfo . arch } ` ) ) . toBeInTheDocument ( )
102+ expect ( screen . getByText ( `OS Name: ${ node . osInfo . name } ` ) ) . toBeInTheDocument ( )
103+ expect (
104+ screen . getByText ( `OS Version: ${ node . osInfo . version } ` ) ) . toBeInTheDocument ( )
105+ } )
106+
107+ it ( 'does not render live view icon when no VNC session is available' , ( ) => {
108+ render ( < Node node = { node } sessions = { [ sessionWithoutVnc ] } origin = "http://localhost:4444" /> )
109+ expect ( screen . queryByTestId ( 'VideocamIcon' ) ) . not . toBeInTheDocument ( )
110+ } )
111+
112+ it ( 'renders live view icon when VNC session is available' , ( ) => {
113+ render ( < Node node = { node } sessions = { [ sessionWithVnc ] } origin = "http://localhost:4444" /> )
114+ expect ( screen . getByTestId ( 'VideocamIcon' ) ) . toBeInTheDocument ( )
115+ } )
116+
117+ it ( 'opens live view dialog when camera icon is clicked' , async ( ) => {
118+ render ( < Node node = { node } sessions = { [ sessionWithVnc ] } origin = "http://localhost:4444" /> )
119+
120+ const user = userEvent . setup ( )
121+ await user . click ( screen . getByTestId ( 'VideocamIcon' ) )
122+
123+ expect ( screen . getByText ( 'Node Session Live View' ) ) . toBeInTheDocument ( )
124+ const dialogTitle = screen . getByText ( 'Node Session Live View' )
125+ const dialog = dialogTitle . closest ( '.MuiDialog-root' )
126+ expect ( dialog ) . not . toBeNull ( )
127+ if ( dialog ) {
128+ expect ( within ( dialog as HTMLElement ) . getAllByText ( node . uri ) . length ) . toBeGreaterThan ( 0 )
129+ }
130+ expect ( screen . getByTestId ( 'mock-live-view' ) ) . toBeInTheDocument ( )
131+ } )
132+
133+ it ( 'closes live view dialog when close button is clicked' , async ( ) => {
134+ render ( < Node node = { node } sessions = { [ sessionWithVnc ] } origin = "http://localhost:4444" /> )
135+
136+ const user = userEvent . setup ( )
137+ await user . click ( screen . getByTestId ( 'VideocamIcon' ) )
138+
139+ expect ( screen . getByText ( 'Node Session Live View' ) ) . toBeInTheDocument ( )
140+
141+ await user . click ( screen . getByRole ( 'button' , { name : / c l o s e / i } ) )
142+
143+ expect ( screen . queryByText ( 'Node Session Live View' ) ) . not . toBeInTheDocument ( )
144+ } )
145+
146+ it ( 'correctly transforms VNC URL for WebSocket connection' , async ( ) => {
147+ const origin = 'https://grid.example.com'
148+ render ( < Node node = { node } sessions = { [ sessionWithVnc ] } origin = { origin } /> )
149+
150+ const user = userEvent . setup ( )
151+ await user . click ( screen . getByTestId ( 'VideocamIcon' ) )
152+
153+ const liveView = screen . getByTestId ( 'mock-live-view' )
154+ const url = liveView . getAttribute ( 'data-url' )
155+
156+ expect ( url ) . toContain ( 'wss:' )
157+ expect ( url ) . toContain ( 'grid.example.com' )
158+ expect ( url ) . toContain ( '/websockify' )
159+ } )
160+
161+ it ( 'handles HTTP to WS protocol conversion correctly' , async ( ) => {
162+ const httpOrigin = 'http://grid.example.com'
163+ render ( < Node node = { node } sessions = { [ sessionWithVnc ] } origin = { httpOrigin } /> )
164+
165+ const user = userEvent . setup ( )
166+ await user . click ( screen . getByTestId ( 'VideocamIcon' ) )
167+
168+ const liveView = screen . getByTestId ( 'mock-live-view' )
169+ const url = liveView . getAttribute ( 'data-url' )
170+
171+ expect ( url ) . toContain ( 'ws:' )
172+ expect ( url ) . not . toContain ( 'wss:' )
173+ } )
60174
61- it ( 'renders detailed node information' , async ( ) => {
62- render ( < Node node = { node } /> )
63- const user = userEvent . setup ( )
64- await user . click ( screen . getByRole ( 'button' ) )
65- expect ( screen . getByText ( `Node Id: ${ node . id } ` ) ) . toBeInTheDocument ( )
66- expect (
67- screen . getByText ( `Total slots: ${ node . slotCount } ` ) ) . toBeInTheDocument ( )
68- expect ( screen . getByText ( `OS Arch: ${ node . osInfo . arch } ` ) ) . toBeInTheDocument ( )
69- expect ( screen . getByText ( `OS Name: ${ node . osInfo . name } ` ) ) . toBeInTheDocument ( )
70- expect (
71- screen . getByText ( `OS Version: ${ node . osInfo . version } ` ) ) . toBeInTheDocument ( )
175+ it ( 'handles invalid VNC URLs gracefully' , async ( ) => {
176+ const invalidVncSession = {
177+ id : 'session-invalid-vnc' ,
178+ capabilities : JSON . stringify ( {
179+ 'browserName' : 'chrome' ,
180+ 'browserVersion' : '88.0' ,
181+ 'se:vnc' : 'invalid-url'
182+ } ) ,
183+ nodeId : node . id
184+ }
185+
186+ render ( < Node node = { node } sessions = { [ invalidVncSession ] } origin = "http://localhost:4444" /> )
187+
188+ const user = userEvent . setup ( )
189+ await user . click ( screen . getByTestId ( 'VideocamIcon' ) )
190+
191+ const liveView = screen . getByTestId ( 'mock-live-view' )
192+ const url = liveView . getAttribute ( 'data-url' )
193+
194+ expect ( url ) . toBe ( '' )
195+ } )
72196} )
0 commit comments