Skip to content

Commit 6a12c7f

Browse files
committed
feat: enable hotkey drop down for KVM
1 parent f2b0f2e commit 6a12c7f

9 files changed

Lines changed: 183 additions & 94 deletions

File tree

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"@angular/platform-browser": "~20.1.2",
3131
"@angular/platform-browser-dynamic": "~20.1.2",
3232
"@angular/router": "~20.1.2",
33-
"@device-management-toolkit/ui-toolkit-angular": "^9.1.4",
33+
"@device-management-toolkit/ui-toolkit-angular": "^10.1.0",
3434
"@xterm/xterm": "^5.5.0",
3535
"@ngx-translate/core": "^16.0.4",
3636
"@ngx-translate/http-loader": "^16.0.1",

src/app/core/toolbar/toolbar.component.spec.ts

Lines changed: 66 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -10,100 +10,119 @@ import { AuthService } from 'src/app/auth.service'
1010
import { ToolbarComponent } from './toolbar.component'
1111
import { BehaviorSubject, of } from 'rxjs'
1212
import { HttpClient, provideHttpClient } from '@angular/common/http'
13-
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'
13+
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'
1414
import { TranslateHttpLoader } from '@ngx-translate/http-loader'
1515
import { provideHttpClientTesting } from '@angular/common/http/testing'
16+
import { environment } from 'src/environments/environment'
17+
18+
// Factory function for the TranslateHttpLoader
19+
function createTranslateLoader(http: HttpClient) {
20+
return new TranslateHttpLoader(http, '/assets/i18n/', '.json')
21+
}
1622

1723
describe('ToolbarComponent', () => {
1824
let component: ToolbarComponent
1925
let fixture: ComponentFixture<ToolbarComponent>
2026
let authService: jasmine.SpyObj<AuthService>
21-
let getMPSVersionSpy: jasmine.Spy
22-
let getRPSVersionSpy: jasmine.Spy
23-
// let getConsoleVersionSpy: jasmine.Spy
24-
// let logoutSpy: jasmine.Spy
25-
let translate: TranslateService
26-
27-
// Factory function for the TranslateHttpLoader
28-
function HttpLoaderFactory(http: HttpClient) {
29-
return new TranslateHttpLoader(http, '/assets/i18n/', '.json')
30-
}
27+
let matDialog: jasmine.SpyObj<MatDialog>
28+
let isLoggedInSubject: BehaviorSubject<boolean>
29+
3130
beforeEach(async () => {
31+
// Override environment.cloud for testing
32+
Object.defineProperty(environment, 'cloud', {
33+
writable: true,
34+
value: true
35+
})
36+
37+
isLoggedInSubject = new BehaviorSubject<boolean>(false)
38+
39+
const authServiceSpy = jasmine.createSpyObj(
40+
'AuthService',
41+
[
42+
'logout',
43+
'getMPSVersion',
44+
'getRPSVersion',
45+
'getConsoleVersion',
46+
'compareSemver'
47+
],
48+
{
49+
isLoggedIn: isLoggedInSubject.asObservable(),
50+
loggedInSubject$: isLoggedInSubject
51+
}
52+
)
53+
54+
authServiceSpy.getMPSVersion.and.returnValue(of({ version: '1.0.0' }))
55+
authServiceSpy.getRPSVersion.and.returnValue(of({ version: '1.0.0' }))
56+
authServiceSpy.getConsoleVersion.and.returnValue(of({ version: '1.0.0' }))
57+
authServiceSpy.compareSemver.and.returnValue(1)
58+
59+
const matDialogSpy = jasmine.createSpyObj('MatDialog', ['open'])
3260
const routerSpy = jasmine.createSpyObj('Router', ['navigate'])
33-
authService = jasmine.createSpyObj('AuthService', [
34-
'logout',
35-
'getMPSVersion',
36-
'getRPSVersion',
37-
'getConsoleVersion'
38-
])
39-
getMPSVersionSpy = authService.getMPSVersion.and.returnValue(of({}))
40-
getRPSVersionSpy = authService.getRPSVersion.and.returnValue(of({}))
41-
// getConsoleVersionSpy = authService.getConsoleVersion.and.returnValue(of({}))
42-
const authServiceStub = {
43-
loggedInSubject$: new BehaviorSubject<boolean>(true)
44-
}
45-
46-
TestBed.configureTestingModule({
61+
62+
await TestBed.configureTestingModule({
4763
imports: [
4864
ToolbarComponent,
4965
TranslateModule.forRoot({
5066
loader: {
5167
provide: TranslateLoader,
52-
useFactory: HttpLoaderFactory,
68+
useFactory: createTranslateLoader,
5369
deps: [HttpClient]
5470
}
5571
})
5672
],
5773
providers: [
58-
{ provide: Router, useValue: routerSpy },
59-
{ provide: AuthService, useValue: { ...authServiceStub, ...authService } },
60-
TranslateService,
6174
provideHttpClient(),
62-
provideHttpClientTesting()
63-
]
64-
})
75+
provideHttpClientTesting(),
76+
{ provide: AuthService, useValue: authServiceSpy },
77+
{ provide: MatDialog, useValue: matDialogSpy },
78+
{ provide: Router, useValue: routerSpy }]
79+
}).compileComponents()
80+
6581
fixture = TestBed.createComponent(ToolbarComponent)
6682
component = fixture.componentInstance
67-
translate = TestBed.inject(TranslateService)
68-
translate.setDefaultLang('en')
69-
fixture.detectChanges()
83+
authService = TestBed.inject(AuthService) as jasmine.SpyObj<AuthService>
84+
matDialog = TestBed.inject(MatDialog) as jasmine.SpyObj<MatDialog>
7085
})
7186

7287
afterEach(() => {
7388
TestBed.resetTestingModule()
74-
component.cloudMode = true
7589
})
7690

7791
it('should create', () => {
92+
fixture.detectChanges()
7893
expect(component).toBeTruthy()
79-
expect(component.isLoggedIn).toBeTrue()
80-
expect(component.cloudMode).toBeTrue()
8194
})
8295

8396
it('should display dialog', () => {
84-
const dialogSpy = spyOn(TestBed.inject(MatDialog), 'open')
97+
fixture.detectChanges()
8598
component.displayAbout()
86-
expect(dialogSpy).toHaveBeenCalled()
99+
expect(matDialog.open).toHaveBeenCalled()
87100
})
88101

89102
it('should logout and redirect to login page', () => {
103+
fixture.detectChanges()
90104
component.logout()
91105
expect(authService.logout).toHaveBeenCalled()
92106
})
93107

94-
it('should call getMPSVersion', () => {
95-
expect(component).toBeTruthy()
96-
expect(getMPSVersionSpy).toHaveBeenCalled()
108+
it('should call getMPSVersion when logged in and cloud mode', () => {
109+
fixture.detectChanges()
110+
isLoggedInSubject.next(true)
111+
112+
expect(authService.getMPSVersion).toHaveBeenCalled()
97113
})
98114

99-
it('should call getRPSVersion', () => {
100-
expect(component).toBeTruthy()
101-
expect(getRPSVersionSpy).toHaveBeenCalled()
115+
it('should call getRPSVersion when logged in and cloud mode', () => {
116+
fixture.detectChanges()
117+
isLoggedInSubject.next(true)
118+
119+
expect(authService.getRPSVersion).toHaveBeenCalled()
102120
})
103121

104122
it('should subscribe to loggedInSubject on init', () => {
105-
component.authService.loggedInSubject$.next(true)
106123
fixture.detectChanges()
124+
isLoggedInSubject.next(true)
125+
107126
expect(component.isLoggedIn).toBeTruthy()
108127
})
109128
})

src/app/devices/kvm/kvm.component.html

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<mat-toolbar style="height: 100px" class="flex flex-row">
22
<div class="flex flex-50 items-center">
33
@if (amtFeatures()?.kvmAvailable) {
4-
<mat-form-field style="margin-bottom: 0">
4+
<mat-form-field style="margin-bottom: 0; margin-right: 16px">
55
<mat-label>Choose encoding type</mat-label>
66
<mat-select [(ngModel)]="selected" (ngModelChange)="onEncodingChange($event)">
77
@for (encode of encodings; track encode) {
@@ -11,6 +11,17 @@
1111
}
1212
</mat-select>
1313
</mat-form-field>
14+
<mat-form-field style="margin-bottom: 0; margin-right: 8px">
15+
<mat-label>Hot Keys</mat-label>
16+
<mat-select [(ngModel)]="selectedHotkey" placeholder="Select hot key to send">
17+
@for (hotkey of hotKeys; track hotkey.value) {
18+
<mat-option [value]="hotkey.value">
19+
{{ hotkey.label }}
20+
</mat-option>
21+
}
22+
</mat-select>
23+
</mat-form-field>
24+
<button mat-stroked-button (click)="sendHotkey()" [disabled]="!selectedHotkey">Send</button>
1425
}
1526
</div>
1627
<div class="flex flex-50 justify-end">
@@ -53,16 +64,17 @@
5364
[deviceId]="deviceId()"
5465
[mpsServer]="mpsServer"
5566
[authToken]="authToken()"
56-
[deviceConnection]="deviceKVMConnection"
57-
[selectedEncoding]="selectedEncoding"
67+
[deviceConnection]="deviceKVMConnection()"
68+
[selectedEncoding]="selectedEncoding()"
69+
[selectedHotkey]="hotKeySignal()"
5870
(deviceStatus)="deviceKVMStatus($event)"></amt-kvm>
5971
}
6072
<amt-ider
6173
class="ider"
6274
[deviceId]="deviceId()"
6375
[mpsServer]="mpsServer"
6476
[authToken]="authToken()"
65-
[deviceConnection]="deviceIDERConnection"
77+
[deviceConnection]="deviceIDERConnection()"
6678
[cdrom]="diskImage"
6779
[floppy]="null"
6880
(deviceStatus)="deviceIDERStatus($event)">

src/app/devices/kvm/kvm.component.spec.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -489,16 +489,49 @@ describe('KvmComponent', () => {
489489
const mockFile = new File([''], 'test-file.txt', { type: 'text/plain' })
490490
const mockEvt = { target: { files: [mockFile] } } as unknown as Event
491491

492-
const deviceIDERConnectioSpy = spyOn(component.deviceIDERConnection, 'emit')
492+
const deviceIDERConnectionSpy = spyOn(component.deviceIDERConnection, 'set')
493493
component.onFileSelected(mockEvt)
494494

495495
expect(component.diskImage).toEqual(mockFile)
496-
expect(deviceIDERConnectioSpy).toHaveBeenCalledWith(true)
496+
expect(deviceIDERConnectionSpy).toHaveBeenCalledWith(true)
497497
})
498498
it('should emit false on canceling IDER', () => {
499-
const deviceIDERConnectioSpy = spyOn(component.deviceIDERConnection, 'emit')
499+
const deviceIDERConnectionSpy = spyOn(component.deviceIDERConnection, 'set')
500500
component.onCancelIDER()
501501

502-
expect(deviceIDERConnectioSpy).toHaveBeenCalledWith(false)
502+
expect(deviceIDERConnectionSpy).toHaveBeenCalledWith(false)
503+
})
504+
505+
// Hot Key tests
506+
it('should send hotkey when sendHotkey is called with selectedHotkey', () => {
507+
const hotKeySignalSpy = spyOn(component.hotKeySignal, 'set')
508+
component.selectedHotkey = 'ctrl-alt-del'
509+
510+
component.sendHotkey()
511+
512+
expect(hotKeySignalSpy).toHaveBeenCalledWith('ctrl-alt-del')
513+
})
514+
515+
it('should not send hotkey when sendHotkey is called without selectedHotkey', () => {
516+
const hotKeySignalSpy = spyOn(component.hotKeySignal, 'set')
517+
component.selectedHotkey = null
518+
519+
component.sendHotkey()
520+
521+
expect(hotKeySignalSpy).not.toHaveBeenCalled()
522+
})
523+
524+
it('should reset hotkey signal after timeout', (done) => {
525+
const hotKeySignalSpy = spyOn(component.hotKeySignal, 'set')
526+
component.selectedHotkey = 'ctrl-alt-del'
527+
528+
component.sendHotkey()
529+
530+
expect(hotKeySignalSpy).toHaveBeenCalledWith('ctrl-alt-del')
531+
532+
setTimeout(() => {
533+
expect(hotKeySignalSpy).toHaveBeenCalledWith(null)
534+
done()
535+
}, 150)
503536
})
504537
})

0 commit comments

Comments
 (0)