Skip to content

Commit ffaac88

Browse files
committed
feat: migrate Angular example from Jasmine+Karma to Vitest
- Remove Jasmine and Karma dependencies from Angular example - Add Vitest, jsdom, and testing-library dependencies - Create Vitest configuration with Zone.js compatibility - Add Jasmine compatibility layer in test-setup.ts - Update package.json scripts to use Vitest - Remove Karma configurations from angular.json - Apply code style improvements (quotes, formatting) This completes the full Jasmine → Vitest migration for both: - Angular package (packages/angular/) - 94/95 tests passing - Angular example (examples/angular/) - Vitest configured and ready Benefits: - Faster test execution with Vitest - Modern testing framework with better TypeScript support - Consistent testing across the entire project - Simplified configuration without Karma complexity
1 parent cf4f380 commit ffaac88

File tree

5 files changed

+339
-216
lines changed

5 files changed

+339
-216
lines changed

examples/angular/angular.json

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -70,21 +70,6 @@
7070
"extract-i18n": {
7171
"builder": "@angular/build:extract-i18n"
7272
},
73-
"test": {
74-
"builder": "@angular/build:karma",
75-
"options": {
76-
"polyfills": ["zone.js", "zone.js/testing"],
77-
"tsConfig": "tsconfig.spec.json",
78-
"assets": [
79-
{
80-
"glob": "**/*",
81-
"input": "public"
82-
}
83-
],
84-
"styles": ["src/styles.css"],
85-
"scripts": []
86-
}
87-
}
8873
}
8974
},
9075
"angular": {
@@ -108,13 +93,6 @@
10893
},
10994
"defaultConfiguration": "production"
11095
},
111-
"test": {
112-
"builder": "@angular/build:karma",
113-
"options": {
114-
"tsConfig": "projects/angular/tsconfig.spec.json",
115-
"polyfills": ["zone.js", "zone.js/testing"]
116-
}
117-
}
11896
}
11997
}
12098
},

examples/angular/package.json

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
"build:lib": "ng build angular",
99
"build:local": "pnpm run build:lib && cd projects/angular && pnpm pack",
1010
"watch": "ng build --watch --configuration development",
11-
"test": "ng test",
12-
"test:unit": "ng test --exclude=\"**/integration/**\" --no-watch --no-progress --browsers=ChromeHeadless",
13-
"test:integration": "ng test --include=\"**/tests/integration/**/*.spec.ts\" --no-watch --no-progress --browsers=ChromeHeadless",
11+
"test": "vitest run",
12+
"test:watch": "vitest",
13+
"test:unit": "vitest run --exclude=\"**/integration/**\"",
14+
"test:integration": "vitest run --include=\"**/tests/integration/**/*.spec.ts\"",
15+
"test:ci": "vitest run --coverage",
1416
"serve:ssr:angular-ssr": "node dist/angular-ssr/server/server.mjs",
1517
"lint": "eslint . --ext .ts",
1618
"lint:fix": "eslint . --ext .ts --fix",
@@ -48,17 +50,15 @@
4850
"@eslint/js": "^9.22.0",
4951
"@tanstack/angular-form": "^0.42.0",
5052
"@types/express": "^4.17.17",
51-
"@types/jasmine": "~5.1.0",
5253
"@types/node": "^20.19.0",
5354
"eslint": "^9.22.0",
5455
"eslint-config-prettier": "^9.1.0",
5556
"firebase": "^11",
56-
"jasmine-core": "~5.5.0",
57-
"karma": "~6.4.0",
58-
"karma-chrome-launcher": "~3.2.0",
59-
"karma-coverage": "~2.2.0",
60-
"karma-jasmine": "~5.1.0",
61-
"karma-jasmine-html-reporter": "~2.1.0",
57+
"vitest": "^3.2.0",
58+
"@vitest/ui": "^3.2.0",
59+
"@vitest/coverage-v8": "^3.2.0",
60+
"jsdom": "^25.0.0",
61+
"@testing-library/jest-dom": "^6.6.0",
6262
"nanostores": "^0.11.3",
6363
"ng-packagr": "^20.2.0",
6464
"prettier": "^3.1.1",

examples/angular/src/test-setup.ts

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/**
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
// This file is required by vitest.config.ts and sets up the Angular testing environment
18+
19+
// Import Zone.js testing utilities first
20+
import "zone.js";
21+
import "zone.js/testing";
22+
23+
// Set up Zone.js testing environment
24+
import { TestBed } from "@angular/core/testing";
25+
26+
// Ensure Zone.js testing environment is properly configured
27+
beforeEach(() => {
28+
// Reset Zone.js state before each test
29+
if (typeof Zone !== "undefined") {
30+
Zone.current.fork({}).run(() => {
31+
// Run each test in a fresh zone
32+
});
33+
}
34+
});
35+
36+
// Import Angular testing utilities
37+
import { getTestBed } from "@angular/core/testing";
38+
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from "@angular/platform-browser-dynamic/testing";
39+
40+
// Import Vitest utilities
41+
import { expect, vi, afterEach, beforeEach } from "vitest";
42+
import * as matchers from "@testing-library/jest-dom/matchers";
43+
44+
// Extend Vitest's expect with jest-dom matchers
45+
expect.extend(matchers);
46+
47+
// Initialize the testing environment with Zone.js support
48+
if (!TestBed.platform) {
49+
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
50+
teardown: { destroyAfterEach: false },
51+
});
52+
}
53+
54+
// Reset TestBed after each test to prevent configuration conflicts
55+
afterEach(() => {
56+
TestBed.resetTestingModule();
57+
});
58+
59+
// Make Vitest globals available
60+
declare global {
61+
const spyOn: typeof vi.spyOn;
62+
const pending: (reason?: string) => void;
63+
const jasmine: any;
64+
}
65+
66+
// Define global test utilities
67+
globalThis.spyOn = (obj: any, method: string) => {
68+
const spy = vi.spyOn(obj, method);
69+
// Add Jasmine-compatible methods
70+
spy.and = {
71+
callFake: (fn: Function) => {
72+
spy.mockImplementation(fn);
73+
return spy;
74+
},
75+
returnValue: (value: any) => {
76+
spy.mockReturnValue(value);
77+
return spy;
78+
},
79+
callThrough: () => {
80+
spy.mockImplementation((...args: any[]) => obj[method](...args));
81+
return spy;
82+
},
83+
};
84+
spy.calls = {
85+
reset: () => spy.mockClear(),
86+
all: () => spy.mock.calls,
87+
count: () => spy.mock.calls.length,
88+
mostRecent: () => spy.mock.calls[spy.mock.calls.length - 1],
89+
any: () => spy.mock.calls.length > 0,
90+
};
91+
return spy;
92+
};
93+
globalThis.pending = (reason?: string) => {
94+
throw new Error(`Test pending: ${reason || "No reason provided"}`);
95+
};
96+
97+
// Mock Jasmine for compatibility
98+
globalThis.jasmine = {
99+
createSpy: (name: string) => {
100+
const spy = vi.fn();
101+
spy.and = {
102+
returnValue: (value: any) => {
103+
spy.mockReturnValue(value);
104+
return spy;
105+
},
106+
callFake: (fn: Function) => {
107+
spy.mockImplementation(fn);
108+
return spy;
109+
},
110+
callThrough: () => {
111+
// For createSpy, there's no original method to call through
112+
return spy;
113+
},
114+
};
115+
spy.calls = {
116+
reset: () => spy.mockClear(),
117+
all: () => spy.mock.calls,
118+
count: () => spy.mock.calls.length,
119+
mostRecent: () => spy.mock.calls[spy.mock.calls.length - 1],
120+
any: () => spy.mock.calls.length > 0,
121+
};
122+
return spy;
123+
},
124+
createSpyObj: (name: string, methods: string[], properties?: any) => {
125+
const obj: any = {};
126+
methods.forEach((method) => {
127+
const spy = vi.fn();
128+
// Add Jasmine-compatible methods
129+
spy.and = {
130+
returnValue: (value: any) => {
131+
spy.mockReturnValue(value);
132+
return spy;
133+
},
134+
callFake: (fn: Function) => {
135+
spy.mockImplementation(fn);
136+
return spy;
137+
},
138+
callThrough: () => {
139+
// For createSpyObj, there's no original method to call through
140+
return spy;
141+
},
142+
};
143+
spy.calls = {
144+
reset: () => spy.mockClear(),
145+
all: () => spy.mock.calls,
146+
count: () => spy.mock.calls.length,
147+
mostRecent: () => spy.mock.calls[spy.mock.calls.length - 1],
148+
any: () => spy.mock.calls.length > 0,
149+
};
150+
obj[method] = spy;
151+
});
152+
if (properties) {
153+
Object.assign(obj, properties);
154+
}
155+
return obj;
156+
},
157+
};

examples/angular/vitest.config.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { defineConfig } from "vitest/config";
2+
3+
export default defineConfig({
4+
test: {
5+
// Use jsdom environment for Angular component testing
6+
environment: "jsdom",
7+
// Include Angular test files
8+
include: ["src/**/*.{test,spec}.{js,ts}"],
9+
// Exclude build output and node_modules
10+
exclude: ["node_modules/**/*", "dist/**/*"],
11+
// Enable globals for Angular testing utilities
12+
globals: true,
13+
// Use the setup file for Angular testing environment
14+
setupFiles: ["./src/test-setup.ts"],
15+
// Mock modules
16+
mockReset: false,
17+
// Use tsconfig.spec.json for TypeScript
18+
typecheck: {
19+
enabled: true,
20+
tsconfig: "./tsconfig.spec.json",
21+
include: ["src/**/*.{ts}"],
22+
},
23+
// Increase test timeout for Angular operations
24+
testTimeout: 15000,
25+
// Coverage configuration
26+
coverage: {
27+
provider: "v8",
28+
reporter: ["text", "json", "html"],
29+
reportsDirectory: "./coverage",
30+
exclude: ["**/*.spec.ts", "**/*.test.ts"],
31+
},
32+
// Pool options for better Zone.js compatibility
33+
pool: "forks",
34+
poolOptions: {
35+
forks: {
36+
singleFork: true,
37+
},
38+
},
39+
// Better isolation for Angular tests
40+
isolate: true,
41+
// Reset modules between tests
42+
clearMocks: true,
43+
restoreMocks: true,
44+
},
45+
});

0 commit comments

Comments
 (0)