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",
"help": "nx help",
"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",
"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",
@ -49,30 +49,31 @@
"private": true,
"devDependencies": {
"@abp/ng.theme.lepton-x": "~5.5.0",
"@abp/ng.schematics": "~10.5.0",
"@abp/utils": "~10.5.0",
"@angular-devkit/build-angular": "~21.2.0",
"@angular-devkit/core": "~21.2.0",
"@angular-devkit/schematics": "~21.2.0",
"@angular-devkit/schematics-cli": "~21.2.0",
"@angular-eslint/eslint-plugin": "~21.2.0",
"@angular-eslint/eslint-plugin-template": "~21.2.0",
"@angular-eslint/template-parser": "~21.2.0",
"@angular/animations": "~21.2.0",
"@angular/aria": "~21.2.0",
"@angular/build": "~21.2.0",
"@angular/cli": "~21.2.0",
"@angular/common": "~21.2.0",
"@angular/compiler": "~21.2.0",
"@angular/compiler-cli": "~21.2.0",
"@angular/core": "~21.2.0",
"@angular/forms": "~21.2.0",
"@angular/language-service": "~21.2.0",
"@angular/localize": "~21.2.0",
"@angular/platform-browser": "~21.2.0",
"@angular/platform-browser-dynamic": "~21.2.0",
"@angular/platform-server": "~21.2.0",
"@angular/router": "~21.2.0",
"@angular/ssr": "~21.2.0",
"@angular-devkit/build-angular": "~22.0.0",
"@angular-devkit/core": "~22.0.0",
"@angular-devkit/schematics": "~22.0.0",
"@angular-devkit/schematics-cli": "~22.0.0",
"@angular-eslint/eslint-plugin": "~22.0.0",
"@angular-eslint/eslint-plugin-template": "~22.0.0",
"@angular-eslint/template-parser": "~22.0.0",
"@angular/animations": "~22.0.0",
"@angular/aria": "~22.0.0",
"@angular/build": "~22.0.0",
"@angular/cli": "~22.0.0",
"@angular/common": "~22.0.0",
"@angular/compiler": "~22.0.0",
"@angular/compiler-cli": "~22.0.0",
"@angular/core": "~22.0.0",
"@angular/forms": "~22.0.0",
"@angular/language-service": "~22.0.0",
"@angular/localize": "~22.0.0",
"@angular/platform-browser": "~22.0.0",
"@angular/platform-browser-dynamic": "~22.0.0",
"@angular/platform-server": "~22.0.0",
"@angular/router": "~22.0.0",
"@angular/ssr": "~22.0.0",
"@fortawesome/fontawesome-free": "^6.0.0",
"@ng-bootstrap/ng-bootstrap": "~20.0.0",
"@ngneat/spectator": "~19.6.2",
@ -159,4 +160,4 @@
"dependencies": {
"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() {
}
[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 {
return isPlatformBrowser(this.platformId) ? localStorage.length : 0;
return this.storage?.length || 0;
}
clear(): void {
if (isPlatformBrowser(this.platformId)) {
localStorage.clear();
}
this.storage?.clear();
}
getItem(key: string): string | null {
if (!isPlatformBrowser(this.platformId)) {
return null;
}
return localStorage.getItem(key);
return this.storage?.getItem(key) || null;
}
key(index: number): string | null {
if (!isPlatformBrowser(this.platformId)) {
return null;
}
return localStorage.key(index);
return this.storage?.key(index) || null;
}
removeItem(key: string): void {
if (isPlatformBrowser(this.platformId)) {
localStorage.removeItem(key);
}
this.storage?.removeItem(key);
}
setItem(key: string, value: string): void {
if (isPlatformBrowser(this.platformId)) {
localStorage.setItem(key, value);
}
this.storage?.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 { AutofocusDirective } from '../directives/autofocus.directive';
import { timer } from 'rxjs';
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
describe('AutofocusDirective', () => {
let spectator: SpectatorDirective<AutofocusDirective>;
@ -11,26 +11,32 @@ describe('AutofocusDirective', () => {
});
beforeEach(() => {
spectator = createDirective('<input [autofocus]="10" />', {
hostProps: {},
});
vi.useFakeTimers();
spectator = createDirective('<input autofocus />');
directive = spectator.directive;
input = spectator.query('input');
});
afterEach(() => {
if (vi.isFakeTimers()) {
vi.runOnlyPendingTimers();
}
vi.useRealTimers();
});
test('should be created', () => {
expect(directive).toBeTruthy();
});
test('should have 10ms delay', () => {
expect(directive.delay).toBe(10);
test('should have 0ms delay', () => {
expect(directive.delay()).toBe(0);
});
test('should focus element after given delay', () => {
timer(0).subscribe(() => expect('input').not.toBeFocused());
timer(11).subscribe(() => {
expect('input').toBeFocused();
expect.hasAssertions();
});
test('should focus element after default delay', () => {
expect(input).not.toBeFocused();
vi.runOnlyPendingTimers();
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';
@Component({
template: ` <input (abpCapsLock)="capsLock = $event" /> `,
template: ` <input abpCapsLock /> `,
imports: [TrackCapsLockDirective],
})
class TestComponent {
@ -14,6 +14,20 @@ class TestComponent {
describe('TrackCapsLockDirective', () => {
let fixture: ComponentFixture<TestComponent>;
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(() => {
fixture = TestBed.configureTestingModule({
@ -23,35 +37,29 @@ describe('TrackCapsLockDirective', () => {
fixture.detectChanges();
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'])(
'is %p works when press capslock and is emit status',
eventName => {
const event = new KeyboardEvent(eventName, {
key: 'CapsLock',
modifierCapsLock: true,
});
window.dispatchEvent(event);
fixture.detectChanges();
expect(fixture.componentInstance.capsLock).toBe(true);
const event = createCapsLockEvent(eventName, true);
eventName === 'keydown' ? directive.onKeyDown(event) : directive.onKeyUp(event);
expect(emitSpy).toHaveBeenCalledWith(true);
},
);
test.each(['keydown', 'keyup'])('is %p detect the change capslock is emit status', eventName => {
const trueEvent = new KeyboardEvent(eventName, {
key: 'CapsLock',
modifierCapsLock: true,
});
window.dispatchEvent(trueEvent);
fixture.detectChanges();
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);
const trueEvent = createCapsLockEvent(eventName, true);
eventName === 'keydown' ? directive.onKeyDown(trueEvent) : directive.onKeyUp(trueEvent);
expect(emitSpy).toHaveBeenCalledWith(true);
const falseEvent = createCapsLockEvent(eventName, false);
eventName === 'keydown' ? directive.onKeyDown(falseEvent) : directive.onKeyUp(falseEvent);
expect(emitSpy).toHaveBeenLastCalledWith(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 { InputEventDebounceDirective } from '../directives/debounce.directive';
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
describe('InputEventDebounceDirective', () => {
let spectator: SpectatorDirective<InputEventDebounceDirective>;
@ -13,7 +13,9 @@ describe('InputEventDebounceDirective', () => {
});
beforeEach(() => {
spectator = createDirective('<input (input.debounce)="inputEventFn()" [debounce]="20" />', {
vi.useFakeTimers();
spectator = createDirective('<input input.debounce (input.debounce)="inputEventFn()" />', {
hostProps: { inputEventFn },
});
directive = spectator.directive;
@ -21,18 +23,31 @@ describe('InputEventDebounceDirective', () => {
inputEventFn.mockClear();
});
afterEach(() => {
if (vi.isFakeTimers()) {
vi.runOnlyPendingTimers();
}
vi.useRealTimers();
});
test('should be created', () => {
expect(directive).toBeTruthy();
});
test('should have 20ms debounce time', () => {
expect(directive.debounce).toBe(20);
test('should have 300ms debounce time', () => {
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);
timer(0).subscribe(() => expect(inputEventFn).not.toHaveBeenCalled());
await firstValueFrom(timer(21));
expect(inputEventFn).toHaveBeenCalled();
expect(emitSpy).not.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 () => {
const injector = spectator.inject(Injector);
const injectorSpy = jest.spyOn(injector, 'get');
const injectorSpy = vi.spyOn(injector, 'get');
const http = spectator.inject(HttpClient);
const requestSpy = jest.spyOn(http, 'request');
const requestSpy = vi.spyOn(http, 'request');
const environmentService = spectator.inject(EnvironmentService);
const setStateSpy = jest.spyOn(environmentService, 'setState');
const setStateSpy = vi.spyOn(environmentService, 'setState');
injectorSpy.mockReturnValueOnce(environmentService);
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 moduleRef = factory.create(injector);
expect('componentFactoryResolver' in moduleRef).toBe(true);
expect('destroy' in moduleRef).toBe(true);
expect('injector' in moduleRef).toBe(true);
expect('instance' 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 { ForDirective } from '../directives/for.directive';
import { setInputSignal } from './utils';
describe('ForDirective', () => {
let spectator: SpectatorDirective<ForDirective>;
@ -9,12 +11,44 @@ describe('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', () => {
beforeEach(() => {
spectator = createDirective('<ul><li *abpFor="let item of items">{{ item }}</li></ul>', {
hostProps: { items },
});
spectator = createDirective('<ul><ng-template abpFor let-item><li>{{ item }}</li></ng-template></ul>');
directive = spectator.directive;
setDirectiveInputs({ items });
});
test('should be created', () => {
@ -29,166 +63,116 @@ describe('ForDirective', () => {
});
test('should sync the DOM when change items', () => {
directive.items = [10, 11, 12];
directive['vcRef'].clear();
directive['lastItemsRef'] = null;
directive['differ'] = null;
directive.ngOnChanges();
spectator.detectChanges();
const elements = spectator.queryAll('li');
setDirectiveInputs({ items: [10, 11, 12] });
const elements = spectator.queryAll('li');
expect(elements[1]).toHaveText('11');
expect(elements).toHaveLength(3);
});
test('should sync the DOM when add an item', () => {
directive.items = [...items, 6];
directive['vcRef'].clear();
directive['lastItemsRef'] = null;
directive['differ'] = null;
directive.ngOnChanges();
spectator.detectChanges();
const elements = spectator.queryAll('li');
setDirectiveInputs({ items: [...items, 6] });
const elements = spectator.queryAll('li');
expect(elements[6]).toHaveText('6');
expect(elements).toHaveLength(7);
});
});
describe('trackBy', () => {
const trackByFn = (_, item) => item;
const trackByFn = (_: number, item: number) => item;
beforeEach(() => {
spectator = createDirective(
'<ul><li *abpFor="let item of items; trackBy: trackByFn">{{ item }}</li></ul>',
{
hostProps: { items, trackByFn },
},
);
spectator = createDirective('<ul><ng-template abpFor let-item><li>{{ item }}</li></ng-template></ul>');
directive = spectator.directive;
setDirectiveInputs({ items, trackBy: trackByFn });
});
test('should be setted the trackBy', () => {
expect(directive.trackBy).toEqual(trackByFn);
expect(directive.trackBy()).toEqual(trackByFn);
});
});
describe('with basic order', () => {
beforeEach(() => {
spectator = createDirective(
`<ul>
<li
*abpFor="let item of [3,6,2];
orderDir: 'ASC'">
{{ item }}
</li>
</ul>`,
);
spectator = createDirective('<ul><ng-template abpFor let-item><li>{{ item }}</li></ng-template></ul>');
directive = spectator.directive;
setDirectiveInputs({ items: [3, 6, 2], orderDir: 'ASC' });
});
test('should order by asc', () => {
const elements = spectator.queryAll('li');
expect(elements.map(el => el.textContent.trim())).toEqual(['2', '3', '6']);
expect(getTexts()).toEqual(['2', '3', '6']);
});
});
describe('with order', () => {
beforeEach(() => {
spectator = createDirective(
`<ul>
<li
*abpFor="let item of [{value: 3}, {value: 6}, {value: 2}];
orderBy: 'value';
orderDir: orderDir">
{{ item.value }}
</li>
</ul>`,
{
hostProps: { orderDir: 'ASC' },
},
'<ul><ng-template abpFor let-item><li>{{ item.value }}</li></ng-template></ul>',
);
directive = spectator.directive;
setDirectiveInputs({
items: [{ value: 3 }, { value: 6 }, { value: 2 }],
orderBy: 'value',
orderDir: 'ASC',
});
});
test('should order by asc', () => {
const elements = spectator.queryAll('li');
expect(elements.map(el => el.textContent.trim())).toEqual(['2', '3', '6']);
expect(getTexts()).toEqual(['2', '3', '6']);
});
test('should order by desc', () => {
directive.orderDir = 'DESC';
directive['vcRef'].clear();
directive['lastItemsRef'] = null;
directive['differ'] = null;
directive.ngOnChanges();
spectator.detectChanges();
setDirectiveInputs({ orderDir: 'DESC' });
const elements = spectator.queryAll('li');
expect(elements.map(el => el.textContent.trim())).toEqual(['6', '3', '2']);
expect(getTexts()).toEqual(['6', '3', '2']);
});
});
describe('with filter', () => {
beforeEach(() => {
spectator = createDirective(
`<ul>
<li
*abpFor="let item of [{value: 'test'}, {value: 'abp'}, {value: 'volo'}];
filterBy: 'value';
filterVal: filterVal">
{{ item.value }}
</li>
</ul>`,
{
hostProps: { filterVal: '' },
},
'<ul><ng-template abpFor let-item><li>{{ item.value }}</li></ng-template></ul>',
);
directive = spectator.directive;
setDirectiveInputs({
items: [{ value: 'test' }, { value: 'abp' }, { value: 'volo' }],
filterBy: 'value',
filterVal: '',
});
});
test('should not filter when filterVal is empty,', () => {
const elements = spectator.queryAll('li');
expect(elements.map(el => el.textContent.trim())).toEqual(['test', 'abp', 'volo']);
expect(getTexts()).toEqual(['test', 'abp', 'volo']);
});
test('should be filtered', () => {
directive.filterVal = 'volo';
directive['vcRef'].clear();
directive['lastItemsRef'] = null;
directive['differ'] = null;
directive.ngOnChanges();
spectator.detectChanges();
setDirectiveInputs({ filterVal: 'volo' });
expect(spectator.query('li')).toHaveText('volo');
});
test('should not show an element when filter value not match to any text', () => {
directive.filterVal = 'volos';
directive.ngOnChanges();
spectator.detectChanges();
setDirectiveInputs({ filterVal: 'volos' });
const elements = spectator.queryAll('li');
expect(elements).toHaveLength(0);
expect(spectator.queryAll('li')).toHaveLength(0);
});
});
describe('with empty ref', () => {
beforeEach(() => {
spectator = createDirective(
`<ul>
<li
*abpFor="let item of items;
emptyRef: empty">
{{ item.value }}
</li>
spectator = createDirective(`
<ul>
<ng-template abpFor let-item>
<li>{{ item.value }}</li>
</ng-template>
<ng-template #empty>No records found</ng-template>
</ul>`,
{
hostProps: { items: [] },
},
);
</ul>
`);
directive = spectator.directive;
const [, emptyRef] = getTemplateRefs();
setDirectiveInputs({ items: [], emptyRef });
});
test('should display the empty ref', () => {
@ -200,12 +184,7 @@ describe('ForDirective', () => {
expect(spectator.query('ul')).toHaveText('No records found');
expect(spectator.queryAll('li')).toHaveLength(0);
directive.items = [0];
directive['vcRef'].clear();
directive['lastItemsRef'] = null;
directive['differ'] = null;
directive.ngOnChanges();
spectator.detectChanges();
setDirectiveInputs({ items: [{ value: 0 }] });
expect(spectator.query('ul')).not.toHaveText('No records found');
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 { FormSubmitDirective } from '../directives/form-submit.directive';
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', () => {
let spectator: SpectatorDirective<FormSubmitDirective>;
@ -16,8 +18,10 @@ describe('FormSubmitDirective', () => {
});
beforeEach(() => {
vi.useFakeTimers();
spectator = createDirective(
'<form [formGroup]="formGroup" (ngSubmit)="submitEventFn()" [debounce]="20">form content</form>',
'<form ngSubmit [formGroup]="formGroup" (ngSubmit)="submitEventFn()">form content</form>',
{
hostProps: {
submitEventFn,
@ -26,6 +30,14 @@ describe('FormSubmitDirective', () => {
},
);
directive = spectator.directive;
setInputSignal(directive.debounce, 20);
});
afterEach(() => {
if (vi.isFakeTimers()) {
vi.runOnlyPendingTimers();
}
vi.useRealTimers();
});
test('should be created', () => {
@ -33,7 +45,7 @@ describe('FormSubmitDirective', () => {
});
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 () => {
@ -44,8 +56,12 @@ describe('FormSubmitDirective', () => {
cancelable: true,
});
form?.dispatchEvent(event);
timer(0).subscribe(() => expect(submitEventFn).not.toHaveBeenCalled());
await firstValueFrom(timer(directive.debounce + 10));
expect(submitEventFn).not.toHaveBeenCalled();
vi.advanceTimersByTime(199);
expect(submitEventFn).not.toHaveBeenCalled();
vi.advanceTimersByTime(1);
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', () => {
let service: AbpLocalStorageService;
let localStorageMock: Storage;
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({});
service = TestBed.inject(AbpLocalStorageService);
});
afterEach(() => {
vi.unstubAllGlobals();
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should be called getItem', () => {
const spy = vi.spyOn(service, 'getItem');
service.getItem('test');
expect(spy).toHaveBeenCalled();
expect(localStorageMock.getItem).toHaveBeenCalledWith('test');
});
it('should be called setItem', () => {
const spy = vi.spyOn(service, 'setItem');
service.setItem('test', 'value');
expect(spy).toHaveBeenCalled();
expect(localStorageMock.setItem).toHaveBeenCalledWith('test', 'value');
});
it('should be called removeItem', () => {
const spy = vi.spyOn(service, 'removeItem');
service.removeItem('test');
expect(spy).toHaveBeenCalled();
expect(localStorageMock.removeItem).toHaveBeenCalledWith('test');
});
it('should be called clear', () => {
const spy = vi.spyOn(service, 'clear');
service.clear();
expect(spy).toHaveBeenCalled();
expect(localStorageMock.clear).toHaveBeenCalled();
});
it('should be called key', () => {
const spy = vi.spyOn(service, 'key');
service.key(0);
expect(spy).toHaveBeenCalled();
expect(localStorageMock.key).toHaveBeenCalledWith(0);
});
it('should be called length', () => {
const spy = vi.spyOn(service, 'length', 'get');
vi.stubGlobal('localStorage', { ...localStorageMock, length: 1 });
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 { PermissionService } from '../services/permission.service';
import { QUEUE_MANAGER } from '../tokens/queue.token';
import { setInputSignal } from './utils';
describe('PermissionDirective', () => {
let spectator: SpectatorDirective<PermissionDirective>;
@ -20,7 +21,7 @@ describe('PermissionDirective', () => {
beforeEach(() => {
spectator = createDirective(
'<div [abpPermission]="permission" [abpPermissionRunChangeDetection]="runCD"></div>',
'<div abpPermission>test</div>',
{
hostProps: { permission: 'test', runCD: false },
},
@ -44,19 +45,19 @@ describe('PermissionDirective', () => {
it('should handle permission input', () => {
grantedPolicy$.next(false);
directive.condition = 'new-permission';
setInputSignal(directive.condition, 'new-permission');
directive.ngOnChanges();
grantedPolicy$.next(true);
expect(directive).toBeTruthy();
expect(directive.condition).toBe('new-permission');
expect(directive.condition()).toBe('new-permission');
});
it('should handle runChangeDetection input', () => {
grantedPolicy$.next(false);
directive.runChangeDetection = true;
setInputSignal(directive.runChangeDetection, true);
directive.ngOnChanges();
grantedPolicy$.next(true);
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 { provideRouter, Route, Router } from '@angular/router';
import { RouterTestingHarness } from '@angular/router/testing';
@ -9,8 +7,8 @@ import { of } from 'rxjs';
import { permissionGuard } from '../guards/permission.guard';
import { HttpErrorReporterService } from '../services/http-error-reporter.service';
import { PermissionService } from '../services/permission.service';
import { provideAbpCore, withOptions } from '../providers';
import { AuthService } from '../abstracts';
import { ConfigStateService, RouteBasedCultureUrlService, RoutesService } from '../services';
@Component({ template: '' })
class DummyComponent {}
@ -20,8 +18,11 @@ class DummyComponent {}
describe('authGuard', () => {
let permissionService: SpyObject<PermissionService>;
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,
};
@ -53,46 +54,30 @@ describe('authGuard', () => {
beforeEach(() => {
httpErrorReporter = createSpyObject(HttpErrorReporterService);
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({
providers: [
provideHttpClient(),
provideHttpClientTesting(),
{ provide: AuthService, useValue: mockOAuthService },
{ provide: AuthService, useValue: authService },
{ provide: PermissionService, useValue: permissionService },
{ provide: HttpErrorReporterService, useValue: httpErrorReporter },
{ provide: RoutesService, useValue: routesService },
{ provide: RouteBasedCultureUrlService, useValue: routeCultureUrl },
{ provide: ConfigStateService, useValue: configStateService },
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 () => {
permissionService.getGrantedPolicy$.andReturn(of(true));
await RouterTestingHarness.create('/dummy');
@ -103,17 +88,24 @@ describe('authGuard', () => {
it('should return false and report an error when the grantedPolicy is false', () => {
permissionService.getGrantedPolicy$.andReturn(of(false));
expect(permissionService.getGrantedPolicy$).toBeDefined();
expect(httpErrorReporter.reportError).toBeDefined();
return RouterTestingHarness.create('/dummy').then(() => {
expect(TestBed.inject(Router).url).toEqual('/');
expect(httpErrorReporter.reportError).toHaveBeenCalledWith({ status: 403 });
});
});
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 => {
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();
});

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 { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest';
import { BehaviorSubject } from 'rxjs';
import { ReplaceableTemplateDirective } from '../directives/replaceable-template.directive';
import { ReplaceableComponents } from '../models/replaceable-components';
import { ReplaceableComponentsService } from '../services/replaceable-components.service';
import { setInputSignal } from './utils';
@Component({
selector: 'abp-default-component',
template: ' <p>default</p> ',
exportAs: 'abpDefaultComponent'
exportAs: 'abpDefaultComponent',
})
class DefaultComponent {
@Input()
oneWay;
onOneWay = input<any>();
@Input()
twoWay: boolean;
twoWay = input<boolean>();
readonly twoWayChange = output<boolean>();
readonly someOutput = output<string>();
setTwoWay(value) {
this.twoWay = value;
setInputSignal(this.twoWay, value);
this.twoWayChange.emit(value);
}
}
@Component({
selector: 'abp-external-component',
template: ' <p>external</p> '
template: ' <p>external</p> ',
})
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', () => {
@ -54,11 +52,12 @@ describe('ReplaceableTemplateDirective', () => {
beforeEach(() => {
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>
</div>
</ng-template>
`,
{
detectChanges: false,
hostProps: {
oneWay: { label: 'Test' },
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', () => {
@ -78,19 +86,22 @@ describe('ReplaceableTemplateDirective', () => {
it('should create directive successfully', () => {
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>
</div>
</ng-template>
`,
{
hostProps: {
oneWay: { label: 'Test' },
twoWay: false,
twoWayChange: vi.fn(),
someOutput: vi.fn(),
},
detectChanges: false,
},
);
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();
});
});

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 { By } from '@angular/platform-browser';
import { ShowPasswordDirective } from '../directives';
import { setInputSignal } from './utils';
@Component({
template: ` <input [abpShowPassword]="true" />
<input [abpShowPassword]="false" />
template: ` <input abpShowPassword />
<input abpShowPassword />
<input />
<input [abpShowPassword]="showPassword" />`,
<input abpShowPassword />`,
imports: [ShowPasswordDirective],
})
class TestComponent {
showPassword = false;
}
class TestComponent {}
describe('ShowPasswordDirective', () => {
let fixture: ComponentFixture<TestComponent>;
let des: 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(() => {
fixture = TestBed.configureTestingModule({
imports: [TestComponent],
}).createComponent(TestComponent);
fixture.detectChanges();
detectChanges();
des = fixture.debugElement.queryAll(By.directive(ShowPasswordDirective));
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', () => {
@ -52,11 +61,7 @@ describe('ShowPasswordDirective', () => {
const input = des[2].nativeElement;
expect(input.type).toBe('password');
fixture.componentInstance.showPassword = true;
const cdr = fixture.componentRef.injector.get(ChangeDetectorRef);
cdr.markForCheck();
cdr.detectChanges();
setShowPassword(2, true);
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 { StopPropagationDirective } from '../directives/stop-propagation.directive';
import { beforeEach, describe, expect, test, vi } from 'vitest';
describe('StopPropagationDirective', () => {
let spectator: SpectatorDirective<StopPropagationDirective>;
@ -13,12 +14,13 @@ describe('StopPropagationDirective', () => {
beforeEach(() => {
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.stopPropEvent.subscribe(childClickEventFn);
link = spectator.query('a');
childClickEventFn.mockClear();
parentClickEventFn.mockClear();
@ -29,8 +31,7 @@ describe('StopPropagationDirective', () => {
});
test('should not call click event of parent when child element is clicked', () => {
spectator.setHostInput({ parentClickEventFn, childClickEventFn });
spectator.click('a');
spectator.click(link);
spectator.detectChanges();
expect(childClickEventFn).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',
);
setLoading(value: boolean): void {
this.modalLoading.set(value);
}
ngOnInit() {
const attributes = this.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 color = input('#77b6ff');
protected readonly isLoading = signal(false);
protected readonly progressLevel = signal(0);
readonly isLoading = signal(false);
readonly progressLevel = signal(0);
interval = 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 delay = input(0, { alias: 'abpLoadingDelay' });
private targetElement: HTMLElement | undefined;
targetElement: HTMLElement | undefined;
componentRef: ComponentRef<LoadingComponent> | 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', () => {
let spectator: SpectatorRouting<RouterOutletComponent>;
let routes: RoutesService;
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
const createRouting = createRoutingFactory({
component: RouterOutletComponent,
@ -51,6 +52,7 @@ describe('BreadcrumbComponent', () => {
},
registerLocaleFn: () => Promise.resolve(),
skipGetAppConfiguration: true,
skipInitAuthService: true,
}),
),
{
@ -102,10 +104,15 @@ describe('BreadcrumbComponent', () => {
beforeAll(() => setupComponentResources('../components/breadcrumb', import.meta.url));
beforeEach(() => {
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
spectator = createRouting();
routes = spectator.inject(RoutesService);
});
afterEach(() => {
consoleErrorSpy.mockRestore();
});
it('should create component', async () => {
routes.add(mockRoutes);
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 { ButtonComponent } from '../components';
import { setInputSignal } from './utils';
describe('ButtonComponent', () => {
let spectator: SpectatorHost<ButtonComponent>;
@ -7,13 +9,13 @@ describe('ButtonComponent', () => {
const createHost = createHostFactory(ButtonComponent);
beforeEach(
() =>
(spectator = createHost(
'<abp-button iconClass="fa fa-check" [attributes]="attributes">Button</abp-button>',
{
hostProps: { attributes: { autofocus: '', name: 'abp-button' } },
},
)),
() => {
spectator = createHost('<abp-button>Button</abp-button>', {
detectChanges: false,
});
setInputSignal(spectator.component.iconClass, 'fa fa-check');
spectator.detectChanges();
},
);
it('should display the button', () => {
@ -48,23 +50,17 @@ describe('ButtonComponent', () => {
});
it('should display the spinner icon when loading input is true', () => {
spectator = createHost(
'<abp-button iconClass="fa fa-check" [loading]="loading">Button</abp-button>',
{ hostProps: { loading: true } },
);
setInputSignal(spectator.component.loading, true);
spectator.detectComponentChanges();
expect(spectator.query('i')).toHaveClass('fa-spinner');
});
it('should clear the spinner icon when loading input becomes false', () => {
spectator = createHost(
'<abp-button iconClass="fa fa-check" [loading]="loading">Button</abp-button>',
{ hostProps: { loading: true } },
);
setInputSignal(spectator.component.loading, true);
spectator.detectComponentChanges();
expect(spectator.query('i')).toHaveClass('fa-spinner');
spectator.setHostInput({ loading: false });
setInputSignal(spectator.component.loading, false);
spectator.detectComponentChanges();
expect(spectator.query('i')).toHaveClass('fa-check');
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 { CardBodyComponent } from '../components';
import { setInputSignal } from './utils';
describe('AbpCardBodyComponent', () => {
let spectator: SpectatorHost<CardBodyComponent>;
@ -8,16 +9,17 @@ describe('AbpCardBodyComponent', () => {
beforeEach(
() =>
(spectator = createHost(
` <abp-card-body
[cardBodyStyle]="{'background-color':'red'}"
>
<p>Body</p>
</abp-card-body>`,
{
hostProps: { attributes: { autofocus: '', name: 'abp-card-body' } },
},
)),
{
spectator = createHost(
`<abp-card-body> <p>Body</p> </abp-card-body>`,
{
detectChanges: false,
hostProps: { attributes: { autofocus: '', name: 'abp-card-body' } },
},
);
setInputSignal(spectator.component.cardBodyStyle, 'background-color: red;');
spectator.detectChanges();
},
);
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 { CardFooterComponent } from '../components';
import { setInputSignal } from './utils';
describe('AbpCardFooterComponent', () => {
let spectator: SpectatorHost<CardFooterComponent>;
@ -8,16 +9,19 @@ describe('AbpCardFooterComponent', () => {
beforeEach(
() =>
(spectator = createHost(
`<abp-card-footer
[cardFooterStyle]="{'background-color':'red'}"
>
<p>Footer</p>
</abp-card-footer>`,
{
hostProps: { attributes: { autofocus: '', name: 'abp-card-footer' } },
},
)),
{
spectator = createHost(
`<abp-card-footer>
<p>Footer</p>
</abp-card-footer>`,
{
detectChanges: false,
hostProps: { attributes: { autofocus: '', name: 'abp-card-footer' } },
},
);
setInputSignal(spectator.component.cardFooterStyle, 'background-color: red;');
spectator.detectChanges();
},
);
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 { CardHeaderComponent } from '../components';
import { setInputSignal } from './utils';
describe('AbpCardHeaderComponent', () => {
let spectator: SpectatorHost<CardHeaderComponent>;
@ -7,16 +8,17 @@ describe('AbpCardHeaderComponent', () => {
beforeEach(
() =>
(spectator = createHost(
` <abp-card-header
[cardHeaderStyle]="{'background-color':'red'}"
>
Header
</abp-card-header>`,
{
hostProps: { attributes: { autofocus: '', name: 'abp-card-header' } },
},
)),
{
spectator = createHost(
`<abp-card-header> Header </abp-card-header>`,
{
detectChanges: false,
hostProps: { attributes: { autofocus: '', name: 'abp-card-header' } },
},
);
setInputSignal(spectator.component.cardHeaderStyle, 'background-color: red;');
spectator.detectChanges();
},
);
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);
beforeEach(
() =>
(spectator = createHost(
`<div
abpCardHeader>
</div>`,
() => {
spectator = createHost(
`<div abpCardHeader></div>`,
{
hostProps: { attributes: { autofocus: '', name: 'abp-card-header' } },
},
)),
);
},
);
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 { FormCheckboxComponent } from '../components/checkbox/checkbox.component';
import { setInputSignal } from './utils';
describe('FormCheckboxComponent', () => {
let spectator: SpectatorHost<FormCheckboxComponent>;
@ -7,13 +8,18 @@ describe('FormCheckboxComponent', () => {
const createHost = createHostFactory(FormCheckboxComponent);
beforeEach(
() =>
(spectator = createHost(
'<abp-checkbox></abp-checkbox>',
() => {
spectator = createHost(
'<abp-checkbox />',
{
detectChanges: false,
hostProps: { attributes: { autofocus: '', name: 'abp-checkbox' } },
},
)),
);
setInputSignal(spectator.component.checkboxId, 'checkbox-id');
setInputSignal(spectator.component.checkboxReadonly, true);
spectator.detectChanges();
},
);
it('should display the input', () => {
@ -29,13 +35,13 @@ describe('FormCheckboxComponent', () => {
});
it('should be readonly when checkboxReadonly is true', () => {
spectator.component.checkboxReadonly = true;
setInputSignal(spectator.component.checkboxReadonly, true);
spectator.detectComponentChanges();
expect(spectator.query('[readonly]')).toBeTruthy();
});
it('should not contain readonly when checboxReadonly is false', () => {
spectator.component.checkboxReadonly = false;
setInputSignal(spectator.component.checkboxReadonly, false);
spectator.detectComponentChanges();
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 { EllipsisDirective } from '../directives/ellipsis.directive';
import { setInputSignal } from './utils';
describe('EllipsisDirective', () => {
let spectator: SpectatorDirective<EllipsisDirective>;
@ -10,15 +11,13 @@ describe('EllipsisDirective', () => {
});
beforeEach(() => {
spectator = createDirective(
'<div [abpEllipsis]="width" [abpEllipsisEnabled]="true" [title]="title">test content</div>',
{
hostProps: {
title: 'test title',
width: '100px',
},
},
);
spectator = createDirective('<div abpEllipsis>test content</div>', {
detectChanges: false,
});
setInputSignal(spectator.directive.width, '100px');
setInputSignal(spectator.directive.enabled, true);
setInputSignal(spectator.directive.title, 'title');
spectator.detectChanges();
directive = spectator.directive;
el = spectator.query('div');
});
@ -28,15 +27,15 @@ describe('EllipsisDirective', () => {
});
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', () => {
expect(directive.enabled).toBe(true);
expect(directive.enabled()).toBe(true);
});
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', () => {
@ -48,26 +47,25 @@ 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',
},
},
);
spectator = createDirective('<div abpEllipsis>test content</div>', {
detectChanges: false,
});
setInputSignal(spectator.directive.width, '100px');
setInputSignal(spectator.directive.enabled, true);
setInputSignal(spectator.directive.title, undefined);
spectator.detectChanges();
directive = spectator.directive;
el = spectator.query('div') as HTMLDivElement;
});
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(() => {
spectator = createDirective(
'<div [abpEllipsis]="width" [abpEllipsisEnabled]="true" [title]="title">test content</div>',
{
hostProps: {
title: 'test title',
width: undefined,
},
},
);
spectator = createDirective('<div abpEllipsis>test content</div>', {
detectChanges: false,
});
setInputSignal(spectator.directive.width, undefined);
setInputSignal(spectator.directive.enabled, true);
setInputSignal(spectator.directive.title, 'test title');
spectator.detectChanges();
directive = spectator.directive;
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 { FormInputComponent } from '../components/form-input/form-input.component';
import { setInputSignal } from './utils';
describe('FormInputComponent', () => {
@ -8,13 +9,14 @@ describe('FormInputComponent', () => {
const createHost = createHostFactory(FormInputComponent);
beforeEach(
() =>
(spectator = createHost(
'<abp-form-input></abp-form-input>',
{
hostProps: { attributes: { autofocus: '', name: 'abp-form-input' } },
},
)),
() => {
spectator = createHost(
'<abp-form-input />',
{
hostProps: { attributes: { autofocus: '', name: 'abp-form-input' } },
},
);
},
);
it('should display the input', () => {
@ -30,13 +32,13 @@ describe('FormInputComponent', () => {
});
it('should be readonly when inputReadonly is true', () => {
spectator.component.inputReadonly = true;
setInputSignal(spectator.component.inputReadonly, true);
spectator.detectComponentChanges();
expect(spectator.query('[readonly]')).toBeTruthy();
});
it('should not contain readonly when inputReadonly is false', () => {
spectator.component.inputReadonly = false;
setInputSignal(spectator.component.inputReadonly, false);
spectator.detectComponentChanges();
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 { combineLatest, firstValueFrom, Subject, timer } from 'rxjs';
import { LoaderBarComponent } from '../components/loader-bar/loader-bar.component';
import { setupComponentResources } from './utils';
import { setupComponentResources, setInputSignal } from './utils';
describe('LoaderBarComponent', () => {
let spectator: Spectator<LoaderBarComponent>;
@ -34,8 +35,8 @@ describe('LoaderBarComponent', () => {
});
it('should initial variable values are correct', () => {
expect(spectator.component.containerClass).toBe('abp-loader-bar');
expect(spectator.component.color).toBe('#77b6ff');
expect(spectator.component.containerClass()).toBe('abp-loader-bar');
expect(spectator.component.color()).toBe('#77b6ff');
});
it('should increase the progressLevel', async () => {
@ -45,8 +46,7 @@ describe('LoaderBarComponent', () => {
spectator.detectChanges();
await new Promise(resolve => setTimeout(resolve, 10));
expect(spectator.component.progressLevel > 0).toBeTruthy();
expect(spectator.component.progressLevel()).toBeGreaterThan(0);
});
@ -100,15 +100,15 @@ describe('LoaderBarComponent', () => {
spectator.detectChanges();
attempts = 0;
while (spectator.component.progressLevel !== 100 && attempts < 50) {
while (spectator.component.progressLevel() !== 100 && attempts < 50) {
await new Promise(resolve => setTimeout(resolve, 10));
spectator.detectChanges();
attempts++;
}
expect(spectator.component.progressLevel).toBe(100);
expect(spectator.component.progressLevel()).toBe(100);
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 () => {
@ -130,15 +130,15 @@ describe('LoaderBarComponent', () => {
spectator.detectChanges();
attempts = 0;
while (spectator.component.progressLevel !== 100 && attempts < 50) {
while (spectator.component.progressLevel() !== 100 && attempts < 50) {
await new Promise(resolve => setTimeout(resolve, 10));
spectator.detectChanges();
attempts++;
}
expect(spectator.component.progressLevel).toBe(100);
expect(spectator.component.progressLevel()).toBe(100);
await firstValueFrom(timer(spectator.component.stopDelay + 10));
expect(spectator.component.progressLevel).toBe(0);
expect(spectator.component.progressLevel()).toBe(0);
});
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 { LoadingDirective } from '../directives';
import { LoadingComponent } from '../components';
import { Component } from '@angular/core';
import { setInputSignal } from './utils';
@Component({
selector: 'abp-dummy',
@ -20,7 +21,7 @@ describe('LoadingDirective', () => {
describe('default', () => {
beforeEach(() => {
spectator = createDirective('<div [abpLoading]="loading">Testing Loading Directive</div>', {
spectator = createDirective('<div abpLoading>Testing Loading Directive</div>', {
hostProps: { loading: true },
});
});
@ -30,10 +31,10 @@ describe('LoadingDirective', () => {
});
it('should handle loading input', async () => {
spectator.directive.loading = false;
setInputSignal(spectator.directive.loading, false);
await new Promise(resolve => setTimeout(resolve, 10));
expect(spectator.directive).toBeTruthy();
expect(spectator.directive.loading).toBe(false);
expect(spectator.directive.loading()).toBe(false);
});
});
@ -42,11 +43,15 @@ describe('LoadingDirective', () => {
beforeEach(() => {
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', () => {
@ -55,17 +60,17 @@ describe('LoadingDirective', () => {
});
it('should handle delay input', async () => {
spectator.directive.delay = 100;
setInputSignal(spectator.directive.delay, 100);
await new Promise(resolve => setTimeout(resolve, 10));
expect(spectator.directive).toBeTruthy();
});
it('should handle loading state changes', async() => {
spectator.directive.loading = false;
setInputSignal(spectator.directive.loading, false);
await new Promise(resolve => setTimeout(resolve, 10));
expect(spectator.directive).toBeTruthy();
spectator.directive.loading = true;
setInputSignal(spectator.directive.loading, true);
await new Promise(resolve => setTimeout(resolve, 10));
expect(spectator.directive).toBeTruthy();
});
@ -73,7 +78,7 @@ describe('LoadingDirective', () => {
describe('with a component selector', () => {
beforeEach(() => {
spectator = createDirective('<abp-dummy [abpLoading]="loading"></abp-dummy>', {
spectator = createDirective('<abp-dummy abpLoading></abp-dummy>', {
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 { 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 { Confirmation } from '@abp/ng.theme.shared';
import { firstValueFrom, Subject, timer } from 'rxjs';
import { ModalComponent } from '../components/modal/modal.component';
import { setInputSignal } from './utils';
import { setupComponentResources } from './utils';
@Component({
@ -22,8 +23,8 @@ import { setupComponentResources } from './utils';
imports: [ModalComponent]
})
class TestHostComponent {
@Input() visible = false;
@Input() busy = false;
visible = input(false);
busy = input(false);
visibleChange = new EventEmitter<boolean>();
}
@ -33,10 +34,13 @@ const disappearFn = vi.fn();
describe('ModalComponent', () => {
let spectator: Spectator<TestHostComponent>;
let createComponent: ReturnType<typeof createComponentFactory<TestHostComponent>>;
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
beforeAll(() => setupComponentResources('../components/modal', import.meta.url));
beforeEach(() => {
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
// Create component factory in beforeEach to ensure beforeAll has run
if (!createComponent) {
createComponent = createComponentFactory({
@ -60,20 +64,24 @@ describe('ModalComponent', () => {
disappearFn.mockClear();
});
afterEach(() => {
consoleErrorSpy.mockRestore();
});
it('should create component', () => {
expect(spectator.component).toBeTruthy();
});
it('should handle visible input', () => {
spectator.setInput('visible', true);
setInputSignal(spectator.component.visible, true);
spectator.detectChanges();
expect(spectator.component.visible).toBe(true);
expect(spectator.component.visible()).toBe(true);
});
it('should handle busy input', () => {
spectator.setInput('busy', true);
setInputSignal(spectator.component.busy, true);
spectator.detectChanges();
expect(spectator.component.busy).toBe(true);
expect(spectator.component.busy()).toBe(true);
});
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', () => {
let spectator: SpectatorService<ToasterService>;
let service: ToasterService;
const mockComponentRef = {
changeDetectorRef: { detectChanges: vi.fn() },
instance: {} as ToastContainerComponent,
} as unknown as ComponentRef<ToastContainerComponent>;
instance: {
setToasts: vi.fn(), remove: vi.fn(), toasts: [], top: 0, right: 0, bottom: 0, left: 0,
},
};
const contentProjectionService = {
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';

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