refactor: migrate test suite from Jest to Jasmine and add zone.js polyfills
This commit is contained in:
parent
6c4febe205
commit
600238de44
@ -58,6 +58,10 @@
|
||||
"options": {
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"karmaConfig": "karma.conf.cjs",
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.spec.ts"
|
||||
],
|
||||
|
10
package-lock.json
generated
10
package-lock.json
generated
@ -62,7 +62,8 @@
|
||||
"postcss": "^8.4.49",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "~5.8.2",
|
||||
"vite": "^6.2.0"
|
||||
"vite": "^6.2.0",
|
||||
"zone.js": "^0.15.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@algolia/abtesting": {
|
||||
@ -18132,6 +18133,13 @@
|
||||
"peerDependencies": {
|
||||
"zod": "^3.24.1"
|
||||
}
|
||||
},
|
||||
"node_modules/zone.js": {
|
||||
"version": "0.15.1",
|
||||
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz",
|
||||
"integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,8 @@
|
||||
"postcss": "^8.4.49",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "~5.8.2",
|
||||
"vite": "^6.2.0"
|
||||
"vite": "^6.2.0",
|
||||
"zone.js": "^0.15.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"@angular/core": "20.3.2",
|
||||
|
@ -17,24 +17,44 @@ describe('sendBatch', () => {
|
||||
data: {},
|
||||
};
|
||||
|
||||
let originalFetch: typeof fetch | undefined;
|
||||
let fetchSpy: (jasmine.Spy & typeof fetch) | undefined;
|
||||
|
||||
beforeAll(() => {
|
||||
originalFetch = globalThis.fetch;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock fetch
|
||||
global.fetch = jest.fn();
|
||||
fetchSpy = jasmine.createSpy('fetch') as jasmine.Spy & typeof fetch;
|
||||
(globalThis as { fetch: typeof fetch }).fetch = fetchSpy;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
fetchSpy = undefined;
|
||||
if (originalFetch) {
|
||||
(globalThis as { fetch: typeof fetch }).fetch = originalFetch;
|
||||
} else {
|
||||
delete (globalThis as { fetch?: typeof fetch }).fetch;
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
if (originalFetch) {
|
||||
(globalThis as { fetch: typeof fetch }).fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
it('should send single record', async () => {
|
||||
(global.fetch as jest.Mock).mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
});
|
||||
fetchSpy!.and.returnValue(
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
} as Response)
|
||||
);
|
||||
|
||||
await sendBatch([mockRecord], '/api/log');
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledWith('/api/log', {
|
||||
expect(fetchSpy).toHaveBeenCalledWith('/api/log', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -44,15 +64,17 @@ describe('sendBatch', () => {
|
||||
});
|
||||
|
||||
it('should send multiple records as array', async () => {
|
||||
(global.fetch as jest.Mock).mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
});
|
||||
fetchSpy.and.returnValue(
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
status: 200,
|
||||
} as Response)
|
||||
);
|
||||
|
||||
const records = [mockRecord, { ...mockRecord, event: 'NAVIGATE' as const }];
|
||||
await sendBatch(records, '/api/log');
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledWith('/api/log', {
|
||||
expect(fetchSpy).toHaveBeenCalledWith('/api/log', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -62,26 +84,38 @@ describe('sendBatch', () => {
|
||||
});
|
||||
|
||||
it('should throw on HTTP error', async () => {
|
||||
(global.fetch as jest.Mock).mockResolvedValue({
|
||||
ok: false,
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
});
|
||||
|
||||
await expect(sendBatch([mockRecord], '/api/log')).rejects.toThrow(
|
||||
'HTTP 500: Internal Server Error'
|
||||
fetchSpy!.and.returnValue(
|
||||
Promise.resolve({
|
||||
ok: false,
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
} as Response)
|
||||
);
|
||||
|
||||
try {
|
||||
await sendBatch([mockRecord], '/api/log');
|
||||
fail('Expected sendBatch to throw on HTTP error');
|
||||
} catch (error) {
|
||||
expect(error).toEqual(jasmine.any(Error));
|
||||
expect((error as Error).message).toBe('HTTP 500: Internal Server Error');
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw on network error', async () => {
|
||||
(global.fetch as jest.Mock).mockRejectedValue(new Error('Network error'));
|
||||
fetchSpy!.and.returnValue(Promise.reject(new Error('Network error')));
|
||||
|
||||
await expect(sendBatch([mockRecord], '/api/log')).rejects.toThrow('Network error');
|
||||
try {
|
||||
await sendBatch([mockRecord], '/api/log');
|
||||
fail('Expected sendBatch to throw on network error');
|
||||
} catch (error) {
|
||||
expect(error).toEqual(jasmine.any(Error));
|
||||
expect((error as Error).message).toBe('Network error');
|
||||
}
|
||||
});
|
||||
|
||||
it('should not send empty batch', async () => {
|
||||
await sendBatch([], '/api/log');
|
||||
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
expect(fetchSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -1,17 +1,45 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { PLATFORM_ID } from '@angular/core';
|
||||
import { LogService } from './log.service';
|
||||
import { environment } from './environment';
|
||||
|
||||
describe('LogService', () => {
|
||||
let service: LogService;
|
||||
let originalOnLine: boolean | undefined;
|
||||
let originalRequestIdle: (typeof window)[keyof typeof window] | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(LogService);
|
||||
|
||||
// Clear localStorage and sessionStorage
|
||||
// Clear storages BEFORE creating the service instance
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
|
||||
// Make sure env won't trigger immediate flushes during assertions
|
||||
environment.logging.enabled = true;
|
||||
environment.logging.batchSize = 9999; // avoid immediate batch flush
|
||||
environment.logging.debounceMs = 60 * 60 * 1000; // 1h to avoid timer firing
|
||||
|
||||
// Stub navigator.onLine and requestIdleCallback for deterministic behavior
|
||||
originalOnLine = navigator.onLine;
|
||||
Object.defineProperty(navigator, 'onLine', { value: true, configurable: true });
|
||||
originalRequestIdle = (window as any).requestIdleCallback;
|
||||
(window as any).requestIdleCallback = (cb: Function) => cb();
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
providers: [{ provide: PLATFORM_ID, useValue: 'browser' }],
|
||||
});
|
||||
service = TestBed.inject(LogService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Restore stubs
|
||||
if (originalOnLine !== undefined) {
|
||||
Object.defineProperty(navigator, 'onLine', { value: originalOnLine, configurable: true });
|
||||
}
|
||||
if (originalRequestIdle) {
|
||||
(window as any).requestIdleCallback = originalRequestIdle;
|
||||
} else {
|
||||
delete (window as any).requestIdleCallback;
|
||||
}
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
@ -61,6 +89,7 @@ describe('LogService', () => {
|
||||
|
||||
it('should not log when disabled', () => {
|
||||
const originalEnabled = environment.logging.enabled;
|
||||
environment.logging.enabled = false;
|
||||
|
||||
service.log('APP_START');
|
||||
|
||||
@ -95,10 +124,9 @@ describe('LogService', () => {
|
||||
];
|
||||
|
||||
localStorage.setItem('obsiviewer.log.queue', JSON.stringify(mockQueue));
|
||||
|
||||
// Create new service instance
|
||||
const newService = TestBed.inject(LogService);
|
||||
const queue = (newService as any).queue;
|
||||
// Trigger loading using the existing instance
|
||||
(service as any).loadQueueFromStorage();
|
||||
const queue = (service as any).queue;
|
||||
|
||||
expect(queue.length).toBe(1);
|
||||
expect(queue[0].event).toBe('APP_START');
|
||||
|
13
src/test.ts
Normal file
13
src/test.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import 'zone.js';
|
||||
import 'zone.js/testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting,
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
// Initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting(),
|
||||
);
|
@ -38,8 +38,9 @@
|
||||
"./src/**/*.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
|
||||
"node_modules"
|
||||
"node_modules",
|
||||
"./src/**/*.spec.ts",
|
||||
"./src/**/*.spec.tsx"
|
||||
],
|
||||
"angularCompilerOptions": {
|
||||
"disableTypeScriptVersionCheck": true
|
||||
|
Loading…
x
Reference in New Issue
Block a user