Browse Source

update: theme-shared package tests

pull/24530/head
sumeyye 3 weeks ago
parent
commit
96adcef3fe
  1. 2
      npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.ts
  2. 81
      npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts
  3. 2
      npm/ng-packs/packages/theme-shared/src/lib/tests/button.component.spec.ts
  4. 2
      npm/ng-packs/packages/theme-shared/src/lib/tests/card.component.spec.ts
  5. 2
      npm/ng-packs/packages/theme-shared/src/lib/tests/checkbox.component.spec.ts
  6. 80
      npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts
  7. 58
      npm/ng-packs/packages/theme-shared/src/lib/tests/ellipsis.directive.spec.ts
  8. 111
      npm/ng-packs/packages/theme-shared/src/lib/tests/error.component.spec.ts
  9. 43
      npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts
  10. 2
      npm/ng-packs/packages/theme-shared/src/lib/tests/form-input.component.spec.ts
  11. 149
      npm/ng-packs/packages/theme-shared/src/lib/tests/loader-bar.component.spec.ts
  12. 25
      npm/ng-packs/packages/theme-shared/src/lib/tests/loading.directive.spec.ts
  13. 48
      npm/ng-packs/packages/theme-shared/src/lib/tests/modal.component.spec.ts
  14. 54
      npm/ng-packs/packages/theme-shared/src/lib/tests/test-utils.ts
  15. 26
      npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts
  16. 2
      npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts
  17. 38
      npm/ng-packs/packages/theme-shared/src/test-setup.ts
  18. 1
      npm/ng-packs/packages/theme-shared/vitest.config.mts

2
npm/ng-packs/packages/theme-shared/src/lib/components/breadcrumb-items/breadcrumb-items.component.ts

@ -6,7 +6,7 @@ import { ABP, LocalizationPipe } from '@abp/ng.core';
@Component({
selector: 'abp-breadcrumb-items',
templateUrl: './breadcrumb-items.component.html',
imports: [ NgTemplateOutlet, RouterLink, LocalizationPipe],
imports: [NgTemplateOutlet, RouterLink, LocalizationPipe],
})
export class BreadcrumbItemsComponent {
@Input() items: Partial<ABP.Route>[] = [];

81
npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts

@ -1,31 +1,25 @@
import {
ABP,
CORE_OPTIONS,
LocalizationPipe,
RouterOutletComponent,
RoutesService,
LocalizationService,
provideAbpCore,
withOptions,
RestService,
AbpApplicationConfigurationService,
ConfigStateService,
} from '@abp/ng.core';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { RouterModule } from '@angular/router';
import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest';
import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/vitest';
import { of } from 'rxjs';
import { BreadcrumbComponent, BreadcrumbItemsComponent } from '../components';
import { OTHERS_GROUP } from '@abp/ng.core';
import { SORT_COMPARE_FUNC } from '@abp/ng.core';
import { setupComponentResources } from './test-utils';
const mockRoutes: ABP.Route[] = [
{ name: '_::Identity', path: '/identity' },
{ name: '_::Users', path: '/identity/users', parentName: '_::Identity' },
];
// Simple compare function that doesn't use inject()
const simpleCompareFunc = (a: any, b: any) => {
const aNumber = a.order || 0;
const bNumber = b.order || 0;
return aNumber - bNumber;
};
describe('BreadcrumbComponent', () => {
let spectator: SpectatorRouting<RouterOutletComponent>;
let routes: RoutesService;
@ -34,39 +28,58 @@ describe('BreadcrumbComponent', () => {
component: RouterOutletComponent,
stubsEnabled: false,
detectChanges: false,
imports: [
RouterModule,
LocalizationPipe,
BreadcrumbComponent,
BreadcrumbItemsComponent,
],
providers: [
provideHttpClient(),
provideHttpClientTesting(),
{
provide: CORE_OPTIONS,
useValue: {
provideAbpCore(
withOptions({
environment: {
apis: {
default: {
url: 'http://localhost:4200',
},
},
application: {
name: 'TestApp',
baseUrl: 'http://localhost:4200',
},
},
}
registerLocaleFn: () => Promise.resolve(),
skipGetAppConfiguration: true,
}),
),
{
provide: RestService,
useValue: {
request: vi.fn(),
handleError: vi.fn(),
},
},
RoutesService,
LocalizationService,
{
provide: OTHERS_GROUP,
useValue: 'AbpUi::OthersGroup',
provide: AbpApplicationConfigurationService,
useValue: {
get: vi.fn(),
},
},
{
provide: SORT_COMPARE_FUNC,
useValue: simpleCompareFunc,
provide: ConfigStateService,
useValue: {
getOne: vi.fn(),
getAll: vi.fn(() => ({})),
getAll$: vi.fn(() => of({})),
getDeep: vi.fn(),
getDeep$: vi.fn(() => of(undefined)),
createOnUpdateStream: vi.fn(() => ({
subscribe: vi.fn(() => ({ unsubscribe: vi.fn() }))
})),
refreshAppState: vi.fn(),
},
},
],
declarations: [],
imports: [
RouterModule,
LocalizationPipe,
BreadcrumbComponent,
BreadcrumbItemsComponent,
],
routes: [
{
path: '',
@ -85,6 +98,8 @@ describe('BreadcrumbComponent', () => {
],
});
beforeAll(() => setupComponentResources('../components/breadcrumb', import.meta.url));
beforeEach(() => {
spectator = createRouting();
routes = spectator.inject(RoutesService);

2
npm/ng-packs/packages/theme-shared/src/lib/tests/button.component.spec.ts

@ -1,4 +1,4 @@
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest';
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest';
import { ButtonComponent } from '../components';
describe('ButtonComponent', () => {

2
npm/ng-packs/packages/theme-shared/src/lib/tests/card.component.spec.ts

@ -1,4 +1,4 @@
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest';
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest';
import {
CardComponent,
CardBodyComponent,

2
npm/ng-packs/packages/theme-shared/src/lib/tests/checkbox.component.spec.ts

@ -1,4 +1,4 @@
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest';
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest';
import { FormCheckboxComponent } from '../components/checkbox/checkbox.component';
describe('FormCheckboxComponent', () => {

80
npm/ng-packs/packages/theme-shared/src/lib/tests/confirmation.service.spec.ts

@ -1,27 +1,24 @@
import { CoreTestingModule } from '@abp/ng.core/testing';
import { NgModule } from '@angular/core';
import { fakeAsync, tick } from '@angular/core/testing';
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { timer } from 'rxjs';
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest';
import { firstValueFrom, timer } from 'rxjs';
import { ConfirmationComponent } from '../components';
import { Confirmation } from '../models';
import { ConfirmationService } from '../services';
import { CONFIRMATION_ICONS, DEFAULT_CONFIRMATION_ICONS } from '../tokens/confirmation-icons.token';
@NgModule({
exports: [ConfirmationComponent],
declarations: [],
imports: [CoreTestingModule.withConfig(), ConfirmationComponent],
providers: [{ provide: CONFIRMATION_ICONS, useValue: DEFAULT_CONFIRMATION_ICONS }],
})
export class MockModule {}
import { setupComponentResources } from './test-utils';
describe('ConfirmationService', () => {
let spectator: SpectatorService<ConfirmationService>;
let service: ConfirmationService;
const createService = createServiceFactory({
service: ConfirmationService,
imports: [CoreTestingModule.withConfig(), MockModule],
imports: [CoreTestingModule.withConfig(), ConfirmationComponent],
providers: [{ provide: CONFIRMATION_ICONS, useValue: DEFAULT_CONFIRMATION_ICONS }],
});
beforeAll(async () => {
await setupComponentResources('../components/confirmation', import.meta.url);
});
beforeEach(() => {
@ -33,16 +30,16 @@ describe('ConfirmationService', () => {
clearElements();
});
test('should display a confirmation popup', fakeAsync(() => {
test('should display a confirmation popup', async () => {
service.show('_::MESSAGE', '_::TITLE');
tick();
await firstValueFrom(timer(10));
expect(selectConfirmationContent('.title')).toBe('TITLE');
expect(selectConfirmationContent('.message')).toBe('MESSAGE');
}));
});
test('should display HTML string in title, message, and buttons', fakeAsync(() => {
test('should display HTML string in title, message, and buttons', async () => {
service.show(
'_::<span class="custom-message">MESSAGE<span>',
'_::<span class="custom-title">TITLE<span>',
@ -53,24 +50,24 @@ describe('ConfirmationService', () => {
},
);
tick();
await firstValueFrom(timer(10));
expect(selectConfirmationContent('.custom-title')).toBe('TITLE');
expect(selectConfirmationContent('.custom-message')).toBe('MESSAGE');
expect(selectConfirmationContent('.custom-cancel')).toBe('CANCEL');
expect(selectConfirmationContent('.custom-yes')).toBe('YES');
}));
});
test('should display custom FA icon', fakeAsync(() => {
test('should display custom FA icon', async () => {
service.show('_::MESSAGE', '_::TITLE', undefined, {
icon: 'fa fa-info',
});
tick();
await firstValueFrom(timer(10));
expect(selectConfirmationElement('.icon').className).toBe('icon fa fa-info');
}));
});
test('should display custom icon as html element', fakeAsync(() => {
test('should display custom icon as html element', async () => {
const className = 'custom-icon';
const selector = '.' + className;
@ -78,12 +75,14 @@ describe('ConfirmationService', () => {
iconTemplate: `<span class="${className}">I am icon</span>`,
});
tick();
await firstValueFrom(timer(10));
const element = selectConfirmationElement(selector);
expect(element).toBeTruthy();
expect(element.innerHTML).toBe('I am icon');
}));
});
test.each`
type | selector | icon
${'info'} | ${'.info'} | ${'.fa-info-circle'}
@ -93,7 +92,7 @@ describe('ConfirmationService', () => {
`('should display $type confirmation popup', async ({ type, selector, icon }) => {
service[type]('_::MESSAGE', '_::TITLE');
await timer(0).toPromise();
await firstValueFrom(timer(10));
expect(selectConfirmationContent('.title')).toBe('TITLE');
expect(selectConfirmationContent('.message')).toBe('MESSAGE');
@ -101,31 +100,18 @@ describe('ConfirmationService', () => {
expect(selectConfirmationElement(icon)).toBeTruthy();
});
// test('should close with ESC key', (done) => {
// service
// .info('', '')
// .pipe(take(1))
// .subscribe((status) => {
// expect(status).toBe(Confirmation.Status.dismiss);
// done();
// });
// const escape = new KeyboardEvent('keyup', { key: 'Escape' });
// document.dispatchEvent(escape);
// });
test('should close when click cancel button', done => {
test('should close when click cancel button', async () => {
service.info('_::', '_::', { yesText: '_::Sure', cancelText: '_::Exit' }).subscribe(status => {
expect(status).toBe(Confirmation.Status.reject);
done();
});
timer(0).subscribe(() => {
expect(selectConfirmationContent('button#cancel')).toBe('Exit');
expect(selectConfirmationContent('button#confirm')).toBe('Sure');
await firstValueFrom(timer(10));
(document.querySelector('button#cancel') as HTMLButtonElement).click();
});
expect(selectConfirmationContent('button#cancel')).toBe('Exit');
expect(selectConfirmationContent('button#confirm')).toBe('Sure');
(document.querySelector('button#cancel') as HTMLButtonElement).click();
});
test.each`
@ -135,9 +121,9 @@ describe('ConfirmationService', () => {
`(
'should call the listenToEscape method $count times when dismissible is $dismissible',
({ dismissible, count }) => {
const spy = jest.spyOn(service as any, 'listenToEscape');
const spy = vi.spyOn(service as any, 'listenToEscape');
service.info('_::', '_::', { dismissible });
service.info('_::', '_::', { dismissible });
expect(spy).toHaveBeenCalledTimes(count);
},

58
npm/ng-packs/packages/theme-shared/src/lib/tests/ellipsis.directive.spec.ts

@ -1,4 +1,4 @@
import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest';
import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest';
import { EllipsisDirective } from '../directives/ellipsis.directive';
describe('EllipsisDirective', () => {
@ -39,17 +39,61 @@ describe('EllipsisDirective', () => {
expect(directive.title).toBe('test title');
});
test('should have element innerText as title if not specified', () => {
spectator.setHostInput({ title: undefined });
test('should add abp-ellipsis-inline class to element if width is given', () => {
expect(el).toHaveClass('abp-ellipsis-inline');
});
});
describe('EllipsisDirective when title is not specified', () => {
let spectator: SpectatorDirective<EllipsisDirective>;
let directive: EllipsisDirective;
let el: HTMLDivElement;
const createDirective = createDirectiveFactory({
directive: EllipsisDirective,
});
beforeEach(() => {
spectator = createDirective(
'<div [abpEllipsis]="width" [abpEllipsisEnabled]="true" [title]="title">test content</div>',
{
hostProps: {
title: undefined,
width: '100px',
},
},
);
directive = spectator.directive;
el = spectator.query('div') as HTMLDivElement;
});
test('should have element innerText as title', () => {
expect(directive.title).toBe(el.innerText);
});
});
test('should add abp-ellipsis-inline class to element if width is given', () => {
expect(el).toHaveClass('abp-ellipsis-inline');
describe('EllipsisDirective when width is not given', () => {
let spectator: SpectatorDirective<EllipsisDirective>;
let directive: EllipsisDirective;
let el: HTMLDivElement;
const createDirective = createDirectiveFactory({
directive: EllipsisDirective,
});
beforeEach(() => {
spectator = createDirective(
'<div [abpEllipsis]="width" [abpEllipsisEnabled]="true" [title]="title">test content</div>',
{
hostProps: {
title: 'test title',
width: undefined,
},
},
);
directive = spectator.directive;
el = spectator.query('div') as HTMLDivElement;
});
test('should add abp-ellipsis class to element if width is not given', () => {
spectator.setHostInput({ width: undefined });
test('should add abp-ellipsis class to element', () => {
expect(el).toHaveClass('abp-ellipsis');
});
});

111
npm/ng-packs/packages/theme-shared/src/lib/tests/error.component.spec.ts

@ -1,49 +1,84 @@
import { CORE_OPTIONS, LocalizationPipe } from '@abp/ng.core';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { ElementRef, Renderer2 } from '@angular/core';
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest';
import { DOCUMENT } from '@angular/common';
import { Router } from '@angular/router';
import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest';
import { Pipe, PipeTransform } from '@angular/core';
import { Subject } from 'rxjs';
import { vi } from 'vitest';
import { HttpErrorWrapperComponent } from '../components/http-error-wrapper/http-error-wrapper.component';
import { setupComponentResources } from './test-utils';
/**
* Mock pipe to avoid ABP DI chain
*/
@Pipe({ name: 'abpLocalization'})
class MockLocalizationPipe implements PipeTransform {
transform(value: any): any {
return value;
}
}
describe('HttpErrorWrapperComponent', () => {
let spectator: Spectator<HttpErrorWrapperComponent>;
let createComponent: ReturnType<typeof createComponentFactory<HttpErrorWrapperComponent>>;
describe('ErrorComponent', () => {
let spectator: SpectatorHost<HttpErrorWrapperComponent>;
const createHost = createHostFactory({
component: HttpErrorWrapperComponent,
declarations: [],
mocks: [HttpClient],
providers: [
{ provide: CORE_OPTIONS, useValue: {} },
{ provide: Renderer2, useValue: { removeChild: () => null } },
{
provide: ElementRef,
useValue: { nativeElement: document.createElement('div') },
},
],
imports: [HttpClientModule, LocalizationPipe],
beforeAll(async () => {
await setupComponentResources(
'../components/http-error-wrapper',
import.meta.url,
);
});
beforeEach(() => {
spectator = createHost(
'<abp-http-error-wrapper title="_::Oops!" details="_::Sorry, an error has occured."></abp-http-error-wrapper>',
);
spectator.component.destroy$ = new Subject();
});
describe('#destroy', () => {
it('should be call when pressed the esc key', done => {
spectator.component.destroy$.subscribe(() => {
done();
});
beforeEach(() => {
if (!createComponent) {
createComponent = createComponentFactory({
component: HttpErrorWrapperComponent,
detectChanges: false,
spectator.keyboard.pressEscape();
});
overrideComponents: [
[
HttpErrorWrapperComponent,
{
set: {
template: '<div></div>',
imports: [MockLocalizationPipe],
},
},
],
],
it('should be call when clicked the close button', done => {
spectator.component.destroy$.subscribe(() => {
done();
providers: [
{
provide: DOCUMENT,
useValue: document,
},
{
provide: Router,
useValue: {
navigateByUrl: vi.fn(),
},
},
],
});
}
spectator = createComponent();
spectator.component.destroy$ = new Subject<void>();
spectator.component.title = '_::Oops!';
spectator.component.details = '_::Sorry, an error has occured.';
});
it('should create component', () => {
expect(spectator.component).toBeTruthy();
});
it('should emit destroy$ when destroy is called', () => {
const spy = vi.fn();
spectator.component.destroy$.subscribe(spy);
spectator.component.destroy();
spectator.click('#abp-close-button');
});
expect(spy).toHaveBeenCalled();
});
});

43
npm/ng-packs/packages/theme-shared/src/lib/tests/error.handler.spec.ts

@ -2,46 +2,48 @@ import { HttpErrorReporterService } from '@abp/ng.core';
import { CoreTestingModule } from '@abp/ng.core/testing';
import { APP_BASE_HREF } from '@angular/common';
import { HttpErrorResponse, HttpHeaders } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest';
import { OAuthService } from 'angular-oauth2-oidc';
import { of, Subject } from 'rxjs';
import { HttpErrorWrapperComponent } from '../components/http-error-wrapper/http-error-wrapper.component';
import { ErrorHandler } from '../handlers';
import { ConfirmationService } from '../services';
import { CreateErrorComponentService } from '../services/create-error-component.service';
import { RouterErrorHandlerService } from '../services/router-error-handler.service';
import { CUSTOM_ERROR_HANDLERS, HTTP_ERROR_CONFIG } from '../tokens/http-error.token';
import { CustomHttpErrorHandlerService } from '../models';
const customHandlerMock: CustomHttpErrorHandlerService = {
priority: 100,
canHandle: jest.fn().mockReturnValue(true),
execute: jest.fn(),
canHandle: vi.fn().mockReturnValue(true),
execute: vi.fn(),
};
const reporter$ = new Subject();
@NgModule({
exports: [HttpErrorWrapperComponent],
declarations: [],
imports: [CoreTestingModule, HttpErrorWrapperComponent],
})
class MockModule {}
let spectator: SpectatorService<ErrorHandler>;
let service: ErrorHandler;
let httpErrorReporter: HttpErrorReporterService;
const errorConfirmation: jest.Mock = jest.fn(() => of(null));
const CONFIRMATION_BUTTONS = {
hideCancelBtn: true,
yesText: 'AbpAccount::Close',
};
const errorConfirmation = vi.fn(() => of(null));
describe('ErrorHandler', () => {
const createService = createServiceFactory({
service: ErrorHandler,
imports: [CoreTestingModule.withConfig(), MockModule],
imports: [CoreTestingModule.withConfig()],
mocks: [OAuthService],
providers: [
{
provide: RouterErrorHandlerService,
useValue: {
listen: vi.fn(),
},
},
{
provide: CreateErrorComponentService,
useValue: {
execute: vi.fn(),
},
},
{
provide: HttpErrorReporterService,
useValue: {
@ -65,7 +67,10 @@ describe('ErrorHandler', () => {
},
{
provide: HTTP_ERROR_CONFIG,
useFactory: () => ({}),
useValue: {
skipHandledErrorCodes: [],
errorScreen: {},
},
},
],
});

2
npm/ng-packs/packages/theme-shared/src/lib/tests/form-input.component.spec.ts

@ -1,4 +1,4 @@
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest';
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest';
import { FormInputComponent } from '../components/form-input/form-input.component';

149
npm/ng-packs/packages/theme-shared/src/lib/tests/loader-bar.component.spec.ts

@ -1,26 +1,32 @@
import { HttpWaitService, LOADER_DELAY, SubscriptionService } from '@abp/ng.core';
import { HttpWaitService, LOADER_DELAY, RouterWaitService, SubscriptionService } from '@abp/ng.core';
import { HttpRequest } from '@angular/common/http';
import { NavigationEnd, NavigationError, NavigationStart, Router } from '@angular/router';
import { createComponentFactory, Spectator, SpyObject } from '@ngneat/spectator/jest';
import { Subject, timer } from 'rxjs';
import { NavigationStart, Router } from '@angular/router';
import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest';
import { combineLatest, firstValueFrom, Subject, timer } from 'rxjs';
import { LoaderBarComponent } from '../components/loader-bar/loader-bar.component';
import { setupComponentResources } from './test-utils';
describe('LoaderBarComponent', () => {
let spectator: Spectator<LoaderBarComponent>;
let router: Router;
let createComponent: ReturnType<typeof createComponentFactory<LoaderBarComponent>>;
const events$ = new Subject();
const createComponent = createComponentFactory({
component: LoaderBarComponent,
detectChanges: false,
providers: [
SubscriptionService,
{ provide: Router, useValue: { events: events$ } },
{ provide: LOADER_DELAY, useValue: 0 },
],
});
beforeAll(() => setupComponentResources('../components/loader-bar', import.meta.url));
beforeEach(() => {
if (!createComponent) {
createComponent = createComponentFactory({
component: LoaderBarComponent,
detectChanges: false,
providers: [
SubscriptionService,
{ provide: Router, useValue: { events: events$ } },
{ provide: LOADER_DELAY, useValue: 0 },
],
});
}
spectator = createComponent({});
spectator.component.intervalPeriod = 1;
spectator.component.stopDelay = 1;
@ -32,66 +38,127 @@ describe('LoaderBarComponent', () => {
expect(spectator.component.color).toBe('#77b6ff');
});
it('should increase the progressLevel', done => {
it('should increase the progressLevel', async () => {
spectator.detectChanges();
const httpWaitService = spectator.inject(HttpWaitService);
httpWaitService.addRequest(new HttpRequest('GET', 'test'));
spectator.detectChanges();
setTimeout(() => {
expect(spectator.component.progressLevel > 0).toBeTruthy();
done();
}, 10);
await new Promise(resolve => setTimeout(resolve, 10));
expect(spectator.component.progressLevel > 0).toBeTruthy();
});
it('should be interval unsubscribed', done => {
const request = new HttpRequest('GET', 'test');
it('should be interval unsubscribed', async () => {
const request = new HttpRequest('GET', 'test');
spectator.detectChanges();
const httpWaitService = spectator.inject(HttpWaitService);
await firstValueFrom(combineLatest([
httpWaitService.getLoading$(),
spectator.inject(RouterWaitService).getLoading$()
]));
httpWaitService.addRequest(request);
spectator.detectChanges();
let attempts = 0;
while (spectator.component.interval.closed && attempts < 50) {
await new Promise(resolve => setTimeout(resolve, 10));
spectator.detectChanges();
attempts++;
}
expect(spectator.component.interval.closed).toBe(false);
httpWaitService.deleteRequest(request);
timer(400).subscribe(() => {
expect(spectator.component.interval.closed).toBe(true);
done();
});
spectator.detectChanges();
await firstValueFrom(timer(400));
expect(spectator.component.interval.closed).toBe(true);
});
it('should start and stop the loading with navigation', done => {
it('should start and stop the loading with navigation', async () => {
spectator.detectChanges();
const routerWaitService = spectator.inject(RouterWaitService);
routerWaitService.setLoading(true);
spectator.detectChanges();
events$.next(new NavigationStart(1, 'test'));
let attempts = 0;
while (spectator.component.interval.closed && attempts < 50) {
await new Promise(resolve => setTimeout(resolve, 10));
spectator.detectChanges();
attempts++;
}
expect(spectator.component.interval.closed).toBe(false);
events$.next(new NavigationEnd(1, 'test', 'test'));
events$.next(new NavigationError(1, 'test', 'test'));
routerWaitService.setLoading(false);
spectator.detectChanges();
attempts = 0;
while (spectator.component.progressLevel !== 100 && attempts < 50) {
await new Promise(resolve => setTimeout(resolve, 10));
spectator.detectChanges();
attempts++;
}
expect(spectator.component.progressLevel).toBe(100);
timer(2).subscribe(() => {
expect(spectator.component.progressLevel).toBe(0);
done();
});
await firstValueFrom(timer(spectator.component.stopDelay + 10));
expect(spectator.component.progressLevel).toBe(0);
});
it('should stop the loading with navigation', done => {
it('should stop the loading with navigation', async () => {
spectator.detectChanges();
const routerWaitService = spectator.inject(RouterWaitService);
routerWaitService.setLoading(true);
spectator.detectChanges();
events$.next(new NavigationStart(1, 'test'));
let attempts = 0;
while (spectator.component.interval.closed && attempts < 50) {
await new Promise(resolve => setTimeout(resolve, 10));
spectator.detectChanges();
attempts++;
}
expect(spectator.component.interval.closed).toBe(false);
events$.next(new NavigationEnd(1, 'testend', 'testend'));
routerWaitService.setLoading(false);
spectator.detectChanges();
attempts = 0;
while (spectator.component.progressLevel !== 100 && attempts < 50) {
await new Promise(resolve => setTimeout(resolve, 10));
spectator.detectChanges();
attempts++;
}
expect(spectator.component.progressLevel).toBe(100);
timer(2).subscribe(() => {
expect(spectator.component.progressLevel).toBe(0);
done();
});
await firstValueFrom(timer(spectator.component.stopDelay + 10));
expect(spectator.component.progressLevel).toBe(0);
});
describe('#startLoading', () => {
it('should return when isLoading is true', done => {
it('should return when isLoading is true', async () => {
spectator.detectChanges();
events$.next(new NavigationStart(1, 'test'));
spectator.detectChanges();
let attempts = 0;
while (spectator.component.interval.closed && attempts < 50) {
await new Promise(resolve => setTimeout(resolve, 10));
spectator.detectChanges();
attempts++;
}
events$.next(new NavigationStart(1, 'test'));
done();
spectator.detectChanges();
expect(spectator.component).toBeTruthy();
});
});
});

25
npm/ng-packs/packages/theme-shared/src/lib/tests/loading.directive.spec.ts

@ -1,4 +1,4 @@
import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/jest';
import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/vitest';
import { LoadingDirective } from '../directives';
import { LoadingComponent } from '../components';
import { Component } from '@angular/core';
@ -29,10 +29,11 @@ describe('LoadingDirective', () => {
expect(spectator.directive).toBeTruthy();
});
it('should handle loading input', () => {
spectator.setHostInput({ loading: false });
spectator.detectChanges();
it('should handle loading input', async () => {
spectator.directive.loading = false;
await new Promise(resolve => setTimeout(resolve, 10));
expect(spectator.directive).toBeTruthy();
expect(spectator.directive.loading).toBe(false);
});
});
@ -53,19 +54,19 @@ describe('LoadingDirective', () => {
expect(spectator.directive.targetElement).toBe(mockTarget);
});
it('should handle delay input', () => {
spectator.setHostInput({ delay: 100 });
spectator.detectChanges();
it('should handle delay input', async () => {
spectator.directive.delay = 100;
await new Promise(resolve => setTimeout(resolve, 10));
expect(spectator.directive).toBeTruthy();
});
it('should handle loading state changes', () => {
spectator.setHostInput({ loading: false });
spectator.detectChanges();
it('should handle loading state changes', async() => {
spectator.directive.loading = false;
await new Promise(resolve => setTimeout(resolve, 10));
expect(spectator.directive).toBeTruthy();
spectator.setHostInput({ loading: true });
spectator.detectChanges();
spectator.directive.loading = true;
await new Promise(resolve => setTimeout(resolve, 10));
expect(spectator.directive).toBeTruthy();
});
});

48
npm/ng-packs/packages/theme-shared/src/lib/tests/modal.component.spec.ts

@ -1,10 +1,11 @@
import { ConfirmationService } from '@abp/ng.theme.shared';
import { CoreTestingModule } from '@abp/ng.core/testing';
import { Component, EventEmitter, Input } from '@angular/core';
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest';
import { Confirmation } from '@abp/ng.theme.shared';
import { Subject, timer } from 'rxjs';
import { firstValueFrom, Subject, timer } from 'rxjs';
import { ModalComponent } from '../components/modal/modal.component';
import { setupComponentResources } from './test-utils';
@Component({
template: `
@ -27,25 +28,34 @@ class TestHostComponent {
}
const mockConfirmation$ = new Subject<Confirmation.Status>();
const disappearFn = jest.fn();
const disappearFn = vi.fn();
describe('ModalComponent', () => {
let spectator: Spectator<TestHostComponent>;
let createComponent: ReturnType<typeof createComponentFactory<TestHostComponent>>;
const createComponent = createComponentFactory({
component: TestHostComponent,
imports: [CoreTestingModule.withConfig()],
providers: [
{
provide: ConfirmationService,
useValue: {
warn: jest.fn(() => mockConfirmation$),
},
},
],
});
beforeAll(() => setupComponentResources('../components/modal', import.meta.url));
beforeEach(() => {
// Create component factory in beforeEach to ensure beforeAll has run
if (!createComponent) {
createComponent = createComponentFactory({
component: TestHostComponent,
imports: [
CoreTestingModule.withConfig(),
ModalComponent,
],
providers: [
{
provide: ConfirmationService,
useValue: {
warn: vi.fn(() => mockConfirmation$),
},
},
],
});
}
spectator = createComponent();
disappearFn.mockClear();
});
@ -71,10 +81,10 @@ describe('ModalComponent', () => {
});
});
async function wait0ms() {
await timer(0).toPromise();
async function wait0ms() {
await firstValueFrom(timer(0));
}
async function wait300ms() {
await timer(300).toPromise();
async function wait300ms() {
await firstValueFrom(timer(300));
}

54
npm/ng-packs/packages/theme-shared/src/lib/tests/test-utils.ts

@ -0,0 +1,54 @@
import { readFileSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
/**
* Sets up component resource resolution for Angular component tests.
* This is needed when components have external templates or stylesheets.
*
* @param componentDirPath - The path to the component directory relative to the test file.
* For example: '../components/loader-bar' or './components/my-component'
* @param testFileUrl - The import.meta.url from the test file. Defaults to the caller's location.
*
* @example
* ```typescript
*
* import { setupComponentResources } from './test-utils';
*
* beforeAll(() => setupComponentResources('../components/loader-bar', import.meta.url));
* ```
*/
export async function setupComponentResources(
componentDirPath: string,
testFileUrl: string = import.meta.url,
): Promise<void> {
try {
if (typeof process !== 'undefined' && process.versions?.node) {
const { ɵresolveComponentResources: resolveComponentResources } = await import('@angular/core');
// Get the test file directory path
const testFileDir = dirname(fileURLToPath(testFileUrl));
const componentDir = resolve(testFileDir, componentDirPath);
await resolveComponentResources((url: string) => {
// For SCSS/SASS files, return empty CSS since jsdom can't parse SCSS
if (url.endsWith('.scss') || url.endsWith('.sass')) {
return Promise.resolve('');
}
// For other files (HTML, CSS, etc.), read the actual content
try {
// Resolve relative paths like './component.scss' or 'component.scss'
const normalizedUrl = url.replace(/^\.\//, '');
const filePath = resolve(componentDir, normalizedUrl);
return Promise.resolve(readFileSync(filePath, 'utf-8'));
} catch (error) {
// If file not found, return empty string
return Promise.resolve('');
}
});
}
} catch (error) {
console.warn('Failed to set up component resource resolver:', error);
}
}

26
npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts

@ -1,23 +1,25 @@
import { CoreTestingModule } from '@abp/ng.core/testing';
import { NgModule } from '@angular/core';
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/jest';
import { ContentProjectionService } from '@abp/ng.core';
import { ComponentRef } from '@angular/core';
import { createServiceFactory, SpectatorService } from '@ngneat/spectator/vitest';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { ToastContainerComponent } from '../components/toast-container/toast-container.component';
import { ToastComponent } from '../components/toast/toast.component';
import { ToasterService } from '../services/toaster.service';
@NgModule({
exports: [ToastContainerComponent],
declarations: [],
imports: [CoreTestingModule.withConfig(), ToastContainerComponent, ToastComponent],
})
export class MockModule {}
describe('ToasterService', () => {
let spectator: SpectatorService<ToasterService>;
let service: ToasterService;
const mockComponentRef = {
changeDetectorRef: { detectChanges: vi.fn() },
instance: {} as ToastContainerComponent,
} as unknown as ComponentRef<ToastContainerComponent>;
const contentProjectionService = {
projectContent: vi.fn().mockReturnValue(mockComponentRef),
} satisfies Partial<ContentProjectionService>;
const createService = createServiceFactory({
service: ToasterService,
imports: [CoreTestingModule.withConfig(), MockModule],
providers: [{ provide: ContentProjectionService, useValue: contentProjectionService }],
});
beforeEach(() => {

2
npm/ng-packs/packages/theme-shared/src/lib/tests/validation-utils.spec.ts

@ -2,7 +2,7 @@ import { AbpApplicationConfigurationService, ConfigStateService } from '@abp/ng.
import { CoreTestingModule } from '@abp/ng.core/testing';
import { HttpClient } from '@angular/common/http';
import { Component, Injector } from '@angular/core';
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest';
import { OAuthService } from 'angular-oauth2-oidc';
import { of } from 'rxjs';
import { getPasswordValidators, validatePassword } from '../utils';

38
npm/ng-packs/packages/theme-shared/src/test-setup.ts

@ -1,10 +1,28 @@
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
setupZoneTestEnv();
const originalError = console.error;
console.error = (...args: any[]) => {
if (args[0]?.includes?.('ExpressionChangedAfterItHasBeenCheckedError')) {
return;
}
originalError.apply(console, args);
};
import '@angular/compiler';
import 'zone.js';
import 'zone.js/testing';
import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';
import {
ɵgetCleanupHook as getCleanupHook,
getTestBed
} from '@angular/core/testing';
beforeEach(getCleanupHook(false));
afterEach(getCleanupHook(true));
// Initialize Angular testing environment
getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting());
// Mock window.location for test environment
Object.defineProperty(window, 'location', {
value: {
href: 'http://localhost:4200',
origin: 'http://localhost:4200',
pathname: '/',
search: '',
hash: '',
},
writable: true,
});

1
npm/ng-packs/packages/theme-shared/vitest.config.mts

@ -11,6 +11,7 @@ export default defineConfig(() => ({
watch: false,
globals: true,
environment: 'jsdom',
setupFiles: ['src/test-setup.ts'],
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {

Loading…
Cancel
Save