diff --git a/npm/ng-packs/packages/core/src/lib/tests/autofocus.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/autofocus.directive.spec.ts index 5f46e900bd..6a8d7bf4bd 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/autofocus.directive.spec.ts +++ b/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; @@ -11,26 +11,32 @@ describe('AutofocusDirective', () => { }); beforeEach(() => { - spectator = createDirective('', { - hostProps: {}, - }); + vi.useFakeTimers(); + + spectator = createDirective(''); 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(); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/capsLock.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/capsLock.directive.spec.ts index e94a099d19..dad3723500 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/capsLock.directive.spec.ts +++ b/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: ` `, + template: ` `, imports: [TrackCapsLockDirective], }) class TestComponent { @@ -14,6 +14,20 @@ class TestComponent { describe('TrackCapsLockDirective', () => { let fixture: ComponentFixture; let des: DebugElement[]; + let directive: TrackCapsLockDirective; + let emitSpy: ReturnType; + + 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); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/debounce.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/debounce.directive.spec.ts index d952965486..ee36dac0ae 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/debounce.directive.spec.ts +++ b/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; @@ -13,7 +13,9 @@ describe('InputEventDebounceDirective', () => { }); beforeEach(() => { - spectator = createDirective('', { + vi.useFakeTimers(); + + spectator = createDirective('', { 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(); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/environment-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/environment-utils.spec.ts index 353f1ebe69..5dfb3252e5 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/environment-utils.spec.ts +++ b/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); diff --git a/npm/ng-packs/packages/core/src/lib/tests/factory-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/factory-utils.spec.ts index 06e8a8d044..d72eabe14d 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/factory-utils.spec.ts +++ b/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'); }); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/for.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/for.directive.spec.ts index 90c2dc68b0..156d6d20c9 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/for.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/for.directive.spec.ts @@ -1,6 +1,12 @@ +import { TemplateRef, ɵSIGNAL as SIGNAL } from '@angular/core'; import { SpectatorDirective, createDirectiveFactory } from '@ngneat/spectator/vitest'; import { ForDirective } from '../directives/for.directive'; +const setInputSignal = (inputSignal: () => T, value: T) => { + const node = inputSignal[SIGNAL]; + node.applyValueToInputSignal(node, value); +}; + describe('ForDirective', () => { let spectator: SpectatorDirective; let directive: ForDirective; @@ -9,12 +15,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>) => { + 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('
  • {{ item }}
', { - hostProps: { items }, - }); + spectator = createDirective('
  • {{ item }}
'); directive = spectator.directive; + setDirectiveInputs({ items }); }); test('should be created', () => { @@ -29,166 +67,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( - '
  • {{ item }}
', - { - hostProps: { items, trackByFn }, - }, - ); + spectator = createDirective('
  • {{ item }}
'); 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( - `
    -
  • - {{ item }} -
  • -
`, - ); + spectator = createDirective('
  • {{ item }}
'); 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( - `
    -
  • - {{ item.value }} -
  • -
`, - { - hostProps: { orderDir: 'ASC' }, - }, + '
  • {{ item.value }}
', ); 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( - `
    -
  • - {{ item.value }} -
  • -
`, - { - hostProps: { filterVal: '' }, - }, + '
  • {{ item.value }}
', ); 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( - `
    -
  • - {{ item.value }} -
  • + spectator = createDirective(` +
      + +
    • {{ item.value }}
    • +
      No records found -
    `, - { - hostProps: { items: [] }, - }, - ); +
+ `); directive = spectator.directive; + const [, emptyRef] = getTemplateRefs(); + setDirectiveInputs({ items: [], emptyRef }); }); test('should display the empty ref', () => { @@ -200,12 +188,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); diff --git a/npm/ng-packs/packages/core/src/lib/tests/form-submit.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/form-submit.directive.spec.ts index 8eb3f59bb6..6e7d40f625 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/form-submit.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/form-submit.directive.spec.ts @@ -2,6 +2,8 @@ import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vi 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'; + describe('FormSubmitDirective', () => { let spectator: SpectatorDirective; @@ -16,6 +18,8 @@ describe('FormSubmitDirective', () => { }); beforeEach(() => { + vi.useFakeTimers(); + spectator = createDirective( '
form content
', { @@ -28,12 +32,19 @@ describe('FormSubmitDirective', () => { directive = spectator.directive; }); + 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); + expect(directive.debounce()).toBe(200); }); test('should dispatch submit event on keyup event triggered after given debounce time', async () => { @@ -44,8 +55,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(); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/local-storage.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/local-storage.service.spec.ts index 8cfd8853a8..3daa086b09 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/local-storage.service.spec.ts +++ b/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); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts index 9d5a9c586d..fd79d365eb 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/permission.directive.spec.ts @@ -1,10 +1,15 @@ -import { ChangeDetectorRef } from '@angular/core'; +import { ChangeDetectorRef, ɵSIGNAL as SIGNAL } from '@angular/core'; import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { Subject } from 'rxjs'; import { PermissionDirective } from '../directives/permission.directive'; import { PermissionService } from '../services/permission.service'; import { QUEUE_MANAGER } from '../tokens/queue.token'; +const setInputSignal = (inputSignal: () => T, value: T) => { + const node = inputSignal[SIGNAL]; + node.applyValueToInputSignal(node, value); +}; + describe('PermissionDirective', () => { let spectator: SpectatorDirective; let directive: PermissionDirective; @@ -44,19 +49,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); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts index 6486a7bf41..a171178170 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/permission.guard.spec.ts +++ b/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; let httpErrorReporter: SpyObject; + let routesService: Pick; + let routeCultureUrl: Pick; + let configStateService: Pick; - 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(); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts index 3cd625cd3c..819ab01936 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts @@ -1,4 +1,4 @@ -import { Component, Input, inject, output } from '@angular/core'; +import { Component, inject, input, output, ɵSIGNAL as SIGNAL } from '@angular/core'; import { Router } from '@angular/router'; import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/vitest'; import { BehaviorSubject } from 'rxjs'; @@ -6,34 +6,37 @@ import { ReplaceableTemplateDirective } from '../directives/replaceable-template import { ReplaceableComponents } from '../models/replaceable-components'; import { ReplaceableComponentsService } from '../services/replaceable-components.service'; +const setInputSignal = (inputSignal: () => T, value: T) => { + const node = inputSignal[SIGNAL]; + node.applyValueToInputSignal(node, value); +}; + @Component({ selector: 'abp-default-component', template: '

default

', - exportAs: 'abpDefaultComponent' + exportAs: 'abpDefaultComponent', }) class DefaultComponent { - @Input() - oneWay; + onOneWay = input(); - @Input() - twoWay: boolean; + twoWay = input(); readonly twoWayChange = output(); readonly someOutput = output(); setTwoWay(value) { - this.twoWay = value; + setInputSignal(this.twoWay, value); this.twoWayChange.emit(value); } } @Component({ selector: 'abp-external-component', - template: '

external

' + template: '

external

', }) class ExternalComponent { - data = inject>('REPLACEABLE_DATA' as any, { optional: true })!; + data = inject>('REPLACEABLE_DATA' as any)!; } describe('ReplaceableTemplateDirective', () => { @@ -54,11 +57,12 @@ describe('ReplaceableTemplateDirective', () => { beforeEach(() => { spectator = createDirective( ` -
+ -
+ `, { + detectChanges: false, hostProps: { oneWay: { label: 'Test' }, twoWay: false, @@ -67,6 +71,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', () => { @@ -83,6 +96,7 @@ describe('ReplaceableTemplateDirective', () => { `, { + detectChanges: false, hostProps: { oneWay: { label: 'Test' }, twoWay: false, @@ -91,6 +105,14 @@ describe('ReplaceableTemplateDirective', () => { }, }, ); + 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(); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/show-password-directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/show-password-directive.spec.ts index e1266094cf..641dd5a911 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/show-password-directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/show-password-directive.spec.ts @@ -1,37 +1,51 @@ -import { Component, DebugElement, ChangeDetectorRef } from '@angular/core'; +import { Component, DebugElement, ɵSIGNAL as SIGNAL } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { ShowPasswordDirective } from '../directives'; @Component({ - template: ` - + template: ` + - `, + `, imports: [ShowPasswordDirective], }) -class TestComponent { - showPassword = false; -} +class TestComponent {} + +const setInputSignal = (inputSignal: () => T, value: T) => { + const node = inputSignal[SIGNAL]; + node.applyValueToInputSignal(node, value); +}; describe('ShowPasswordDirective', () => { let fixture: ComponentFixture; 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 +66,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'); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/stop-propagation.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/stop-propagation.directive.spec.ts index 8e0595c3c3..88356b51b2 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/stop-propagation.directive.spec.ts +++ b/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; @@ -13,12 +14,13 @@ describe('StopPropagationDirective', () => { beforeEach(() => { spectator = createDirective( - '', + '', { - 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();