Skip to content

Commit 28e705e

Browse files
test: migrate unit tests from karma/jasmine to vitest (#2571)
Switches the test target from `@angular/build:karma` to `@angular/build:unit-test` with the vitest runner, running in headless Chromium via Playwright. Coverage reporters, the `ui-toolkit-angular.xml` JUnit report and the CI artifact upload are all preserved.
1 parent d50b2c7 commit 28e705e

12 files changed

Lines changed: 826 additions & 1657 deletions

.github/dependabot.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,16 @@ updates:
1111
- '@angular/*'
1212
- '@angular-devkit/*'
1313
- 'rxjs'
14-
- 'zone.js'
1514
- 'typescript'
1615
update-types:
1716
- patch
1817
- minor
18+
# vitest and its plugins peer-depend on the same 4.x line; bump together.
19+
vitest:
20+
patterns:
21+
- 'vitest'
22+
- '@vitest/*'
23+
- 'playwright'
1924
ignore:
2025
- dependency-name: '@angular/*'
2126
update-types:
@@ -26,9 +31,6 @@ updates:
2631
- dependency-name: 'rxjs'
2732
update-types:
2833
- version-update:semver-major
29-
- dependency-name: 'zone.js'
30-
update-types:
31-
- version-update:semver-major
3234
- dependency-name: 'typescript'
3335
update-types:
3436
- version-update:semver-major

.github/workflows/ci.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,22 @@ jobs:
4747
- name: Run Prettier Check
4848
run: npm run ci-prettify
4949

50+
- name: Install Playwright Chromium
51+
run: npx playwright install --with-deps chromium
52+
5053
- name: Run Test
5154
run: npm run test-ci
5255

5356
- uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
5457
name: Upload Coverage Results
5558
with:
56-
directory: ./coverage
59+
directory: ./coverage/ui-toolkit-angular
5760
- name: Run Build
5861
run: npm run build
5962

60-
- name: Upload Karma Results
63+
- name: Upload Test Results
64+
# Run even when Test fails — that is exactly when the junit report matters.
65+
if: always()
6166
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
6267
with:
6368
name: ui-toolkit-angular-unit

angular.json

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,39 @@
2525
"defaultConfiguration": "production"
2626
},
2727
"test": {
28-
"builder": "@angular/build:karma",
28+
"builder": "@angular/build:unit-test",
2929
"options": {
30-
"codeCoverage": true,
31-
"main": "test.ts",
30+
"buildTarget": "ui-toolkit-angular:build",
31+
"runner": "vitest",
3232
"tsConfig": "tsconfig.spec.json",
33-
"karmaConfig": "karma.conf.js"
33+
"browsers": [
34+
"chromium"
35+
],
36+
"headless": true,
37+
"coverage": true,
38+
"coverageReporters": [
39+
"html",
40+
"text-summary",
41+
"lcov"
42+
],
43+
"reporters": [
44+
"default",
45+
[
46+
"junit",
47+
{
48+
"outputFile": "ui-toolkit-angular.xml"
49+
}
50+
]
51+
]
3452
}
3553
},
3654
"lint": {
3755
"builder": "@angular-eslint/builder:lint",
3856
"options": {
39-
"lintFilePatterns": ["**/*.ts", "**/*.html"]
57+
"lintFilePatterns": [
58+
"**/*.ts",
59+
"**/*.html"
60+
]
4061
}
4162
}
4263
}

eslint.config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ const angular = require('angular-eslint')
88
const eslintConfigPrettier = require('eslint-config-prettier')
99
// Export our config array, which is composed together thanks to the typed utility function from typescript-eslint
1010
module.exports = tseslint.config(
11+
{
12+
// Flat config does not honour .gitignore; without this the generated
13+
// coverage HTML report fails to parse under the lint globs.
14+
ignores: [
15+
'coverage/',
16+
'dist/'
17+
]
18+
},
1119
{
1220
// Everything in this config object targets our TypeScript files (Components, Directives, Pipes etc)
1321
files: ['**/*.ts'],

ider/src/ider.component.spec.ts

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ComponentFixture, TestBed } from '@angular/core/testing'
22
import { IDERComponent } from './ider.component'
33
import { AMTRedirector, AMTIDER } from '@device-management-toolkit/ui-toolkit/core'
4+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
45

56
describe('IderComponent', () => {
67
let component: IDERComponent
@@ -24,6 +25,13 @@ describe('IderComponent', () => {
2425
// Initialize with deviceConnection = false to avoid triggering effect during setup
2526
fixture.componentRef.setInput('deviceConnection', false)
2627
fixture.detectChanges()
28+
29+
// Never let a spec open a real WebSocket to the MPS server.
30+
vi.spyOn(AMTRedirector.prototype, 'start').mockImplementation(() => {})
31+
})
32+
33+
afterEach(() => {
34+
vi.restoreAllMocks()
2735
})
2836

2937
it('should create', () => {
@@ -49,45 +57,49 @@ describe('IderComponent', () => {
4957
})
5058

5159
it('should emit device status', () => {
52-
spyOn(component.deviceStatus, 'emit')
60+
const emitSpy = vi.spyOn(component.deviceStatus, 'emit').mockImplementation(() => {})
5361
component.onConnectionStateChange(component.redirector, 1)
54-
expect(component.deviceStatus.emit).toHaveBeenCalledWith(1)
62+
expect(emitSpy).toHaveBeenCalledWith(1)
5563
})
5664

5765
it('should call init when deviceConnection is true', () => {
58-
spyOn(component, 'init')
66+
const initSpy = vi.spyOn(component, 'init').mockImplementation(() => {})
5967
fixture.componentRef.setInput('deviceConnection', true)
6068
fixture.detectChanges()
61-
expect(component.init).toHaveBeenCalled()
69+
expect(initSpy).toHaveBeenCalled()
6270
})
6371

6472
it('should call stopIder when deviceConnection is false', () => {
65-
spyOn(component, 'stopIder')
73+
// Stub init so it only instantiates: the real init() schedules a 4s setTimeout
74+
// that would call startIder() long after this spec has finished.
75+
vi.spyOn(component, 'init').mockImplementation(() => {
76+
component.instantiate()
77+
})
78+
const stopIderSpy = vi.spyOn(component, 'stopIder').mockImplementation(() => {})
6679

6780
// First set deviceConnection to true to initialize
6881
fixture.componentRef.setInput('deviceConnection', true)
6982
fixture.detectChanges()
7083

71-
// Set up a mock redirector so the effect will call stopIder
72-
component.redirector = {} as any
84+
expect(component.redirector).toBeInstanceOf(AMTRedirector)
7385

7486
// Then set deviceConnection to false
7587
fixture.componentRef.setInput('deviceConnection', false)
7688
fixture.detectChanges()
7789

78-
expect(component.stopIder).toHaveBeenCalled()
90+
expect(stopIderSpy).toHaveBeenCalled()
7991
})
8092

8193
it('should emit updated iderData', () => {
82-
spyOn(component.iderData, 'emit')
94+
const emitSpy = vi.spyOn(component.iderData, 'emit').mockImplementation(() => {})
8395
component.instantiate()
8496
component.iderSectorStats(1, 0, 0, 0, 2)
85-
expect(component.iderData.emit).toHaveBeenCalled()
97+
expect(emitSpy).toHaveBeenCalled()
8698
})
8799

88100
it('should stop ider', () => {
89-
const redirectorSpy = spyOn(AMTRedirector.prototype, 'stop')
90-
const cleanupSpy = spyOn(component, 'cleanup')
101+
const redirectorSpy = vi.spyOn(AMTRedirector.prototype, 'stop').mockImplementation(() => {})
102+
const cleanupSpy = vi.spyOn(component, 'cleanup').mockImplementation(() => {})
91103
component.instantiate()
92104
component.stopIder()
93105

@@ -98,7 +110,7 @@ describe('IderComponent', () => {
98110
})
99111

100112
it('should stop ider on destroy', () => {
101-
const stopSpy = spyOn(AMTIDER.prototype, 'stop')
113+
const stopSpy = vi.spyOn(AMTIDER.prototype, 'stop').mockImplementation(() => {})
102114
component.instantiate()
103115
component.ngOnDestroy()
104116
expect(stopSpy).toHaveBeenCalled()

karma.conf.js

Lines changed: 0 additions & 59 deletions
This file was deleted.

0 commit comments

Comments
 (0)