Browse Source

Merge pull request #25729 from abpframework/task/update-ng-ui-tests

Angular - Align the UI tests with the v22 upgrade
pull/25735/head
sumeyye 3 months ago
committed by GitHub
parent
commit
9b0a1c9208
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 51
      npm/ng-packs/package.json
  2. 37
      npm/ng-packs/packages/core/src/lib/services/local-storage.service.ts
  3. 30
      npm/ng-packs/packages/core/src/lib/tests/autofocus.directive.spec.ts
  4. 52
      npm/ng-packs/packages/core/src/lib/tests/capsLock.directive.spec.ts
  5. 31
      npm/ng-packs/packages/core/src/lib/tests/debounce.directive.spec.ts
  6. 6
      npm/ng-packs/packages/core/src/lib/tests/environment-utils.spec.ts
  7. 3
      npm/ng-packs/packages/core/src/lib/tests/factory-utils.spec.ts
  8. 177
      npm/ng-packs/packages/core/src/lib/tests/for.directive.spec.ts
  9. 26
      npm/ng-packs/packages/core/src/lib/tests/form-submit.directive.spec.ts
  10. 34
      npm/ng-packs/packages/core/src/lib/tests/local-storage.service.spec.ts
  11. 11
      npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts
  12. 68
      npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts
  13. 51
      npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts
  14. 37
      npm/ng-packs/packages/core/src/lib/tests/show-password-directive.spec.ts
  15. 9
      npm/ng-packs/packages/core/src/lib/tests/stop-propagation.directive.spec.ts
  16. 4
      npm/ng-packs/packages/core/src/lib/tests/utils/index.ts
  17. 6
      npm/ng-packs/packages/core/src/lib/tests/utils/input-signal.ts
  18. 8
      npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts
  19. 4
      npm/ng-packs/packages/theme-shared/src/lib/components/loader-bar/loader-bar.component.ts
  20. 2
      npm/ng-packs/packages/theme-shared/src/lib/directives/loading.directive.ts
  21. 7
      npm/ng-packs/packages/theme-shared/src/lib/tests/breadcrumb.component.spec.ts
  22. 28
      npm/ng-packs/packages/theme-shared/src/lib/tests/button.component.spec.ts
  23. 22
      npm/ng-packs/packages/theme-shared/src/lib/tests/card-body.component.spec.ts
  24. 24
      npm/ng-packs/packages/theme-shared/src/lib/tests/card-footer.component.spec.ts
  25. 22
      npm/ng-packs/packages/theme-shared/src/lib/tests/card-header.component.spec.ts
  26. 11
      npm/ng-packs/packages/theme-shared/src/lib/tests/card-header.directive.spec.ts
  27. 18
      npm/ng-packs/packages/theme-shared/src/lib/tests/checkbox.component.spec.ts
  28. 58
      npm/ng-packs/packages/theme-shared/src/lib/tests/ellipsis.directive.spec.ts
  29. 20
      npm/ng-packs/packages/theme-shared/src/lib/tests/form-input.component.spec.ts
  30. 22
      npm/ng-packs/packages/theme-shared/src/lib/tests/loader-bar.component.spec.ts
  31. 25
      npm/ng-packs/packages/theme-shared/src/lib/tests/loading.directive.spec.ts
  32. 22
      npm/ng-packs/packages/theme-shared/src/lib/tests/modal.component.spec.ts
  33. 7
      npm/ng-packs/packages/theme-shared/src/lib/tests/toaster.service.spec.ts
  34. 1
      npm/ng-packs/packages/theme-shared/src/lib/tests/utils/index.ts
  35. 6
      npm/ng-packs/packages/theme-shared/src/lib/tests/utils/input-signal.ts

51
npm/ng-packs/package.json

@ -32,7 +32,7 @@
"dep-graph": "nx dep-graph", "dep-graph": "nx dep-graph",
"help": "nx help", "help": "nx help",
"build:schematics": "cd scripts && yarn && yarn build:schematics && cd ..", "build:schematics": "cd scripts && yarn && yarn build:schematics && cd ..",
"dev:schematics": "tsc -p packages/schematics/tsconfig.lib.json -w", "dev:schematics": "tsc -p packages/schematics/tsconfig.json -w",
"mock:schematics": "cd scripts/mock-schematic && yarn && yarn start", "mock:schematics": "cd scripts/mock-schematic && yarn && yarn start",
"debug:schematics": "./node_modules/.bin/nx g ./packages/schematics/src/collection.json:proxy-add --module identity --apiName __default --source __default --target __default --url https://localhost:44305 --serviceType application --entryPoint __default ", "debug:schematics": "./node_modules/.bin/nx g ./packages/schematics/src/collection.json:proxy-add --module identity --apiName __default --source __default --target __default --url https://localhost:44305 --serviceType application --entryPoint __default ",
"debug:schematics-dist": "./node_modules/.bin/ng g ./dist/packages/schematics/collection.json:proxy-add --module __default --apiName __default --source __default --target __default --url http://localhost:4300 --service-type application --entryPoint __default", "debug:schematics-dist": "./node_modules/.bin/ng g ./dist/packages/schematics/collection.json:proxy-add --module __default --apiName __default --source __default --target __default --url http://localhost:4300 --service-type application --entryPoint __default",
@ -49,30 +49,31 @@
"private": true, "private": true,
"devDependencies": { "devDependencies": {
"@abp/ng.theme.lepton-x": "~5.5.0", "@abp/ng.theme.lepton-x": "~5.5.0",
"@abp/ng.schematics": "~10.5.0",
"@abp/utils": "~10.5.0", "@abp/utils": "~10.5.0",
"@angular-devkit/build-angular": "~21.2.0", "@angular-devkit/build-angular": "~22.0.0",
"@angular-devkit/core": "~21.2.0", "@angular-devkit/core": "~22.0.0",
"@angular-devkit/schematics": "~21.2.0", "@angular-devkit/schematics": "~22.0.0",
"@angular-devkit/schematics-cli": "~21.2.0", "@angular-devkit/schematics-cli": "~22.0.0",
"@angular-eslint/eslint-plugin": "~21.2.0", "@angular-eslint/eslint-plugin": "~22.0.0",
"@angular-eslint/eslint-plugin-template": "~21.2.0", "@angular-eslint/eslint-plugin-template": "~22.0.0",
"@angular-eslint/template-parser": "~21.2.0", "@angular-eslint/template-parser": "~22.0.0",
"@angular/animations": "~21.2.0", "@angular/animations": "~22.0.0",
"@angular/aria": "~21.2.0", "@angular/aria": "~22.0.0",
"@angular/build": "~21.2.0", "@angular/build": "~22.0.0",
"@angular/cli": "~21.2.0", "@angular/cli": "~22.0.0",
"@angular/common": "~21.2.0", "@angular/common": "~22.0.0",
"@angular/compiler": "~21.2.0", "@angular/compiler": "~22.0.0",
"@angular/compiler-cli": "~21.2.0", "@angular/compiler-cli": "~22.0.0",
"@angular/core": "~21.2.0", "@angular/core": "~22.0.0",
"@angular/forms": "~21.2.0", "@angular/forms": "~22.0.0",
"@angular/language-service": "~21.2.0", "@angular/language-service": "~22.0.0",
"@angular/localize": "~21.2.0", "@angular/localize": "~22.0.0",
"@angular/platform-browser": "~21.2.0", "@angular/platform-browser": "~22.0.0",
"@angular/platform-browser-dynamic": "~21.2.0", "@angular/platform-browser-dynamic": "~22.0.0",
"@angular/platform-server": "~21.2.0", "@angular/platform-server": "~22.0.0",
"@angular/router": "~21.2.0", "@angular/router": "~22.0.0",
"@angular/ssr": "~21.2.0", "@angular/ssr": "~22.0.0",
"@fortawesome/fontawesome-free": "^6.0.0", "@fortawesome/fontawesome-free": "^6.0.0",
"@ng-bootstrap/ng-bootstrap": "~20.0.0", "@ng-bootstrap/ng-bootstrap": "~20.0.0",
"@ngneat/spectator": "~19.6.2", "@ngneat/spectator": "~19.6.2",
@ -159,4 +160,4 @@
"dependencies": { "dependencies": {
"openid-client": "^6.6.4" "openid-client": "^6.6.4"
} }
} }

37
npm/ng-packs/packages/core/src/lib/services/local-storage.service.ts

@ -10,35 +10,36 @@ export class AbpLocalStorageService implements Storage {
constructor() { constructor() {
} }
[name: string]: any; [name: string]: any;
private get storage(): Storage | null {
if (!isPlatformBrowser(this.platformId) || typeof window === 'undefined') {
return null;
}
try {
return window.localStorage;
} catch {
return null;
}
}
get length(): number { get length(): number {
return isPlatformBrowser(this.platformId) ? localStorage.length : 0; return this.storage?.length || 0;
} }
clear(): void { clear(): void {
if (isPlatformBrowser(this.platformId)) { this.storage?.clear();
localStorage.clear();
}
} }
getItem(key: string): string | null { getItem(key: string): string | null {
if (!isPlatformBrowser(this.platformId)) { return this.storage?.getItem(key) || null;
return null;
}
return localStorage.getItem(key);
} }
key(index: number): string | null { key(index: number): string | null {
if (!isPlatformBrowser(this.platformId)) { return this.storage?.key(index) || null;
return null;
}
return localStorage.key(index);
} }
removeItem(key: string): void { removeItem(key: string): void {
if (isPlatformBrowser(this.platformId)) { this.storage?.removeItem(key);
localStorage.removeItem(key);
}
} }
setItem(key: string, value: string): void { setItem(key: string, value: string): void {
if (isPlatformBrowser(this.platformId)) { this.storage?.setItem(key, value);
localStorage.setItem(key, value);
}
} }
} }

30
npm/ng-packs/packages/core/src/lib/tests/autofocus.directive.spec.ts

@ -1,6 +1,6 @@
import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest';
import { AutofocusDirective } from '../directives/autofocus.directive'; import { AutofocusDirective } from '../directives/autofocus.directive';
import { timer } from 'rxjs'; import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
describe('AutofocusDirective', () => { describe('AutofocusDirective', () => {
let spectator: SpectatorDirective<AutofocusDirective>; let spectator: SpectatorDirective<AutofocusDirective>;
@ -11,26 +11,32 @@ describe('AutofocusDirective', () => {
}); });
beforeEach(() => { beforeEach(() => {
spectator = createDirective('<input [autofocus]="10" />', { vi.useFakeTimers();
hostProps: {},
}); spectator = createDirective('<input autofocus />');
directive = spectator.directive; directive = spectator.directive;
input = spectator.query('input'); input = spectator.query('input');
}); });
afterEach(() => {
if (vi.isFakeTimers()) {
vi.runOnlyPendingTimers();
}
vi.useRealTimers();
});
test('should be created', () => { test('should be created', () => {
expect(directive).toBeTruthy(); expect(directive).toBeTruthy();
}); });
test('should have 10ms delay', () => { test('should have 0ms delay', () => {
expect(directive.delay).toBe(10); expect(directive.delay()).toBe(0);
}); });
test('should focus element after given delay', () => { test('should focus element after default delay', () => {
timer(0).subscribe(() => expect('input').not.toBeFocused()); expect(input).not.toBeFocused();
timer(11).subscribe(() => {
expect('input').toBeFocused(); vi.runOnlyPendingTimers();
expect.hasAssertions(); expect(input).toBeFocused();
});
}); });
}); });

52
npm/ng-packs/packages/core/src/lib/tests/capsLock.directive.spec.ts

@ -4,7 +4,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TrackCapsLockDirective } from '../directives'; import { TrackCapsLockDirective } from '../directives';
@Component({ @Component({
template: ` <input (abpCapsLock)="capsLock = $event" /> `, template: ` <input abpCapsLock /> `,
imports: [TrackCapsLockDirective], imports: [TrackCapsLockDirective],
}) })
class TestComponent { class TestComponent {
@ -14,6 +14,20 @@ class TestComponent {
describe('TrackCapsLockDirective', () => { describe('TrackCapsLockDirective', () => {
let fixture: ComponentFixture<TestComponent>; let fixture: ComponentFixture<TestComponent>;
let des: DebugElement[]; let des: DebugElement[];
let directive: TrackCapsLockDirective;
let emitSpy: ReturnType<typeof vi.spyOn>;
const createCapsLockEvent = (eventName: string, capsLock: boolean) => {
const event = new KeyboardEvent(eventName, {
key: 'CapsLock',
});
Object.defineProperty(event, 'getModifierState', {
value: (key: string) => key === 'CapsLock' && capsLock,
});
return event;
};
beforeEach(() => { beforeEach(() => {
fixture = TestBed.configureTestingModule({ fixture = TestBed.configureTestingModule({
@ -23,35 +37,29 @@ describe('TrackCapsLockDirective', () => {
fixture.detectChanges(); fixture.detectChanges();
des = fixture.debugElement.queryAll(By.directive(TrackCapsLockDirective)); des = fixture.debugElement.queryAll(By.directive(TrackCapsLockDirective));
directive = des[0].injector.get(TrackCapsLockDirective);
emitSpy = vi.spyOn(directive.capsLock, 'emit');
});
afterEach(() => {
vi.restoreAllMocks();
}); });
test.each(['keydown', 'keyup'])( test.each(['keydown', 'keyup'])(
'is %p works when press capslock and is emit status', 'is %p works when press capslock and is emit status',
eventName => { eventName => {
const event = new KeyboardEvent(eventName, { const event = createCapsLockEvent(eventName, true);
key: 'CapsLock', eventName === 'keydown' ? directive.onKeyDown(event) : directive.onKeyUp(event);
modifierCapsLock: true, expect(emitSpy).toHaveBeenCalledWith(true);
});
window.dispatchEvent(event);
fixture.detectChanges();
expect(fixture.componentInstance.capsLock).toBe(true);
}, },
); );
test.each(['keydown', 'keyup'])('is %p detect the change capslock is emit status', eventName => { test.each(['keydown', 'keyup'])('is %p detect the change capslock is emit status', eventName => {
const trueEvent = new KeyboardEvent(eventName, { const trueEvent = createCapsLockEvent(eventName, true);
key: 'CapsLock', eventName === 'keydown' ? directive.onKeyDown(trueEvent) : directive.onKeyUp(trueEvent);
modifierCapsLock: true, expect(emitSpy).toHaveBeenCalledWith(true);
}); const falseEvent = createCapsLockEvent(eventName, false);
window.dispatchEvent(trueEvent); eventName === 'keydown' ? directive.onKeyDown(falseEvent) : directive.onKeyUp(falseEvent);
fixture.detectChanges(); expect(emitSpy).toHaveBeenLastCalledWith(false);
expect(fixture.componentInstance.capsLock).toBe(true);
const falseEvent = new KeyboardEvent(eventName, {
key: 'CapsLock',
modifierCapsLock: false,
});
window.dispatchEvent(falseEvent);
fixture.detectChanges();
expect(fixture.componentInstance.capsLock).toBe(false);
}); });
}); });

31
npm/ng-packs/packages/core/src/lib/tests/debounce.directive.spec.ts

@ -1,6 +1,6 @@
import { timer , firstValueFrom } from 'rxjs';
import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest';
import { InputEventDebounceDirective } from '../directives/debounce.directive'; import { InputEventDebounceDirective } from '../directives/debounce.directive';
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
describe('InputEventDebounceDirective', () => { describe('InputEventDebounceDirective', () => {
let spectator: SpectatorDirective<InputEventDebounceDirective>; let spectator: SpectatorDirective<InputEventDebounceDirective>;
@ -13,7 +13,9 @@ describe('InputEventDebounceDirective', () => {
}); });
beforeEach(() => { beforeEach(() => {
spectator = createDirective('<input (input.debounce)="inputEventFn()" [debounce]="20" />', { vi.useFakeTimers();
spectator = createDirective('<input input.debounce (input.debounce)="inputEventFn()" />', {
hostProps: { inputEventFn }, hostProps: { inputEventFn },
}); });
directive = spectator.directive; directive = spectator.directive;
@ -21,18 +23,31 @@ describe('InputEventDebounceDirective', () => {
inputEventFn.mockClear(); inputEventFn.mockClear();
}); });
afterEach(() => {
if (vi.isFakeTimers()) {
vi.runOnlyPendingTimers();
}
vi.useRealTimers();
});
test('should be created', () => { test('should be created', () => {
expect(directive).toBeTruthy(); expect(directive).toBeTruthy();
}); });
test('should have 20ms debounce time', () => { test('should have 300ms debounce time', () => {
expect(directive.debounce).toBe(20); expect(directive.debounce()).toBe(300);
}); });
test('should call fromEvent with target element and target event', async () => { test('should call fromEvent with target element and target event', () => {
const emitSpy = vi.spyOn(directive.debounceEvent, 'emit');
spectator.dispatchFakeEvent('input', 'input', true); spectator.dispatchFakeEvent('input', 'input', true);
timer(0).subscribe(() => expect(inputEventFn).not.toHaveBeenCalled()); expect(emitSpy).not.toHaveBeenCalled();
await firstValueFrom(timer(21));
expect(inputEventFn).toHaveBeenCalled(); vi.advanceTimersByTime(299);
expect(emitSpy).not.toHaveBeenCalled();
vi.advanceTimersByTime(1);
expect(emitSpy).toHaveBeenCalled();
}); });
}); });

6
npm/ng-packs/packages/core/src/lib/tests/environment-utils.spec.ts

@ -97,11 +97,11 @@ describe('EnvironmentUtils', () => {
it('should handle request error gracefully and use local environment', async () => { it('should handle request error gracefully and use local environment', async () => {
const injector = spectator.inject(Injector); const injector = spectator.inject(Injector);
const injectorSpy = jest.spyOn(injector, 'get'); const injectorSpy = vi.spyOn(injector, 'get');
const http = spectator.inject(HttpClient); const http = spectator.inject(HttpClient);
const requestSpy = jest.spyOn(http, 'request'); const requestSpy = vi.spyOn(http, 'request');
const environmentService = spectator.inject(EnvironmentService); const environmentService = spectator.inject(EnvironmentService);
const setStateSpy = jest.spyOn(environmentService, 'setState'); const setStateSpy = vi.spyOn(environmentService, 'setState');
injectorSpy.mockReturnValueOnce(environmentService); injectorSpy.mockReturnValueOnce(environmentService);
injectorSpy.mockReturnValueOnce(http); injectorSpy.mockReturnValueOnce(http);

3
npm/ng-packs/packages/core/src/lib/tests/factory-utils.spec.ts

@ -28,11 +28,12 @@ describe('LazyModuleFactory', () => {
const injector = TestBed.inject(Injector); const injector = TestBed.inject(Injector);
const moduleRef = factory.create(injector); const moduleRef = factory.create(injector);
expect('componentFactoryResolver' in moduleRef).toBe(true);
expect('destroy' in moduleRef).toBe(true); expect('destroy' in moduleRef).toBe(true);
expect('injector' in moduleRef).toBe(true); expect('injector' in moduleRef).toBe(true);
expect('instance' in moduleRef).toBe(true); expect('instance' in moduleRef).toBe(true);
expect('onDestroy' in moduleRef).toBe(true); expect('onDestroy' in moduleRef).toBe(true);
expect(moduleRef.instance).toBeInstanceOf(Module);
expect(moduleRef.injector.get('foo')).toBe('bar');
}); });
}); });
}); });

177
npm/ng-packs/packages/core/src/lib/tests/for.directive.spec.ts

@ -1,5 +1,7 @@
import { TemplateRef } from '@angular/core';
import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/vitest'; import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/vitest';
import { ForDirective } from '../directives/for.directive'; import { ForDirective } from '../directives/for.directive';
import { setInputSignal } from './utils';
describe('ForDirective', () => { describe('ForDirective', () => {
let spectator: SpectatorDirective<ForDirective>; let spectator: SpectatorDirective<ForDirective>;
@ -9,12 +11,44 @@ describe('ForDirective', () => {
directive: ForDirective, directive: ForDirective,
}); });
const renderChanges = () => {
directive.ngOnChanges();
spectator.fixture.detectChanges(false);
spectator.fixture.detectChanges(false);
};
const resetProjection = () => {
directive['vcRef'].clear();
directive['lastItemsRef'] = null;
directive['differ'] = null;
};
const setDirectiveInputs = (inputs: Partial<Record<keyof ForDirective, unknown>>) => {
Object.entries(inputs).forEach(([key, value]) => {
setInputSignal(directive[key], value);
});
resetProjection();
renderChanges();
};
const getTexts = () => spectator.queryAll('li').map(el => el.textContent.trim());
const getTemplateRef = node => {
try {
return node.injector.get(TemplateRef);
} catch {
return null;
}
};
const getTemplateRefs = () =>
spectator.fixture.debugElement.queryAllNodes(getTemplateRef).map(getTemplateRef);
describe('basic', () => { describe('basic', () => {
beforeEach(() => { beforeEach(() => {
spectator = createDirective('<ul><li *abpFor="let item of items">{{ item }}</li></ul>', { spectator = createDirective('<ul><ng-template abpFor let-item><li>{{ item }}</li></ng-template></ul>');
hostProps: { items },
});
directive = spectator.directive; directive = spectator.directive;
setDirectiveInputs({ items });
}); });
test('should be created', () => { test('should be created', () => {
@ -29,166 +63,116 @@ describe('ForDirective', () => {
}); });
test('should sync the DOM when change items', () => { test('should sync the DOM when change items', () => {
directive.items = [10, 11, 12]; setDirectiveInputs({ items: [10, 11, 12] });
directive['vcRef'].clear();
directive['lastItemsRef'] = null;
directive['differ'] = null;
directive.ngOnChanges();
spectator.detectChanges();
const elements = spectator.queryAll('li');
const elements = spectator.queryAll('li');
expect(elements[1]).toHaveText('11'); expect(elements[1]).toHaveText('11');
expect(elements).toHaveLength(3); expect(elements).toHaveLength(3);
}); });
test('should sync the DOM when add an item', () => { test('should sync the DOM when add an item', () => {
directive.items = [...items, 6]; setDirectiveInputs({ items: [...items, 6] });
directive['vcRef'].clear();
directive['lastItemsRef'] = null;
directive['differ'] = null;
directive.ngOnChanges();
spectator.detectChanges();
const elements = spectator.queryAll('li');
const elements = spectator.queryAll('li');
expect(elements[6]).toHaveText('6'); expect(elements[6]).toHaveText('6');
expect(elements).toHaveLength(7); expect(elements).toHaveLength(7);
}); });
}); });
describe('trackBy', () => { describe('trackBy', () => {
const trackByFn = (_, item) => item; const trackByFn = (_: number, item: number) => item;
beforeEach(() => { beforeEach(() => {
spectator = createDirective( spectator = createDirective('<ul><ng-template abpFor let-item><li>{{ item }}</li></ng-template></ul>');
'<ul><li *abpFor="let item of items; trackBy: trackByFn">{{ item }}</li></ul>',
{
hostProps: { items, trackByFn },
},
);
directive = spectator.directive; directive = spectator.directive;
setDirectiveInputs({ items, trackBy: trackByFn });
}); });
test('should be setted the trackBy', () => { test('should be setted the trackBy', () => {
expect(directive.trackBy).toEqual(trackByFn); expect(directive.trackBy()).toEqual(trackByFn);
}); });
}); });
describe('with basic order', () => { describe('with basic order', () => {
beforeEach(() => { beforeEach(() => {
spectator = createDirective( spectator = createDirective('<ul><ng-template abpFor let-item><li>{{ item }}</li></ng-template></ul>');
`<ul>
<li
*abpFor="let item of [3,6,2];
orderDir: 'ASC'">
{{ item }}
</li>
</ul>`,
);
directive = spectator.directive; directive = spectator.directive;
setDirectiveInputs({ items: [3, 6, 2], orderDir: 'ASC' });
}); });
test('should order by asc', () => { test('should order by asc', () => {
const elements = spectator.queryAll('li'); expect(getTexts()).toEqual(['2', '3', '6']);
expect(elements.map(el => el.textContent.trim())).toEqual(['2', '3', '6']);
}); });
}); });
describe('with order', () => { describe('with order', () => {
beforeEach(() => { beforeEach(() => {
spectator = createDirective( spectator = createDirective(
`<ul> '<ul><ng-template abpFor let-item><li>{{ item.value }}</li></ng-template></ul>',
<li
*abpFor="let item of [{value: 3}, {value: 6}, {value: 2}];
orderBy: 'value';
orderDir: orderDir">
{{ item.value }}
</li>
</ul>`,
{
hostProps: { orderDir: 'ASC' },
},
); );
directive = spectator.directive; directive = spectator.directive;
setDirectiveInputs({
items: [{ value: 3 }, { value: 6 }, { value: 2 }],
orderBy: 'value',
orderDir: 'ASC',
});
}); });
test('should order by asc', () => { test('should order by asc', () => {
const elements = spectator.queryAll('li'); expect(getTexts()).toEqual(['2', '3', '6']);
expect(elements.map(el => el.textContent.trim())).toEqual(['2', '3', '6']);
}); });
test('should order by desc', () => { test('should order by desc', () => {
directive.orderDir = 'DESC'; setDirectiveInputs({ orderDir: 'DESC' });
directive['vcRef'].clear();
directive['lastItemsRef'] = null;
directive['differ'] = null;
directive.ngOnChanges();
spectator.detectChanges();
const elements = spectator.queryAll('li'); expect(getTexts()).toEqual(['6', '3', '2']);
expect(elements.map(el => el.textContent.trim())).toEqual(['6', '3', '2']);
}); });
}); });
describe('with filter', () => { describe('with filter', () => {
beforeEach(() => { beforeEach(() => {
spectator = createDirective( spectator = createDirective(
`<ul> '<ul><ng-template abpFor let-item><li>{{ item.value }}</li></ng-template></ul>',
<li
*abpFor="let item of [{value: 'test'}, {value: 'abp'}, {value: 'volo'}];
filterBy: 'value';
filterVal: filterVal">
{{ item.value }}
</li>
</ul>`,
{
hostProps: { filterVal: '' },
},
); );
directive = spectator.directive; directive = spectator.directive;
setDirectiveInputs({
items: [{ value: 'test' }, { value: 'abp' }, { value: 'volo' }],
filterBy: 'value',
filterVal: '',
});
}); });
test('should not filter when filterVal is empty,', () => { test('should not filter when filterVal is empty,', () => {
const elements = spectator.queryAll('li'); expect(getTexts()).toEqual(['test', 'abp', 'volo']);
expect(elements.map(el => el.textContent.trim())).toEqual(['test', 'abp', 'volo']);
}); });
test('should be filtered', () => { test('should be filtered', () => {
directive.filterVal = 'volo'; setDirectiveInputs({ filterVal: 'volo' });
directive['vcRef'].clear();
directive['lastItemsRef'] = null;
directive['differ'] = null;
directive.ngOnChanges();
spectator.detectChanges();
expect(spectator.query('li')).toHaveText('volo'); expect(spectator.query('li')).toHaveText('volo');
}); });
test('should not show an element when filter value not match to any text', () => { test('should not show an element when filter value not match to any text', () => {
directive.filterVal = 'volos'; setDirectiveInputs({ filterVal: 'volos' });
directive.ngOnChanges();
spectator.detectChanges();
const elements = spectator.queryAll('li'); expect(spectator.queryAll('li')).toHaveLength(0);
expect(elements).toHaveLength(0);
}); });
}); });
describe('with empty ref', () => { describe('with empty ref', () => {
beforeEach(() => { beforeEach(() => {
spectator = createDirective( spectator = createDirective(`
`<ul> <ul>
<li <ng-template abpFor let-item>
*abpFor="let item of items; <li>{{ item.value }}</li>
emptyRef: empty"> </ng-template>
{{ item.value }}
</li>
<ng-template #empty>No records found</ng-template> <ng-template #empty>No records found</ng-template>
</ul>`, </ul>
{ `);
hostProps: { items: [] },
},
);
directive = spectator.directive; directive = spectator.directive;
const [, emptyRef] = getTemplateRefs();
setDirectiveInputs({ items: [], emptyRef });
}); });
test('should display the empty ref', () => { test('should display the empty ref', () => {
@ -200,12 +184,7 @@ describe('ForDirective', () => {
expect(spectator.query('ul')).toHaveText('No records found'); expect(spectator.query('ul')).toHaveText('No records found');
expect(spectator.queryAll('li')).toHaveLength(0); expect(spectator.queryAll('li')).toHaveLength(0);
directive.items = [0]; setDirectiveInputs({ items: [{ value: 0 }] });
directive['vcRef'].clear();
directive['lastItemsRef'] = null;
directive['differ'] = null;
directive.ngOnChanges();
spectator.detectChanges();
expect(spectator.query('ul')).not.toHaveText('No records found'); expect(spectator.query('ul')).not.toHaveText('No records found');
expect(spectator.queryAll('li')).toHaveLength(1); expect(spectator.queryAll('li')).toHaveLength(1);

26
npm/ng-packs/packages/core/src/lib/tests/form-submit.directive.spec.ts

@ -1,7 +1,9 @@
import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest';
import { FormSubmitDirective } from '../directives/form-submit.directive'; import { FormSubmitDirective } from '../directives/form-submit.directive';
import { FormsModule, ReactiveFormsModule, FormGroup } from '@angular/forms'; import { FormsModule, ReactiveFormsModule, FormGroup } from '@angular/forms';
import { timer, firstValueFrom } from 'rxjs'; import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import { setInputSignal } from './utils';
describe('FormSubmitDirective', () => { describe('FormSubmitDirective', () => {
let spectator: SpectatorDirective<FormSubmitDirective>; let spectator: SpectatorDirective<FormSubmitDirective>;
@ -16,8 +18,10 @@ describe('FormSubmitDirective', () => {
}); });
beforeEach(() => { beforeEach(() => {
vi.useFakeTimers();
spectator = createDirective( spectator = createDirective(
'<form [formGroup]="formGroup" (ngSubmit)="submitEventFn()" [debounce]="20">form content</form>', '<form ngSubmit [formGroup]="formGroup" (ngSubmit)="submitEventFn()">form content</form>',
{ {
hostProps: { hostProps: {
submitEventFn, submitEventFn,
@ -26,6 +30,14 @@ describe('FormSubmitDirective', () => {
}, },
); );
directive = spectator.directive; directive = spectator.directive;
setInputSignal(directive.debounce, 20);
});
afterEach(() => {
if (vi.isFakeTimers()) {
vi.runOnlyPendingTimers();
}
vi.useRealTimers();
}); });
test('should be created', () => { test('should be created', () => {
@ -33,7 +45,7 @@ describe('FormSubmitDirective', () => {
}); });
test('should have 20ms debounce time', () => { test('should have 20ms debounce time', () => {
expect(directive.debounce).toBe(20); expect(directive.debounce()).toBe(20);
}); });
test('should dispatch submit event on keyup event triggered after given debounce time', async () => { test('should dispatch submit event on keyup event triggered after given debounce time', async () => {
@ -44,8 +56,12 @@ describe('FormSubmitDirective', () => {
cancelable: true, cancelable: true,
}); });
form?.dispatchEvent(event); form?.dispatchEvent(event);
timer(0).subscribe(() => expect(submitEventFn).not.toHaveBeenCalled()); expect(submitEventFn).not.toHaveBeenCalled();
await firstValueFrom(timer(directive.debounce + 10));
vi.advanceTimersByTime(199);
expect(submitEventFn).not.toHaveBeenCalled();
vi.advanceTimersByTime(1);
expect(submitEventFn).toHaveBeenCalled(); expect(submitEventFn).toHaveBeenCalled();
}); });
}); });

34
npm/ng-packs/packages/core/src/lib/tests/local-storage.service.spec.ts

@ -4,49 +4,59 @@ import { AbpLocalStorageService } from '../services/local-storage.service';
describe('LocalStorageService', () => { describe('LocalStorageService', () => {
let service: AbpLocalStorageService; let service: AbpLocalStorageService;
let localStorageMock: Storage;
beforeEach(() => { beforeEach(() => {
localStorageMock = {
clear: vi.fn(),
getItem: vi.fn(),
key: vi.fn(),
removeItem: vi.fn(),
setItem: vi.fn(),
length: 0,
};
vi.stubGlobal('localStorage', localStorageMock);
TestBed.configureTestingModule({}); TestBed.configureTestingModule({});
service = TestBed.inject(AbpLocalStorageService); service = TestBed.inject(AbpLocalStorageService);
}); });
afterEach(() => {
vi.unstubAllGlobals();
});
it('should be created', () => { it('should be created', () => {
expect(service).toBeTruthy(); expect(service).toBeTruthy();
}); });
it('should be called getItem', () => { it('should be called getItem', () => {
const spy = vi.spyOn(service, 'getItem');
service.getItem('test'); service.getItem('test');
expect(spy).toHaveBeenCalled(); expect(localStorageMock.getItem).toHaveBeenCalledWith('test');
}); });
it('should be called setItem', () => { it('should be called setItem', () => {
const spy = vi.spyOn(service, 'setItem');
service.setItem('test', 'value'); service.setItem('test', 'value');
expect(spy).toHaveBeenCalled(); expect(localStorageMock.setItem).toHaveBeenCalledWith('test', 'value');
}); });
it('should be called removeItem', () => { it('should be called removeItem', () => {
const spy = vi.spyOn(service, 'removeItem');
service.removeItem('test'); service.removeItem('test');
expect(spy).toHaveBeenCalled(); expect(localStorageMock.removeItem).toHaveBeenCalledWith('test');
}); });
it('should be called clear', () => { it('should be called clear', () => {
const spy = vi.spyOn(service, 'clear');
service.clear(); service.clear();
expect(spy).toHaveBeenCalled(); expect(localStorageMock.clear).toHaveBeenCalled();
}); });
it('should be called key', () => { it('should be called key', () => {
const spy = vi.spyOn(service, 'key');
service.key(0); service.key(0);
expect(spy).toHaveBeenCalled(); expect(localStorageMock.key).toHaveBeenCalledWith(0);
}); });
it('should be called length', () => { it('should be called length', () => {
const spy = vi.spyOn(service, 'length', 'get'); vi.stubGlobal('localStorage', { ...localStorageMock, length: 1 });
service.length; service.length;
expect(spy).toHaveBeenCalled(); expect(service.length).toBe(1);
}); });
}); });

11
npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts

@ -4,6 +4,7 @@ import { Subject } from 'rxjs';
import { PermissionDirective } from '../directives/permission.directive'; import { PermissionDirective } from '../directives/permission.directive';
import { PermissionService } from '../services/permission.service'; import { PermissionService } from '../services/permission.service';
import { QUEUE_MANAGER } from '../tokens/queue.token'; import { QUEUE_MANAGER } from '../tokens/queue.token';
import { setInputSignal } from './utils';
describe('PermissionDirective', () => { describe('PermissionDirective', () => {
let spectator: SpectatorDirective<PermissionDirective>; let spectator: SpectatorDirective<PermissionDirective>;
@ -20,7 +21,7 @@ describe('PermissionDirective', () => {
beforeEach(() => { beforeEach(() => {
spectator = createDirective( spectator = createDirective(
'<div [abpPermission]="permission" [abpPermissionRunChangeDetection]="runCD"></div>', '<div abpPermission>test</div>',
{ {
hostProps: { permission: 'test', runCD: false }, hostProps: { permission: 'test', runCD: false },
}, },
@ -44,19 +45,19 @@ describe('PermissionDirective', () => {
it('should handle permission input', () => { it('should handle permission input', () => {
grantedPolicy$.next(false); grantedPolicy$.next(false);
directive.condition = 'new-permission'; setInputSignal(directive.condition, 'new-permission');
directive.ngOnChanges(); directive.ngOnChanges();
grantedPolicy$.next(true); grantedPolicy$.next(true);
expect(directive).toBeTruthy(); expect(directive).toBeTruthy();
expect(directive.condition).toBe('new-permission'); expect(directive.condition()).toBe('new-permission');
}); });
it('should handle runChangeDetection input', () => { it('should handle runChangeDetection input', () => {
grantedPolicy$.next(false); grantedPolicy$.next(false);
directive.runChangeDetection = true; setInputSignal(directive.runChangeDetection, true);
directive.ngOnChanges(); directive.ngOnChanges();
grantedPolicy$.next(true); grantedPolicy$.next(true);
expect(directive).toBeTruthy(); expect(directive).toBeTruthy();
expect(directive.runChangeDetection).toBe(true); expect(directive.runChangeDetection()).toBe(true);
}); });
}); });

68
npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts

@ -1,5 +1,3 @@
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { provideHttpClient } from '@angular/common/http';
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { provideRouter, Route, Router } from '@angular/router'; import { provideRouter, Route, Router } from '@angular/router';
import { RouterTestingHarness } from '@angular/router/testing'; import { RouterTestingHarness } from '@angular/router/testing';
@ -9,8 +7,8 @@ import { of } from 'rxjs';
import { permissionGuard } from '../guards/permission.guard'; import { permissionGuard } from '../guards/permission.guard';
import { HttpErrorReporterService } from '../services/http-error-reporter.service'; import { HttpErrorReporterService } from '../services/http-error-reporter.service';
import { PermissionService } from '../services/permission.service'; import { PermissionService } from '../services/permission.service';
import { provideAbpCore, withOptions } from '../providers';
import { AuthService } from '../abstracts'; import { AuthService } from '../abstracts';
import { ConfigStateService, RouteBasedCultureUrlService, RoutesService } from '../services';
@Component({ template: '' }) @Component({ template: '' })
class DummyComponent {} class DummyComponent {}
@ -20,8 +18,11 @@ class DummyComponent {}
describe('authGuard', () => { describe('authGuard', () => {
let permissionService: SpyObject<PermissionService>; let permissionService: SpyObject<PermissionService>;
let httpErrorReporter: SpyObject<HttpErrorReporterService>; let httpErrorReporter: SpyObject<HttpErrorReporterService>;
let routesService: Pick<RoutesService, 'find'>;
let routeCultureUrl: Pick<RouteBasedCultureUrlService, 'getRoutePathForMatching'>;
let configStateService: Pick<ConfigStateService, 'getAll$'>;
const mockOAuthService = { const authService = {
isAuthenticated: true, isAuthenticated: true,
}; };
@ -53,46 +54,30 @@ describe('authGuard', () => {
beforeEach(() => { beforeEach(() => {
httpErrorReporter = createSpyObject(HttpErrorReporterService); httpErrorReporter = createSpyObject(HttpErrorReporterService);
permissionService = createSpyObject(PermissionService); permissionService = createSpyObject(PermissionService);
permissionService.getGrantedPolicy$.andReturn(of(true));
routesService = {
find: vi.fn(),
};
routeCultureUrl = {
getRoutePathForMatching: vi.fn((_: Router, url: string) => url),
};
configStateService = {
getAll$: vi.fn(() => of({ auth: { grantedPolicies: {} } })),
};
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [ providers: [
provideHttpClient(), { provide: AuthService, useValue: authService },
provideHttpClientTesting(),
{ provide: AuthService, useValue: mockOAuthService },
{ provide: PermissionService, useValue: permissionService }, { provide: PermissionService, useValue: permissionService },
{ provide: HttpErrorReporterService, useValue: httpErrorReporter }, { provide: HttpErrorReporterService, useValue: httpErrorReporter },
{ provide: RoutesService, useValue: routesService },
{ provide: RouteBasedCultureUrlService, useValue: routeCultureUrl },
{ provide: ConfigStateService, useValue: configStateService },
provideRouter(routes), provideRouter(routes),
provideAbpCore(
withOptions({
environment: {
apis: {
default: {
url: 'http://localhost:4200',
},
},
application: {
baseUrl: 'http://localhost:4200',
name: 'TestApp',
},
remoteEnv: {
url: 'http://localhost:4200',
mergeStrategy: 'deepmerge',
},
},
registerLocaleFn: () => Promise.resolve(),
skipGetAppConfiguration: true,
}),
),
], ],
}); });
}); });
afterEach(async () => {
// Wait for any pending async operations to complete before teardown
await new Promise(resolve => setTimeout(resolve, 0));
TestBed.resetTestingModule();
});
it('should return true when the grantedPolicy is true', async () => { it('should return true when the grantedPolicy is true', async () => {
permissionService.getGrantedPolicy$.andReturn(of(true)); permissionService.getGrantedPolicy$.andReturn(of(true));
await RouterTestingHarness.create('/dummy'); await RouterTestingHarness.create('/dummy');
@ -103,17 +88,24 @@ describe('authGuard', () => {
it('should return false and report an error when the grantedPolicy is false', () => { it('should return false and report an error when the grantedPolicy is false', () => {
permissionService.getGrantedPolicy$.andReturn(of(false)); permissionService.getGrantedPolicy$.andReturn(of(false));
expect(permissionService.getGrantedPolicy$).toBeDefined(); return RouterTestingHarness.create('/dummy').then(() => {
expect(httpErrorReporter.reportError).toBeDefined(); expect(TestBed.inject(Router).url).toEqual('/');
expect(httpErrorReporter.reportError).toHaveBeenCalledWith({ status: 403 });
});
}); });
it('should check the requiredPolicy from RoutesService', async () => { it('should check the requiredPolicy from RoutesService', async () => {
routesService.find = vi.fn(predicate => {
const route = { path: '/zibzib', requiredPolicy: 'TestPolicy' };
return predicate(route) ? route : null;
});
permissionService.getGrantedPolicy$.mockImplementation(policy => { permissionService.getGrantedPolicy$.mockImplementation(policy => {
return of(policy === 'TestPolicy'); return of(policy === 'TestPolicy');
}); });
await RouterTestingHarness.create('/dummy'); await RouterTestingHarness.create('/zibzib');
expect(TestBed.inject(Router).url).toEqual('/dummy'); expect(permissionService.getGrantedPolicy$).toHaveBeenCalledWith('TestPolicy');
expect(TestBed.inject(Router).url).toEqual('/zibzib');
expect(httpErrorReporter.reportError).not.toHaveBeenCalled(); expect(httpErrorReporter.reportError).not.toHaveBeenCalled();
}); });

51
npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts

@ -1,39 +1,37 @@
import { Component, Input, inject, output } from '@angular/core'; import { Component, inject, input, output } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { ReplaceableTemplateDirective } from '../directives/replaceable-template.directive'; import { ReplaceableTemplateDirective } from '../directives/replaceable-template.directive';
import { ReplaceableComponents } from '../models/replaceable-components'; import { ReplaceableComponents } from '../models/replaceable-components';
import { ReplaceableComponentsService } from '../services/replaceable-components.service'; import { ReplaceableComponentsService } from '../services/replaceable-components.service';
import { setInputSignal } from './utils';
@Component({ @Component({
selector: 'abp-default-component', selector: 'abp-default-component',
template: ' <p>default</p> ', template: ' <p>default</p> ',
exportAs: 'abpDefaultComponent' exportAs: 'abpDefaultComponent',
}) })
class DefaultComponent { class DefaultComponent {
@Input() onOneWay = input<any>();
oneWay;
@Input() twoWay = input<boolean>();
twoWay: boolean;
readonly twoWayChange = output<boolean>(); readonly twoWayChange = output<boolean>();
readonly someOutput = output<string>(); readonly someOutput = output<string>();
setTwoWay(value) { setTwoWay(value) {
this.twoWay = value; setInputSignal(this.twoWay, value);
this.twoWayChange.emit(value); this.twoWayChange.emit(value);
} }
} }
@Component({ @Component({
selector: 'abp-external-component', selector: 'abp-external-component',
template: ' <p>external</p> ' template: ' <p>external</p> ',
}) })
class ExternalComponent { class ExternalComponent {
data = inject<ReplaceableComponents.ReplaceableTemplateData<any, any>>('REPLACEABLE_DATA' as any, { optional: true })!; data = inject<ReplaceableComponents.ReplaceableTemplateData<any, any>>('REPLACEABLE_DATA' as any)!;
} }
describe('ReplaceableTemplateDirective', () => { describe('ReplaceableTemplateDirective', () => {
@ -54,11 +52,12 @@ describe('ReplaceableTemplateDirective', () => {
beforeEach(() => { beforeEach(() => {
spectator = createDirective( spectator = createDirective(
` `
<div *abpReplaceableTemplate="{inputs: {oneWay: {value: oneWay}, twoWay: {value: twoWay, twoWay: true}}, outputs: {twoWayChange: twoWayChange, someOutput: someOutput}, componentKey: 'TestModule.TestComponent'}; let initTemplate = initTemplate"> <ng-template abpReplaceableTemplate let-initTemplate="initTemplate">
<abp-default-component #defaultComponent="abpDefaultComponent"></abp-default-component> <abp-default-component #defaultComponent="abpDefaultComponent"></abp-default-component>
</div> </ng-template>
`, `,
{ {
detectChanges: false,
hostProps: { hostProps: {
oneWay: { label: 'Test' }, oneWay: { label: 'Test' },
twoWay: false, twoWay: false,
@ -67,6 +66,15 @@ describe('ReplaceableTemplateDirective', () => {
}, },
}, },
); );
setInputSignal(spectator.directive.data, {
inputs: {
oneWay: { value: { label: 'Test' } },
twoWay: { value: false, twoWay: true },
},
outputs: { twoWayChange, someOutput },
componentKey: 'TestModule.TestComponent',
});
spectator.detectChanges();
}); });
it('should create directive successfully', () => { it('should create directive successfully', () => {
@ -78,19 +86,22 @@ describe('ReplaceableTemplateDirective', () => {
it('should create directive successfully', () => { it('should create directive successfully', () => {
spectator = createDirective( spectator = createDirective(
` `
<div *abpReplaceableTemplate="{inputs: {oneWay: {value: oneWay}, twoWay: {value: twoWay, twoWay: true}}, outputs: {twoWayChange: twoWayChange, someOutput: someOutput}, componentKey: 'TestModule.TestComponent'}; let initTemplate = initTemplate"> <ng-template abpReplaceableTemplate let-initTemplate="initTemplate">
<abp-default-component #defaultComponent="abpDefaultComponent"></abp-default-component> <abp-default-component #defaultComponent="abpDefaultComponent"></abp-default-component>
</div> </ng-template>
`, `,
{ {
hostProps: { detectChanges: false,
oneWay: { label: 'Test' },
twoWay: false,
twoWayChange: vi.fn(),
someOutput: vi.fn(),
},
}, },
); );
setInputSignal(spectator.directive.data, {
inputs: {
oneWay: { value: { label: 'Test' } },
twoWay: { value: false, twoWay: true },
},
outputs: { twoWayChange: vi.fn(), someOutput: vi.fn() },
componentKey: 'TestModule.TestComponent',
});
expect(spectator.directive).toBeTruthy(); expect(spectator.directive).toBeTruthy();
}); });
}); });

37
npm/ng-packs/packages/core/src/lib/tests/show-password-directive.spec.ts

@ -1,37 +1,46 @@
import { Component, DebugElement, ChangeDetectorRef } from '@angular/core'; import { Component, DebugElement } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { ShowPasswordDirective } from '../directives'; import { ShowPasswordDirective } from '../directives';
import { setInputSignal } from './utils';
@Component({ @Component({
template: ` <input [abpShowPassword]="true" /> template: ` <input abpShowPassword />
<input [abpShowPassword]="false" /> <input abpShowPassword />
<input /> <input />
<input [abpShowPassword]="showPassword" />`, <input abpShowPassword />`,
imports: [ShowPasswordDirective], imports: [ShowPasswordDirective],
}) })
class TestComponent { class TestComponent {}
showPassword = false;
}
describe('ShowPasswordDirective', () => { describe('ShowPasswordDirective', () => {
let fixture: ComponentFixture<TestComponent>; let fixture: ComponentFixture<TestComponent>;
let des: DebugElement[]; let des: DebugElement[];
let desAll: DebugElement[]; let desAll: DebugElement[];
let bareInput;
const detectChanges = () => {
fixture.detectChanges();
TestBed.flushEffects();
};
const setShowPassword = (index: number, value: boolean) => {
setInputSignal(des[index].injector.get(ShowPasswordDirective).abpShowPassword, value);
detectChanges();
};
beforeEach(() => { beforeEach(() => {
fixture = TestBed.configureTestingModule({ fixture = TestBed.configureTestingModule({
imports: [TestComponent], imports: [TestComponent],
}).createComponent(TestComponent); }).createComponent(TestComponent);
fixture.detectChanges(); detectChanges();
des = fixture.debugElement.queryAll(By.directive(ShowPasswordDirective)); des = fixture.debugElement.queryAll(By.directive(ShowPasswordDirective));
desAll = fixture.debugElement.queryAll(By.all()); desAll = fixture.debugElement.queryAll(By.all());
bareInput = fixture.debugElement.query(By.css('input:not([abpShowPassword])')); setShowPassword(0, true);
setShowPassword(1, false);
setShowPassword(2, false);
}); });
it('should have three input has ShowPasswordDirective elements', () => { it('should have three input has ShowPasswordDirective elements', () => {
@ -52,11 +61,7 @@ describe('ShowPasswordDirective', () => {
const input = des[2].nativeElement; const input = des[2].nativeElement;
expect(input.type).toBe('password'); expect(input.type).toBe('password');
fixture.componentInstance.showPassword = true; setShowPassword(2, true);
const cdr = fixture.componentRef.injector.get(ChangeDetectorRef);
cdr.markForCheck();
cdr.detectChanges();
expect(input.type).toBe('text'); expect(input.type).toBe('text');
}); });

9
npm/ng-packs/packages/core/src/lib/tests/stop-propagation.directive.spec.ts

@ -1,5 +1,6 @@
import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest';
import { StopPropagationDirective } from '../directives/stop-propagation.directive'; import { StopPropagationDirective } from '../directives/stop-propagation.directive';
import { beforeEach, describe, expect, test, vi } from 'vitest';
describe('StopPropagationDirective', () => { describe('StopPropagationDirective', () => {
let spectator: SpectatorDirective<StopPropagationDirective>; let spectator: SpectatorDirective<StopPropagationDirective>;
@ -13,12 +14,13 @@ describe('StopPropagationDirective', () => {
beforeEach(() => { beforeEach(() => {
spectator = createDirective( spectator = createDirective(
'<div (click)="parentClickEventFn()"><a (click.stop)="childClickEventFn()" >Link</a></div>', '<div (click)="parentClickEventFn()"><a click.stop>Link</a></div>',
{ {
hostProps: { parentClickEventFn, childClickEventFn }, hostProps: { parentClickEventFn },
}, },
); );
directive = spectator.directive; directive = spectator.directive;
directive.stopPropEvent.subscribe(childClickEventFn);
link = spectator.query('a'); link = spectator.query('a');
childClickEventFn.mockClear(); childClickEventFn.mockClear();
parentClickEventFn.mockClear(); parentClickEventFn.mockClear();
@ -29,8 +31,7 @@ describe('StopPropagationDirective', () => {
}); });
test('should not call click event of parent when child element is clicked', () => { test('should not call click event of parent when child element is clicked', () => {
spectator.setHostInput({ parentClickEventFn, childClickEventFn }); spectator.click(link);
spectator.click('a');
spectator.detectChanges(); spectator.detectChanges();
expect(childClickEventFn).toHaveBeenCalled(); expect(childClickEventFn).toHaveBeenCalled();
expect(parentClickEventFn).not.toHaveBeenCalled(); expect(parentClickEventFn).not.toHaveBeenCalled();

4
npm/ng-packs/packages/core/src/lib/tests/utils/index.ts

@ -0,0 +1,4 @@
export * from './input-signal';
export * from './common.utils';
export * from './mock-compare-function';
export * from './permission-service.spec.utils';

6
npm/ng-packs/packages/core/src/lib/tests/utils/input-signal.ts

@ -0,0 +1,6 @@
import { ɵSIGNAL as SIGNAL } from '@angular/core';
export const setInputSignal = <T>(inputSignal: () => T, value: T) => {
const node = inputSignal[SIGNAL];
node.applyValueToInputSignal(node, value);
};

8
npm/ng-packs/packages/theme-shared/src/lib/components/button/button.component.ts

@ -63,10 +63,6 @@ export class ButtonComponent implements OnInit {
this.isLoading() ? 'fa fa-spinner fa-spin' : this.iconClass() || 'd-none', this.isLoading() ? 'fa fa-spinner fa-spin' : this.iconClass() || 'd-none',
); );
setLoading(value: boolean): void {
this.modalLoading.set(value);
}
ngOnInit() { ngOnInit() {
const attributes = this.attributes(); const attributes = this.attributes();
if (attributes) { if (attributes) {
@ -77,4 +73,8 @@ export class ButtonComponent implements OnInit {
}); });
} }
} }
setLoading(value: boolean): void {
this.modalLoading.set(value);
}
} }

4
npm/ng-packs/packages/theme-shared/src/lib/components/loader-bar/loader-bar.component.ts

@ -40,8 +40,8 @@ export class LoaderBarComponent implements OnDestroy, OnInit {
readonly containerClass = input('abp-loader-bar'); readonly containerClass = input('abp-loader-bar');
readonly color = input('#77b6ff'); readonly color = input('#77b6ff');
protected readonly isLoading = signal(false); readonly isLoading = signal(false);
protected readonly progressLevel = signal(0); readonly progressLevel = signal(0);
interval = new Subscription(); interval = new Subscription();
timer = new Subscription(); timer = new Subscription();

2
npm/ng-packs/packages/theme-shared/src/lib/directives/loading.directive.ts

@ -32,7 +32,7 @@ export class LoadingDirective implements OnInit, OnDestroy {
readonly targetElementInput = input<HTMLElement | undefined>(undefined, { alias: 'abpLoadingTargetElement' }); readonly targetElementInput = input<HTMLElement | undefined>(undefined, { alias: 'abpLoadingTargetElement' });
readonly delay = input(0, { alias: 'abpLoadingDelay' }); readonly delay = input(0, { alias: 'abpLoadingDelay' });
private targetElement: HTMLElement | undefined; targetElement: HTMLElement | undefined;
componentRef: ComponentRef<LoadingComponent> | null = null; componentRef: ComponentRef<LoadingComponent> | null = null;
rootNode: HTMLDivElement | null = null; rootNode: HTMLDivElement | null = null;

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

@ -24,6 +24,7 @@ const mockRoutes: ABP.Route[] = [
describe('BreadcrumbComponent', () => { describe('BreadcrumbComponent', () => {
let spectator: SpectatorRouting<RouterOutletComponent>; let spectator: SpectatorRouting<RouterOutletComponent>;
let routes: RoutesService; let routes: RoutesService;
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
const createRouting = createRoutingFactory({ const createRouting = createRoutingFactory({
component: RouterOutletComponent, component: RouterOutletComponent,
@ -51,6 +52,7 @@ describe('BreadcrumbComponent', () => {
}, },
registerLocaleFn: () => Promise.resolve(), registerLocaleFn: () => Promise.resolve(),
skipGetAppConfiguration: true, skipGetAppConfiguration: true,
skipInitAuthService: true,
}), }),
), ),
{ {
@ -102,10 +104,15 @@ describe('BreadcrumbComponent', () => {
beforeAll(() => setupComponentResources('../components/breadcrumb', import.meta.url)); beforeAll(() => setupComponentResources('../components/breadcrumb', import.meta.url));
beforeEach(() => { beforeEach(() => {
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
spectator = createRouting(); spectator = createRouting();
routes = spectator.inject(RoutesService); routes = spectator.inject(RoutesService);
}); });
afterEach(() => {
consoleErrorSpy.mockRestore();
});
it('should create component', async () => { it('should create component', async () => {
routes.add(mockRoutes); routes.add(mockRoutes);
await spectator.router.navigateByUrl('/identity/users'); await spectator.router.navigateByUrl('/identity/users');

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

@ -1,5 +1,7 @@
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest'; import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest';
import { ButtonComponent } from '../components'; import { ButtonComponent } from '../components';
import { setInputSignal } from './utils';
describe('ButtonComponent', () => { describe('ButtonComponent', () => {
let spectator: SpectatorHost<ButtonComponent>; let spectator: SpectatorHost<ButtonComponent>;
@ -7,13 +9,13 @@ describe('ButtonComponent', () => {
const createHost = createHostFactory(ButtonComponent); const createHost = createHostFactory(ButtonComponent);
beforeEach( beforeEach(
() => () => {
(spectator = createHost( spectator = createHost('<abp-button>Button</abp-button>', {
'<abp-button iconClass="fa fa-check" [attributes]="attributes">Button</abp-button>', detectChanges: false,
{ });
hostProps: { attributes: { autofocus: '', name: 'abp-button' } }, setInputSignal(spectator.component.iconClass, 'fa fa-check');
}, spectator.detectChanges();
)), },
); );
it('should display the button', () => { it('should display the button', () => {
@ -48,23 +50,17 @@ describe('ButtonComponent', () => {
}); });
it('should display the spinner icon when loading input is true', () => { it('should display the spinner icon when loading input is true', () => {
spectator = createHost( setInputSignal(spectator.component.loading, true);
'<abp-button iconClass="fa fa-check" [loading]="loading">Button</abp-button>',
{ hostProps: { loading: true } },
);
spectator.detectComponentChanges(); spectator.detectComponentChanges();
expect(spectator.query('i')).toHaveClass('fa-spinner'); expect(spectator.query('i')).toHaveClass('fa-spinner');
}); });
it('should clear the spinner icon when loading input becomes false', () => { it('should clear the spinner icon when loading input becomes false', () => {
spectator = createHost( setInputSignal(spectator.component.loading, true);
'<abp-button iconClass="fa fa-check" [loading]="loading">Button</abp-button>',
{ hostProps: { loading: true } },
);
spectator.detectComponentChanges(); spectator.detectComponentChanges();
expect(spectator.query('i')).toHaveClass('fa-spinner'); expect(spectator.query('i')).toHaveClass('fa-spinner');
spectator.setHostInput({ loading: false }); setInputSignal(spectator.component.loading, false);
spectator.detectComponentChanges(); spectator.detectComponentChanges();
expect(spectator.query('i')).toHaveClass('fa-check'); expect(spectator.query('i')).toHaveClass('fa-check');
expect(spectator.query('i')).not.toHaveClass('fa-spinner'); expect(spectator.query('i')).not.toHaveClass('fa-spinner');

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

@ -1,5 +1,6 @@
import { createHostFactory, SpectatorHost } from '@ngneat/spectator'; import { createHostFactory, SpectatorHost } from '@ngneat/spectator';
import { CardBodyComponent } from '../components'; import { CardBodyComponent } from '../components';
import { setInputSignal } from './utils';
describe('AbpCardBodyComponent', () => { describe('AbpCardBodyComponent', () => {
let spectator: SpectatorHost<CardBodyComponent>; let spectator: SpectatorHost<CardBodyComponent>;
@ -8,16 +9,17 @@ describe('AbpCardBodyComponent', () => {
beforeEach( beforeEach(
() => () =>
(spectator = createHost( {
` <abp-card-body spectator = createHost(
[cardBodyStyle]="{'background-color':'red'}" `<abp-card-body> <p>Body</p> </abp-card-body>`,
> {
<p>Body</p> detectChanges: false,
</abp-card-body>`, hostProps: { attributes: { autofocus: '', name: 'abp-card-body' } },
{ },
hostProps: { attributes: { autofocus: '', name: 'abp-card-body' } }, );
}, setInputSignal(spectator.component.cardBodyStyle, 'background-color: red;');
)), spectator.detectChanges();
},
); );
it('should create an instance', () => { it('should create an instance', () => {

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

@ -1,5 +1,6 @@
import { createHostFactory, SpectatorHost } from '@ngneat/spectator'; import { createHostFactory, SpectatorHost } from '@ngneat/spectator';
import { CardFooterComponent } from '../components'; import { CardFooterComponent } from '../components';
import { setInputSignal } from './utils';
describe('AbpCardFooterComponent', () => { describe('AbpCardFooterComponent', () => {
let spectator: SpectatorHost<CardFooterComponent>; let spectator: SpectatorHost<CardFooterComponent>;
@ -8,16 +9,19 @@ describe('AbpCardFooterComponent', () => {
beforeEach( beforeEach(
() => () =>
(spectator = createHost( {
`<abp-card-footer spectator = createHost(
[cardFooterStyle]="{'background-color':'red'}" `<abp-card-footer>
> <p>Footer</p>
<p>Footer</p> </abp-card-footer>`,
</abp-card-footer>`, {
{ detectChanges: false,
hostProps: { attributes: { autofocus: '', name: 'abp-card-footer' } }, hostProps: { attributes: { autofocus: '', name: 'abp-card-footer' } },
}, },
)), );
setInputSignal(spectator.component.cardFooterStyle, 'background-color: red;');
spectator.detectChanges();
},
); );
it('should create an instance', () => { it('should create an instance', () => {

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

@ -1,5 +1,6 @@
import { createHostFactory, SpectatorHost } from '@ngneat/spectator'; import { createHostFactory, SpectatorHost } from '@ngneat/spectator';
import { CardHeaderComponent } from '../components'; import { CardHeaderComponent } from '../components';
import { setInputSignal } from './utils';
describe('AbpCardHeaderComponent', () => { describe('AbpCardHeaderComponent', () => {
let spectator: SpectatorHost<CardHeaderComponent>; let spectator: SpectatorHost<CardHeaderComponent>;
@ -7,16 +8,17 @@ describe('AbpCardHeaderComponent', () => {
beforeEach( beforeEach(
() => () =>
(spectator = createHost( {
` <abp-card-header spectator = createHost(
[cardHeaderStyle]="{'background-color':'red'}" `<abp-card-header> Header </abp-card-header>`,
> {
Header detectChanges: false,
</abp-card-header>`, hostProps: { attributes: { autofocus: '', name: 'abp-card-header' } },
{ },
hostProps: { attributes: { autofocus: '', name: 'abp-card-header' } }, );
}, setInputSignal(spectator.component.cardHeaderStyle, 'background-color: red;');
)), spectator.detectChanges();
},
); );
it('should create an instance', () => { it('should create an instance', () => {

11
npm/ng-packs/packages/theme-shared/src/lib/tests/card-header.directive.spec.ts

@ -7,15 +7,14 @@ describe('AbpCardHeaderDirective', () => {
const createHost = createHostFactory(CardHeaderDirective); const createHost = createHostFactory(CardHeaderDirective);
beforeEach( beforeEach(
() => () => {
(spectator = createHost( spectator = createHost(
`<div `<div abpCardHeader></div>`,
abpCardHeader>
</div>`,
{ {
hostProps: { attributes: { autofocus: '', name: 'abp-card-header' } }, hostProps: { attributes: { autofocus: '', name: 'abp-card-header' } },
}, },
)), );
},
); );
it('should create an instance', () => { it('should create an instance', () => {

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

@ -1,5 +1,6 @@
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest'; import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest';
import { FormCheckboxComponent } from '../components/checkbox/checkbox.component'; import { FormCheckboxComponent } from '../components/checkbox/checkbox.component';
import { setInputSignal } from './utils';
describe('FormCheckboxComponent', () => { describe('FormCheckboxComponent', () => {
let spectator: SpectatorHost<FormCheckboxComponent>; let spectator: SpectatorHost<FormCheckboxComponent>;
@ -7,13 +8,18 @@ describe('FormCheckboxComponent', () => {
const createHost = createHostFactory(FormCheckboxComponent); const createHost = createHostFactory(FormCheckboxComponent);
beforeEach( beforeEach(
() => () => {
(spectator = createHost( spectator = createHost(
'<abp-checkbox></abp-checkbox>', '<abp-checkbox />',
{ {
detectChanges: false,
hostProps: { attributes: { autofocus: '', name: 'abp-checkbox' } }, hostProps: { attributes: { autofocus: '', name: 'abp-checkbox' } },
}, },
)), );
setInputSignal(spectator.component.checkboxId, 'checkbox-id');
setInputSignal(spectator.component.checkboxReadonly, true);
spectator.detectChanges();
},
); );
it('should display the input', () => { it('should display the input', () => {
@ -29,13 +35,13 @@ describe('FormCheckboxComponent', () => {
}); });
it('should be readonly when checkboxReadonly is true', () => { it('should be readonly when checkboxReadonly is true', () => {
spectator.component.checkboxReadonly = true; setInputSignal(spectator.component.checkboxReadonly, true);
spectator.detectComponentChanges(); spectator.detectComponentChanges();
expect(spectator.query('[readonly]')).toBeTruthy(); expect(spectator.query('[readonly]')).toBeTruthy();
}); });
it('should not contain readonly when checboxReadonly is false', () => { it('should not contain readonly when checboxReadonly is false', () => {
spectator.component.checkboxReadonly = false; setInputSignal(spectator.component.checkboxReadonly, false);
spectator.detectComponentChanges(); spectator.detectComponentChanges();
expect(spectator.query('[disabled]')).toBeFalsy(); expect(spectator.query('[disabled]')).toBeFalsy();
}); });

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

@ -1,5 +1,6 @@
import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest';
import { EllipsisDirective } from '../directives/ellipsis.directive'; import { EllipsisDirective } from '../directives/ellipsis.directive';
import { setInputSignal } from './utils';
describe('EllipsisDirective', () => { describe('EllipsisDirective', () => {
let spectator: SpectatorDirective<EllipsisDirective>; let spectator: SpectatorDirective<EllipsisDirective>;
@ -10,15 +11,13 @@ describe('EllipsisDirective', () => {
}); });
beforeEach(() => { beforeEach(() => {
spectator = createDirective( spectator = createDirective('<div abpEllipsis>test content</div>', {
'<div [abpEllipsis]="width" [abpEllipsisEnabled]="true" [title]="title">test content</div>', detectChanges: false,
{ });
hostProps: { setInputSignal(spectator.directive.width, '100px');
title: 'test title', setInputSignal(spectator.directive.enabled, true);
width: '100px', setInputSignal(spectator.directive.title, 'title');
}, spectator.detectChanges();
},
);
directive = spectator.directive; directive = spectator.directive;
el = spectator.query('div'); el = spectator.query('div');
}); });
@ -28,15 +27,15 @@ describe('EllipsisDirective', () => {
}); });
test('should have 100px ellipsis width', () => { test('should have 100px ellipsis width', () => {
expect(directive.width).toBe('100px'); expect(directive.width()).toBe('100px');
}); });
test('should be enabled if abpEllipsisEnabled input is true', () => { test('should be enabled if abpEllipsisEnabled input is true', () => {
expect(directive.enabled).toBe(true); expect(directive.enabled()).toBe(true);
}); });
test('should have given title', () => { test('should have given title', () => {
expect(directive.title).toBe('test title'); expect(directive.title()).toBe('title');
}); });
test('should add abp-ellipsis-inline class to element if width is given', () => { test('should add abp-ellipsis-inline class to element if width is given', () => {
@ -48,26 +47,25 @@ describe('EllipsisDirective when title is not specified', () => {
let spectator: SpectatorDirective<EllipsisDirective>; let spectator: SpectatorDirective<EllipsisDirective>;
let directive: EllipsisDirective; let directive: EllipsisDirective;
let el: HTMLDivElement; let el: HTMLDivElement;
const createDirective = createDirectiveFactory({ const createDirective = createDirectiveFactory({
directive: EllipsisDirective, directive: EllipsisDirective,
}); });
beforeEach(() => { beforeEach(() => {
spectator = createDirective( spectator = createDirective('<div abpEllipsis>test content</div>', {
'<div [abpEllipsis]="width" [abpEllipsisEnabled]="true" [title]="title">test content</div>', detectChanges: false,
{ });
hostProps: { setInputSignal(spectator.directive.width, '100px');
title: undefined, setInputSignal(spectator.directive.enabled, true);
width: '100px', setInputSignal(spectator.directive.title, undefined);
}, spectator.detectChanges();
},
);
directive = spectator.directive; directive = spectator.directive;
el = spectator.query('div') as HTMLDivElement; el = spectator.query('div') as HTMLDivElement;
}); });
test('should have element innerText as title', () => { test('should have element innerText as title', () => {
expect(directive.title).toBe(el.innerText); expect(directive.title()).toBe(el.innerText);
}); });
}); });
@ -80,15 +78,13 @@ describe('EllipsisDirective when width is not given', () => {
}); });
beforeEach(() => { beforeEach(() => {
spectator = createDirective( spectator = createDirective('<div abpEllipsis>test content</div>', {
'<div [abpEllipsis]="width" [abpEllipsisEnabled]="true" [title]="title">test content</div>', detectChanges: false,
{ });
hostProps: { setInputSignal(spectator.directive.width, undefined);
title: 'test title', setInputSignal(spectator.directive.enabled, true);
width: undefined, setInputSignal(spectator.directive.title, 'test title');
}, spectator.detectChanges();
},
);
directive = spectator.directive; directive = spectator.directive;
el = spectator.query('div') as HTMLDivElement; el = spectator.query('div') as HTMLDivElement;
}); });

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

@ -1,5 +1,6 @@
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest'; import { createHostFactory, SpectatorHost } from '@ngneat/spectator/vitest';
import { FormInputComponent } from '../components/form-input/form-input.component'; import { FormInputComponent } from '../components/form-input/form-input.component';
import { setInputSignal } from './utils';
describe('FormInputComponent', () => { describe('FormInputComponent', () => {
@ -8,13 +9,14 @@ describe('FormInputComponent', () => {
const createHost = createHostFactory(FormInputComponent); const createHost = createHostFactory(FormInputComponent);
beforeEach( beforeEach(
() => () => {
(spectator = createHost( spectator = createHost(
'<abp-form-input></abp-form-input>', '<abp-form-input />',
{ {
hostProps: { attributes: { autofocus: '', name: 'abp-form-input' } }, hostProps: { attributes: { autofocus: '', name: 'abp-form-input' } },
}, },
)), );
},
); );
it('should display the input', () => { it('should display the input', () => {
@ -30,13 +32,13 @@ describe('FormInputComponent', () => {
}); });
it('should be readonly when inputReadonly is true', () => { it('should be readonly when inputReadonly is true', () => {
spectator.component.inputReadonly = true; setInputSignal(spectator.component.inputReadonly, true);
spectator.detectComponentChanges(); spectator.detectComponentChanges();
expect(spectator.query('[readonly]')).toBeTruthy(); expect(spectator.query('[readonly]')).toBeTruthy();
}); });
it('should not contain readonly when inputReadonly is false', () => { it('should not contain readonly when inputReadonly is false', () => {
spectator.component.inputReadonly = false; setInputSignal(spectator.component.inputReadonly, false);
spectator.detectComponentChanges(); spectator.detectComponentChanges();
expect(spectator.query('[disabled]')).toBeFalsy(); expect(spectator.query('[disabled]')).toBeFalsy();
}); });

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

@ -4,7 +4,8 @@ import { NavigationStart, Router } from '@angular/router';
import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest';
import { combineLatest, firstValueFrom, Subject, timer } from 'rxjs'; import { combineLatest, firstValueFrom, Subject, timer } from 'rxjs';
import { LoaderBarComponent } from '../components/loader-bar/loader-bar.component'; import { LoaderBarComponent } from '../components/loader-bar/loader-bar.component';
import { setupComponentResources } from './utils'; import { setupComponentResources, setInputSignal } from './utils';
describe('LoaderBarComponent', () => { describe('LoaderBarComponent', () => {
let spectator: Spectator<LoaderBarComponent>; let spectator: Spectator<LoaderBarComponent>;
@ -34,8 +35,8 @@ describe('LoaderBarComponent', () => {
}); });
it('should initial variable values are correct', () => { it('should initial variable values are correct', () => {
expect(spectator.component.containerClass).toBe('abp-loader-bar'); expect(spectator.component.containerClass()).toBe('abp-loader-bar');
expect(spectator.component.color).toBe('#77b6ff'); expect(spectator.component.color()).toBe('#77b6ff');
}); });
it('should increase the progressLevel', async () => { it('should increase the progressLevel', async () => {
@ -45,8 +46,7 @@ describe('LoaderBarComponent', () => {
spectator.detectChanges(); spectator.detectChanges();
await new Promise(resolve => setTimeout(resolve, 10)); await new Promise(resolve => setTimeout(resolve, 10));
expect(spectator.component.progressLevel()).toBeGreaterThan(0);
expect(spectator.component.progressLevel > 0).toBeTruthy();
}); });
@ -100,15 +100,15 @@ describe('LoaderBarComponent', () => {
spectator.detectChanges(); spectator.detectChanges();
attempts = 0; attempts = 0;
while (spectator.component.progressLevel !== 100 && attempts < 50) { while (spectator.component.progressLevel() !== 100 && attempts < 50) {
await new Promise(resolve => setTimeout(resolve, 10)); await new Promise(resolve => setTimeout(resolve, 10));
spectator.detectChanges(); spectator.detectChanges();
attempts++; attempts++;
} }
expect(spectator.component.progressLevel).toBe(100); expect(spectator.component.progressLevel()).toBe(100);
await firstValueFrom(timer(spectator.component.stopDelay + 10)); await firstValueFrom(timer(spectator.component.stopDelay + 10));
expect(spectator.component.progressLevel).toBe(0); expect(spectator.component.progressLevel()).toBe(0);
}); });
it('should stop the loading with navigation', async () => { it('should stop the loading with navigation', async () => {
@ -130,15 +130,15 @@ describe('LoaderBarComponent', () => {
spectator.detectChanges(); spectator.detectChanges();
attempts = 0; attempts = 0;
while (spectator.component.progressLevel !== 100 && attempts < 50) { while (spectator.component.progressLevel() !== 100 && attempts < 50) {
await new Promise(resolve => setTimeout(resolve, 10)); await new Promise(resolve => setTimeout(resolve, 10));
spectator.detectChanges(); spectator.detectChanges();
attempts++; attempts++;
} }
expect(spectator.component.progressLevel).toBe(100); expect(spectator.component.progressLevel()).toBe(100);
await firstValueFrom(timer(spectator.component.stopDelay + 10)); await firstValueFrom(timer(spectator.component.stopDelay + 10));
expect(spectator.component.progressLevel).toBe(0); expect(spectator.component.progressLevel()).toBe(0);
}); });
describe('#startLoading', () => { describe('#startLoading', () => {

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

@ -1,7 +1,8 @@
import { Component } from '@angular/core';
import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/vitest'; import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/vitest';
import { LoadingDirective } from '../directives'; import { LoadingDirective } from '../directives';
import { LoadingComponent } from '../components'; import { LoadingComponent } from '../components';
import { Component } from '@angular/core'; import { setInputSignal } from './utils';
@Component({ @Component({
selector: 'abp-dummy', selector: 'abp-dummy',
@ -20,7 +21,7 @@ describe('LoadingDirective', () => {
describe('default', () => { describe('default', () => {
beforeEach(() => { beforeEach(() => {
spectator = createDirective('<div [abpLoading]="loading">Testing Loading Directive</div>', { spectator = createDirective('<div abpLoading>Testing Loading Directive</div>', {
hostProps: { loading: true }, hostProps: { loading: true },
}); });
}); });
@ -30,10 +31,10 @@ describe('LoadingDirective', () => {
}); });
it('should handle loading input', async () => { it('should handle loading input', async () => {
spectator.directive.loading = false; setInputSignal(spectator.directive.loading, false);
await new Promise(resolve => setTimeout(resolve, 10)); await new Promise(resolve => setTimeout(resolve, 10));
expect(spectator.directive).toBeTruthy(); expect(spectator.directive).toBeTruthy();
expect(spectator.directive.loading).toBe(false); expect(spectator.directive.loading()).toBe(false);
}); });
}); });
@ -42,11 +43,15 @@ describe('LoadingDirective', () => {
beforeEach(() => { beforeEach(() => {
spectator = createDirective( spectator = createDirective(
'<div [abpLoading]="loading" [abpLoadingDelay]="delay" [abpLoadingTargetElement]="target">Testing Loading Directive</div>', '<div abpLoading>Testing Loading Directive</div>',
{ {
hostProps: { loading: true, target: mockTarget, delay: 0 }, detectChanges: false,
}, },
); );
setInputSignal(spectator.directive.loading, true);
setInputSignal(spectator.directive.delay, 0);
setInputSignal(spectator.directive.targetElementInput, mockTarget);
spectator.detectChanges();
}); });
it('should create directive with custom target', () => { it('should create directive with custom target', () => {
@ -55,17 +60,17 @@ describe('LoadingDirective', () => {
}); });
it('should handle delay input', async () => { it('should handle delay input', async () => {
spectator.directive.delay = 100; setInputSignal(spectator.directive.delay, 100);
await new Promise(resolve => setTimeout(resolve, 10)); await new Promise(resolve => setTimeout(resolve, 10));
expect(spectator.directive).toBeTruthy(); expect(spectator.directive).toBeTruthy();
}); });
it('should handle loading state changes', async() => { it('should handle loading state changes', async() => {
spectator.directive.loading = false; setInputSignal(spectator.directive.loading, false);
await new Promise(resolve => setTimeout(resolve, 10)); await new Promise(resolve => setTimeout(resolve, 10));
expect(spectator.directive).toBeTruthy(); expect(spectator.directive).toBeTruthy();
spectator.directive.loading = true; setInputSignal(spectator.directive.loading, true);
await new Promise(resolve => setTimeout(resolve, 10)); await new Promise(resolve => setTimeout(resolve, 10));
expect(spectator.directive).toBeTruthy(); expect(spectator.directive).toBeTruthy();
}); });
@ -73,7 +78,7 @@ describe('LoadingDirective', () => {
describe('with a component selector', () => { describe('with a component selector', () => {
beforeEach(() => { beforeEach(() => {
spectator = createDirective('<abp-dummy [abpLoading]="loading"></abp-dummy>', { spectator = createDirective('<abp-dummy abpLoading></abp-dummy>', {
hostProps: { loading: true }, hostProps: { loading: true },
}); });
}); });

22
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 { ConfirmationService } from '@abp/ng.theme.shared';
import { CoreTestingModule } from '@abp/ng.core/testing'; import { CoreTestingModule } from '@abp/ng.core/testing';
import { Component, EventEmitter, Input } from '@angular/core'; import { Component, EventEmitter, input } from '@angular/core';
import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest'; import { createComponentFactory, Spectator } from '@ngneat/spectator/vitest';
import { Confirmation } from '@abp/ng.theme.shared'; import { Confirmation } from '@abp/ng.theme.shared';
import { firstValueFrom, Subject, timer } from 'rxjs'; import { firstValueFrom, Subject, timer } from 'rxjs';
import { ModalComponent } from '../components/modal/modal.component'; import { ModalComponent } from '../components/modal/modal.component';
import { setInputSignal } from './utils';
import { setupComponentResources } from './utils'; import { setupComponentResources } from './utils';
@Component({ @Component({
@ -22,8 +23,8 @@ import { setupComponentResources } from './utils';
imports: [ModalComponent] imports: [ModalComponent]
}) })
class TestHostComponent { class TestHostComponent {
@Input() visible = false; visible = input(false);
@Input() busy = false; busy = input(false);
visibleChange = new EventEmitter<boolean>(); visibleChange = new EventEmitter<boolean>();
} }
@ -33,10 +34,13 @@ const disappearFn = vi.fn();
describe('ModalComponent', () => { describe('ModalComponent', () => {
let spectator: Spectator<TestHostComponent>; let spectator: Spectator<TestHostComponent>;
let createComponent: ReturnType<typeof createComponentFactory<TestHostComponent>>; let createComponent: ReturnType<typeof createComponentFactory<TestHostComponent>>;
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
beforeAll(() => setupComponentResources('../components/modal', import.meta.url)); beforeAll(() => setupComponentResources('../components/modal', import.meta.url));
beforeEach(() => { beforeEach(() => {
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
// Create component factory in beforeEach to ensure beforeAll has run // Create component factory in beforeEach to ensure beforeAll has run
if (!createComponent) { if (!createComponent) {
createComponent = createComponentFactory({ createComponent = createComponentFactory({
@ -60,20 +64,24 @@ describe('ModalComponent', () => {
disappearFn.mockClear(); disappearFn.mockClear();
}); });
afterEach(() => {
consoleErrorSpy.mockRestore();
});
it('should create component', () => { it('should create component', () => {
expect(spectator.component).toBeTruthy(); expect(spectator.component).toBeTruthy();
}); });
it('should handle visible input', () => { it('should handle visible input', () => {
spectator.setInput('visible', true); setInputSignal(spectator.component.visible, true);
spectator.detectChanges(); spectator.detectChanges();
expect(spectator.component.visible).toBe(true); expect(spectator.component.visible()).toBe(true);
}); });
it('should handle busy input', () => { it('should handle busy input', () => {
spectator.setInput('busy', true); setInputSignal(spectator.component.busy, true);
spectator.detectChanges(); spectator.detectChanges();
expect(spectator.component.busy).toBe(true); expect(spectator.component.busy()).toBe(true);
}); });
it('should have visibleChange emitter', () => { it('should have visibleChange emitter', () => {

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

@ -8,10 +8,13 @@ import { ToasterService } from '../services/toaster.service';
describe('ToasterService', () => { describe('ToasterService', () => {
let spectator: SpectatorService<ToasterService>; let spectator: SpectatorService<ToasterService>;
let service: ToasterService; let service: ToasterService;
const mockComponentRef = { const mockComponentRef = {
changeDetectorRef: { detectChanges: vi.fn() }, changeDetectorRef: { detectChanges: vi.fn() },
instance: {} as ToastContainerComponent, instance: {
} as unknown as ComponentRef<ToastContainerComponent>; setToasts: vi.fn(), remove: vi.fn(), toasts: [], top: 0, right: 0, bottom: 0, left: 0,
},
};
const contentProjectionService = { const contentProjectionService = {
projectContent: vi.fn().mockReturnValue(mockComponentRef), projectContent: vi.fn().mockReturnValue(mockComponentRef),

1
npm/ng-packs/packages/theme-shared/src/lib/tests/utils/index.ts

@ -1 +1,2 @@
export * from './input-signal';
export * from './setup-component-resources'; export * from './setup-component-resources';

6
npm/ng-packs/packages/theme-shared/src/lib/tests/utils/input-signal.ts

@ -0,0 +1,6 @@
import { ɵSIGNAL as SIGNAL } from '@angular/core';
export const setInputSignal = <T>(inputSignal: () => T, value: T) => {
const node = inputSignal[SIGNAL];
node.applyValueToInputSignal(node, value);
};
Loading…
Cancel
Save