@@ -20,8 +20,21 @@ import Node from '../../components/Node/Node'
20
20
import NodeInfo from '../../models/node-info'
21
21
import OsInfo from '../../models/os-info'
22
22
import StereotypeInfo from '../../models/stereotype-info'
23
- import { render , screen } from '@testing-library/react'
23
+ import { render , screen , within } from '@testing-library/react'
24
24
import 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
+ } )
25
38
26
39
const osInfo : OsInfo = {
27
40
name : 'Mac OS X' ,
@@ -49,24 +62,135 @@ const node: NodeInfo = {
49
62
slotStereotypes : [ slotStereotype ]
50
63
}
51
64
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
+ } )
60
174
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
+ } )
72
196
} )
0 commit comments