Browse Source

Migrate Angular tests from Karma/Jasmine to Vitest

Replaces Karma and Jasmine with Vitest for unit testing in the Angular template.
pull/24725/head
Fahri Gedik 2 months ago
parent
commit
1a2dee7129
  1. 12
      templates/app/angular/angular.json
  2. 44
      templates/app/angular/karma.conf.js
  3. 11
      templates/app/angular/package.json
  4. 1
      templates/app/angular/src/app/home/home.component.html
  5. 114
      templates/app/angular/src/app/home/home.component.spec.ts
  6. 79
      templates/app/angular/src/app/home/home.component.ts
  7. 13
      templates/app/angular/src/test.ts
  8. 13
      templates/app/angular/tsconfig.spec.json

12
templates/app/angular/angular.json

@ -163,17 +163,7 @@
} }
}, },
"test": { "test": {
"builder": "@angular/build:karma", "builder": "@angular/build:unit-test"
"options": {
"browser": "src/test.ts",
"polyfills": ["src/polyfills.ts"],
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.scss"],
"scripts": []
}
}, },
"lint": { "lint": {
"builder": "@angular-eslint/builder:lint", "builder": "@angular-eslint/builder:lint",

44
templates/app/angular/karma.conf.js

@ -1,44 +0,0 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/MyProjectName'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

11
templates/app/angular/package.json

@ -47,17 +47,12 @@
"@angular/cli": "~21.0.0", "@angular/cli": "~21.0.0",
"@angular/compiler-cli": "~21.0.0", "@angular/compiler-cli": "~21.0.0",
"@angular/language-service": "~21.0.0", "@angular/language-service": "~21.0.0",
"@types/jasmine": "~3.6.0",
"@types/node": "~20.11.0", "@types/node": "~20.11.0",
"@typescript-eslint/eslint-plugin": "7.16.0", "@typescript-eslint/eslint-plugin": "7.16.0",
"@typescript-eslint/parser": "7.16.0", "@typescript-eslint/parser": "7.16.0",
"eslint": "^8.0.0", "eslint": "^8.0.0",
"jasmine-core": "~4.0.0", "jsdom": "^27.1.0",
"karma": "~6.4.4", "typescript": "~5.9.3",
"karma-chrome-launcher": "~3.1.0", "vitest": "^4.0.8"
"karma-coverage": "~2.1.0",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.7.0",
"typescript": "~5.9.3"
} }
} }

1
templates/app/angular/src/app/home/home.component.html

@ -1,5 +1,4 @@
<div class="row mb-3"> <div class="row mb-3">
<abp-dynamic-form [fields]="formFields" />
<div class="col-xl-6 col-12 d-flex"> <div class="col-xl-6 col-12 d-flex">
<div class="card h-lg-100 w-100 overflow-hidden"> <div class="card h-lg-100 w-100 overflow-hidden">
<div class="card-body"> <div class="card-body">

114
templates/app/angular/src/app/home/home.component.spec.ts

@ -1,39 +1,43 @@
import { CoreTestingModule } from "@abp/ng.core/testing"; import { CoreTestingModule } from "@abp/ng.core/testing";
import { ThemeSharedTestingModule } from "@abp/ng.theme.shared/testing"; import { ThemeSharedTestingModule } from "@abp/ng.theme.shared/testing";
import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { ComponentFixture, TestBed } from "@angular/core/testing";
import { NgxValidateCoreModule } from "@ngx-validate/core"; import { NgxValidateCoreModule } from "@ngx-validate/core";
import { HomeComponent } from "./home.component"; import { HomeComponent } from "./home.component";
import { OAuthService } from 'angular-oauth2-oidc'; import { OAuthService } from 'angular-oauth2-oidc';
import { AuthService } from '@abp/ng.core'; import { AuthService } from '@abp/ng.core';
import { vi } from 'vitest';
describe("HomeComponent", () => { describe("HomeComponent", () => {
let fixture: ComponentFixture<HomeComponent>; let fixture: ComponentFixture<HomeComponent>;
const mockOAuthService = jasmine.createSpyObj('OAuthService', ['hasValidAccessToken']) const mockOAuthService = {
const mockAuthService = jasmine.createSpyObj('AuthService', ['navigateToLogin']) hasValidAccessToken: vi.fn()
beforeEach( };
waitForAsync(() => { const mockAuthService = {
TestBed.configureTestingModule({ navigateToLogin: vi.fn()
declarations: [], };
imports: [
CoreTestingModule.withConfig(), beforeEach(async () => {
ThemeSharedTestingModule.withConfig(), await TestBed.configureTestingModule({
NgxValidateCoreModule, declarations: [],
HomeComponent imports: [
], CoreTestingModule.withConfig(),
providers: [ ThemeSharedTestingModule.withConfig(),
/* mock providers here */ NgxValidateCoreModule,
{ HomeComponent
provide: OAuthService, ],
useValue: mockOAuthService providers: [
}, /* mock providers here */
{ {
provide: AuthService, provide: OAuthService,
useValue: mockAuthService useValue: mockOAuthService
} },
], {
}).compileComponents(); provide: AuthService,
}) useValue: mockAuthService
); }
],
}).compileComponents();
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(HomeComponent); fixture = TestBed.createComponent(HomeComponent);
@ -44,55 +48,49 @@ describe("HomeComponent", () => {
expect(fixture.componentInstance).toBeTruthy(); expect(fixture.componentInstance).toBeTruthy();
}); });
describe('when login state is true', () => { describe('when login state is true', () => {
beforeAll(() => { beforeAll(() => {
mockOAuthService.hasValidAccessToken.and.returnValue(true) mockOAuthService.hasValidAccessToken.mockReturnValue(true);
}); });
it("hasLoggedIn should be true", () => { it("hasLoggedIn should be true", () => {
expect(fixture.componentInstance.hasLoggedIn).toBe(true);
expect(fixture.componentInstance.hasLoggedIn).toBeTrue(); expect(mockOAuthService.hasValidAccessToken).toHaveBeenCalled();
expect(mockOAuthService.hasValidAccessToken).toHaveBeenCalled() });
})
it("button should not be exists", () => { it("button should not be exists", () => {
const element = fixture.nativeElement const element = fixture.nativeElement;
const button = element.querySelector('[role="button"]') const button = element.querySelector('[role="button"]');
expect(button).toBeNull() expect(button).toBeNull();
}) });
});
})
describe('when login state is false', () => { describe('when login state is false', () => {
beforeAll(() => { beforeAll(() => {
mockOAuthService.hasValidAccessToken.and.returnValue(false) mockOAuthService.hasValidAccessToken.mockReturnValue(false);
}); });
it("hasLoggedIn should be false", () => { it("hasLoggedIn should be false", () => {
expect(fixture.componentInstance.hasLoggedIn).toBe(false);
expect(fixture.componentInstance.hasLoggedIn).toBeFalse(); expect(mockOAuthService.hasValidAccessToken).toHaveBeenCalled();
expect(mockOAuthService.hasValidAccessToken).toHaveBeenCalled() });
})
it("button should be exists", () => { it("button should be exists", () => {
const element = fixture.nativeElement const element = fixture.nativeElement;
const button = element.querySelector('[role="button"]') const button = element.querySelector('[role="button"]');
expect(button).toBeDefined() expect(button).toBeDefined();
}) });
describe('when button clicked', () => {
describe('when button clicked', () => {
beforeEach(() => { beforeEach(() => {
const element = fixture.nativeElement const element = fixture.nativeElement;
const button = element.querySelector('[role="button"]') const button = element.querySelector('[role="button"]');
button.click() button.click();
}); });
it("navigateToLogin have been called", () => { it("navigateToLogin have been called", () => {
expect(mockAuthService.navigateToLogin).toHaveBeenCalled() expect(mockAuthService.navigateToLogin).toHaveBeenCalled();
}) });
}) });
}) });
}); });

79
templates/app/angular/src/app/home/home.component.ts

@ -1,89 +1,16 @@
import {AuthService, LocalizationPipe} from '@abp/ng.core'; import { AuthService, LocalizationPipe } from '@abp/ng.core';
import { Component, inject } from '@angular/core'; import { Component, inject } from '@angular/core';
import {NgTemplateOutlet} from "@angular/common"; import { NgTemplateOutlet } from "@angular/common";
import {DynamicFormComponent, FormFieldConfig} from "@abp/ng.components/dynamic-form";
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
templateUrl: './home.component.html', templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'], styleUrls: ['./home.component.scss'],
imports: [NgTemplateOutlet, LocalizationPipe, DynamicFormComponent] imports: [NgTemplateOutlet, LocalizationPipe]
}) })
export class HomeComponent { export class HomeComponent {
private authService = inject(AuthService); private authService = inject(AuthService);
formFields: FormFieldConfig[] = [
{
key: 'firstName',
type: 'text',
label: 'First Name',
placeholder: 'Enter first name',
value: 'erdemc',
required: true,
validators: [
{ type: 'required', message: 'First name is required' },
{ type: 'minLength', value: 2, message: 'Minimum 2 characters required' }
],
gridSize: 6,
order: 1
},
{
key: 'lastName',
type: 'text',
label: 'Last Name',
placeholder: 'Enter last name',
required: true,
validators: [
{ type: 'required', message: 'Last name is required' }
],
gridSize: 12,
order: 3
},
{
key: 'email',
type: 'email',
label: 'Email Address',
placeholder: 'Enter email',
required: true,
validators: [
{ type: 'required', message: 'Email is required' },
{ type: 'email', message: 'Please enter a valid email' }
],
gridSize: 6,
order: 2
},
{
key: 'userType',
type: 'select',
label: 'User Type',
required: true,
options: [
{ key: 'admin', value: 'Administrator' },
{ key: 'user', value: 'Regular User' },
{ key: 'guest', value: 'Guest User' }
],
validators: [
{ type: 'required', message: 'Please select user type' }
],
order: 4
},
{
key: 'adminNotes',
type: 'textarea',
label: 'Admin Notes',
placeholder: 'Enter admin-specific notes',
conditionalLogic: [
{
dependsOn: 'userType',
condition: 'equals',
value: 'admin',
action: 'show'
}
],
order: 5
}
];
get hasLoggedIn(): boolean { get hasLoggedIn(): boolean {
return this.authService.isAuthenticated; return this.authService.isAuthenticated;
} }

13
templates/app/angular/src/test.ts

@ -1,13 +0,0 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);

13
templates/app/angular/tsconfig.spec.json

@ -1,18 +1,15 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */ /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "./out-tsc/spec", "outDir": "./out-tsc/spec",
"types": [ "types": [
"jasmine" "vitest/globals"
] ]
}, },
"files": [
"src/test.ts",
"src/polyfills.ts"
],
"include": [ "include": [
"src/**/*.spec.ts", "src/**/*.d.ts",
"src/**/*.d.ts" "src/**/*.spec.ts"
] ]
} }

Loading…
Cancel
Save