From dfded29257ef573a3a3a2559191d0dcf7e4e65ff Mon Sep 17 00:00:00 2001 From: Fahri Gedik Date: Wed, 6 Aug 2025 13:34:27 +0300 Subject: [PATCH] Revert "Expand and improve core service and utility test coverage" This reverts commit dab1d319c0a5d0f950cdcc78af5d999db0f5ffa2. --- .../src/lib/services/localization.service.ts | 6 +- .../tests/content-projection.service.spec.ts | 61 +-- .../core/src/lib/tests/date-utils.spec.ts | 41 +- .../tests/dynamic-layout.component.spec.ts | 482 ++++++++---------- .../src/lib/tests/generator-utils.spec.ts | 98 +--- .../core/src/lib/tests/initial-utils.spec.ts | 71 +-- .../src/lib/tests/lazy-load.service.spec.ts | 76 +-- .../lib/tests/localization.service.spec.ts | 460 +++++++++-------- .../src/lib/tests/ng-model.component.spec.ts | 97 +--- .../lib/tests/permission.directive.spec.ts | 4 +- .../src/lib/tests/permission.guard.spec.ts | 98 +--- .../src/lib/tests/projection.strategy.spec.ts | 22 +- ...laceable-route-container.component.spec.ts | 5 +- .../replaceable-template.directive.spec.ts | 350 +++++++------ .../core/src/lib/tests/route-utils.spec.ts | 5 +- .../lib/tests/router-events.service.spec.ts | 32 +- .../lib/tests/router-outlet.component.spec.ts | 4 +- .../core/src/lib/tests/routes.handler.spec.ts | 52 +- .../core/src/lib/tests/routes.service.spec.ts | 284 +---------- .../core/src/lib/tests/string-utils.spec.ts | 4 +- 20 files changed, 735 insertions(+), 1517 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/services/localization.service.ts b/npm/ng-packs/packages/core/src/lib/services/localization.service.ts index b433a3d424..dd6def0c24 100644 --- a/npm/ng-packs/packages/core/src/lib/services/localization.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/localization.service.ts @@ -273,11 +273,7 @@ function recursivelyMergeBaseResources( ): ApplicationLocalizationResourceDto { const item = source[baseResourceName]; - if (!item) { - return { texts: {}, baseResources: [] }; - } - - if (!item.baseResources || item.baseResources.length === 0) { + if (item.baseResources.length === 0) { return item; } diff --git a/npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts index 6c0843e82a..34f9451b0a 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/content-projection.service.spec.ts @@ -4,19 +4,12 @@ import { ContentProjectionService } from '../services'; import { PROJECTION_STRATEGY } from '../strategies'; describe('ContentProjectionService', () => { - @Component({ template: '
bar
', standalone: true }) + @Component({ template: '
bar
' }) class TestComponent {} - @Component({ - template: '
{{ contextData }}
', - standalone: true - }) - class ContextComponent { - contextData: string = ''; - } - + // createServiceFactory does not accept entryComponents directly @NgModule({ - imports: [TestComponent, ContextComponent], + declarations: [TestComponent], }) class TestModule {} @@ -29,16 +22,10 @@ describe('ContentProjectionService', () => { beforeEach(() => (spectator = createService())); - afterEach(() => { - if (componentRef) { - componentRef.destroy(); - } - const elements = document.querySelectorAll('ng-component'); - elements.forEach(el => el.remove()); - }); + afterEach(() => componentRef.destroy()); describe('#projectContent', () => { - it('should call injectContent of given projectionStrategy and return what it returns for AppendComponentToBody', () => { + it('should call injectContent of given projectionStrategy and return what it returns', () => { const strategy = PROJECTION_STRATEGY.AppendComponentToBody(TestComponent); componentRef = spectator.service.projectContent(strategy); const foo = document.querySelector('body > ng-component > div.foo'); @@ -46,43 +33,5 @@ describe('ContentProjectionService', () => { expect(componentRef).toBeInstanceOf(ComponentRef); expect(foo.textContent).toBe('bar'); }); - - it('should handle component with context for AppendComponentToBody', () => { - const strategy = PROJECTION_STRATEGY.AppendComponentToBody( - ContextComponent, - { contextData: 'context test' } - ); - componentRef = spectator.service.projectContent(strategy); - - const contextDiv = document.querySelector('body > ng-component > div.context'); - expect(componentRef).toBeInstanceOf(ComponentRef); - expect(contextDiv.textContent).toBe('context test'); - }); - - it('should return ComponentRef when projecting component', () => { - const strategy = PROJECTION_STRATEGY.AppendComponentToBody(TestComponent); - const result = spectator.service.projectContent(strategy); - - expect(result).toBeInstanceOf(ComponentRef); - expect(result.componentType).toBe(TestComponent); - }); - - it('should work with different projection strategies', () => { - const appendStrategy = PROJECTION_STRATEGY.AppendComponentToBody(TestComponent); - const appendResult = spectator.service.projectContent(appendStrategy); - - expect(appendResult).toBeInstanceOf(ComponentRef); - - appendResult.destroy(); - - const contextStrategy = PROJECTION_STRATEGY.AppendComponentToBody( - ContextComponent, - { contextData: 'test context' } - ); - const contextResult = spectator.service.projectContent(contextStrategy); - - expect(contextResult).toBeInstanceOf(ComponentRef); - expect(contextResult.componentType).toBe(ContextComponent); - }); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/date-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/date-utils.spec.ts index b0f16b09c8..33f02dfb4f 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/date-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/date-utils.spec.ts @@ -1,4 +1,3 @@ -import { TestBed } from '@angular/core/testing'; import { ConfigStateService } from '../services'; import { getShortDateFormat, getShortDateShortTimeFormat, getShortTimeFormat } from '../utils'; @@ -16,17 +15,7 @@ describe('Date Utils', () => { let config: ConfigStateService; beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - { - provide: ConfigStateService, - useValue: { - getDeep: jest.fn(), - }, - }, - ], - }); - config = TestBed.inject(ConfigStateService); + config = new ConfigStateService(null, null, null); }); describe('#getShortDateFormat', () => { @@ -47,20 +36,6 @@ describe('Date Utils', () => { expect(getShortTimeFormat(config)).toBe('h:mm a'); expect(getDeepSpy).toHaveBeenCalledWith('localization.currentCulture.dateTimeFormat'); }); - - test('should handle null shortTimePattern', () => { - const getDeepSpy = jest.spyOn(config, 'getDeep'); - getDeepSpy.mockReturnValueOnce({ ...dateTimeFormat, shortTimePattern: null }); - - expect(getShortTimeFormat(config)).toBeUndefined(); - }); - - test('should handle undefined shortTimePattern', () => { - const getDeepSpy = jest.spyOn(config, 'getDeep'); - getDeepSpy.mockReturnValueOnce({ ...dateTimeFormat, shortTimePattern: undefined }); - - expect(getShortTimeFormat(config)).toBeUndefined(); - }); }); describe('#getShortDateShortTimeFormat', () => { @@ -71,19 +46,5 @@ describe('Date Utils', () => { expect(getShortDateShortTimeFormat(config)).toBe('M/d/yyyy h:mm a'); expect(getDeepSpy).toHaveBeenCalledWith('localization.currentCulture.dateTimeFormat'); }); - - test('should handle null shortTimePattern', () => { - const getDeepSpy = jest.spyOn(config, 'getDeep'); - getDeepSpy.mockReturnValueOnce({ ...dateTimeFormat, shortTimePattern: null }); - - expect(getShortDateShortTimeFormat(config)).toBe('M/d/yyyy undefined'); - }); - - test('should handle undefined shortTimePattern', () => { - const getDeepSpy = jest.spyOn(config, 'getDeep'); - getDeepSpy.mockReturnValueOnce({ ...dateTimeFormat, shortTimePattern: undefined }); - - expect(getShortDateShortTimeFormat(config)).toBe('M/d/yyyy undefined'); - }); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts index 5d267d61a2..aac6c41fcd 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts @@ -1,281 +1,201 @@ -import { HttpClient } from '@angular/common/http'; -import { Component, inject as inject_1 } from '@angular/core'; -import { ActivatedRoute, RouterModule, RouterOutlet } from '@angular/router'; -import { of, BehaviorSubject } from 'rxjs'; -import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest'; -import { DynamicLayoutComponent, RouterOutletComponent } from '../components'; -import { eLayoutType } from '../enums/common'; -import { ABP } from '../models'; -import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service'; -import { ReplaceableComponentsService, RoutesService, RouterEvents, EnvironmentService, LocalizationService } from '../services'; -import { DYNAMIC_LAYOUTS_TOKEN } from '../tokens/dynamic-layout.token'; - -const mockRoutesService = () => ({ - add: jest.fn(), - find: jest.fn((predicate) => { - - if (predicate && typeof predicate === 'function') { - if (predicate({ path: '/parentWithLayout/childWithoutLayout' })) { - return { layout: eLayoutType.application }; - } - if (predicate({ path: '/parentWithLayout/childWithLayout' })) { - return { layout: eLayoutType.account }; - } - if (predicate({ path: '/withData' })) { - return { layout: eLayoutType.empty }; - } - if (predicate({ path: '/withoutLayout' })) { - return { layout: null }; - } - } - return null; - }), - search: jest.fn((query) => { - - if (query && query.invisible) { - return { layout: eLayoutType.account }; - } - return null; - }), - flat$: of([]), - tree$: of([]), - visible$: of([]), -}); - -@Component({ - selector: 'abp-layout-application', - template: '', - standalone: true, - imports: [RouterOutlet], -}) -class DummyApplicationLayoutComponent {} - -@Component({ - selector: 'abp-layout-account', - template: '', - standalone: true, - imports: [RouterOutlet], -}) -class DummyAccountLayoutComponent {} - -@Component({ - selector: 'abp-layout-empty', - template: '', - standalone: true, - imports: [RouterOutlet], -}) -class DummyEmptyLayoutComponent {} - -const LAYOUTS = [ - DummyApplicationLayoutComponent, - DummyAccountLayoutComponent, - DummyEmptyLayoutComponent, -]; - -@Component({ - selector: 'abp-dummy', - template: '{{route.snapshot.data?.name}} works!', - standalone: true, -}) -class DummyComponent { - route = inject_1(ActivatedRoute); -} - -const routes: ABP.Route[] = [ - { - path: '', - name: 'Root', - }, - { - path: '/parentWithLayout', - name: 'ParentWithLayout', - parentName: 'Root', - layout: eLayoutType.application, - }, - { - path: '/parentWithLayout/childWithoutLayout', - name: 'ChildWithoutLayout', - parentName: 'ParentWithLayout', - }, - { - path: '/parentWithLayout/childWithLayout', - name: 'ChildWithLayout', - parentName: 'ParentWithLayout', - layout: eLayoutType.account, - }, - { - path: '/withData', - name: 'WithData', - layout: eLayoutType.application, - }, -]; - -describe('DynamicLayoutComponent', () => { - const createComponent = createRoutingFactory({ - component: DynamicLayoutComponent, - stubsEnabled: false, - declarations: [], - mocks: [AbpApplicationConfigurationService, HttpClient], - providers: [ - { - provide: RoutesService, - useValue: mockRoutesService(), - }, - { - provide: RouterEvents, - useValue: { - getNavigationEvents: jest.fn().mockReturnValue(of({})), - }, - }, - { - provide: EnvironmentService, - useValue: { - getEnvironment: jest.fn().mockReturnValue({ - oAuthConfig: { responseType: 'code' }, - }), - }, - }, - { - provide: LocalizationService, - useValue: { - languageChange$: new BehaviorSubject('en'), - }, - }, - { - provide: DYNAMIC_LAYOUTS_TOKEN, - useValue: new Map([ - [eLayoutType.application, 'Theme.ApplicationLayoutComponent'], - [eLayoutType.account, 'Theme.AccountLayoutComponent'], - [eLayoutType.empty, 'Theme.EmptyLayoutComponent'], - ]), - }, - { - provide: ReplaceableComponentsService, - useValue: { - add: jest.fn(), - get: jest.fn((key) => { - if (key === 'Theme.ApplicationLayoutComponent') { - return { component: DummyApplicationLayoutComponent }; - } - if (key === 'Theme.AccountLayoutComponent') { - return { component: DummyAccountLayoutComponent }; - } - if (key === 'Theme.EmptyLayoutComponent') { - return { component: DummyEmptyLayoutComponent }; - } - return null; - }), - }, - }, - ], - imports: [RouterModule, DummyComponent, DynamicLayoutComponent, ...LAYOUTS], - routes: [ - { path: '', component: RouterOutletComponent }, - { - path: 'parentWithLayout', - component: DynamicLayoutComponent, - children: [ - { - path: 'childWithoutLayout', - component: DummyComponent, - data: { name: 'childWithoutLayout' }, - }, - { - path: 'childWithLayout', - component: DummyComponent, - data: { name: 'childWithLayout' }, - }, - ], - }, - { - path: 'withData', - component: DynamicLayoutComponent, - children: [ - { - path: '', - component: DummyComponent, - data: { name: 'withData' }, - }, - ], - data: { layout: eLayoutType.empty }, - }, - { - path: 'withoutLayout', - component: DynamicLayoutComponent, - children: [ - { - path: '', - component: DummyComponent, - data: { name: 'withoutLayout' }, - }, - ], - data: { layout: null }, - }, - ], - }); - - let spectator: SpectatorRouting; - let replaceableComponents: ReplaceableComponentsService; - - beforeEach(async () => { - spectator = createComponent(); - replaceableComponents = spectator.inject(ReplaceableComponentsService); - const routesService = spectator.inject(RoutesService); - routesService.add(routes); - - replaceableComponents.add({ - key: 'Theme.ApplicationLayoutComponent', - component: DummyApplicationLayoutComponent, - }); - replaceableComponents.add({ - key: 'Theme.AccountLayoutComponent', - component: DummyAccountLayoutComponent, - }); - replaceableComponents.add({ - key: 'Theme.EmptyLayoutComponent', - component: DummyEmptyLayoutComponent, - }); - }); - - it('should handle application layout from parent abp route and display it', async () => { - spectator.router.navigateByUrl('/parentWithLayout/childWithoutLayout'); - await spectator.fixture.whenStable(); - await new Promise(resolve => setTimeout(resolve, 100)); - spectator.detectComponentChanges(); - expect(spectator.query('abp-dynamic-layout')).toBeTruthy(); - expect(spectator.query('abp-layout-application')).toBeTruthy(); - }); - - it('should handle account layout from own property and display it', async () => { - spectator.router.navigateByUrl('/parentWithLayout/childWithLayout'); - await spectator.fixture.whenStable(); - await new Promise(resolve => setTimeout(resolve, 100)); - spectator.detectComponentChanges(); - expect(spectator.query('abp-layout-account')).toBeTruthy(); - }); - - it('should handle empty layout from route data and display it', async () => { - spectator.router.navigateByUrl('/withData'); - await spectator.fixture.whenStable(); - await new Promise(resolve => setTimeout(resolve, 100)); - spectator.detectComponentChanges(); - expect(spectator.query('abp-layout-empty')).toBeTruthy(); - }); - - it('should display empty layout when layout is null', async () => { - spectator.router.navigateByUrl('/withoutLayout'); - await spectator.fixture.whenStable(); - await new Promise(resolve => setTimeout(resolve, 100)); - spectator.detectComponentChanges(); - expect(spectator.query('abp-layout-empty')).toBeTruthy(); - }); - - it('should handle layout not found scenario', async () => { - spectator.router.navigateByUrl('/withoutLayout'); - await spectator.fixture.whenStable(); - await new Promise(resolve => setTimeout(resolve, 100)); - spectator.detectComponentChanges(); - - expect(spectator.query('abp-dynamic-layout')).toBeTruthy(); - }); -}); +import { HttpClient } from '@angular/common/http'; +import { Component, NgModule, inject as inject_1 } from '@angular/core'; +import { ActivatedRoute, RouterModule } from '@angular/router'; +import { createRoutingFactory, SpectatorRouting } from '@ngneat/spectator/jest'; +import { DynamicLayoutComponent, RouterOutletComponent } from '../components'; +import { eLayoutType } from '../enums/common'; +import { ABP } from '../models'; +import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service'; +import { ReplaceableComponentsService, RoutesService } from '../services'; +import { mockRoutesService } from './routes.service.spec'; + +@Component({ + selector: 'abp-layout-application', + template: '', +}) +class DummyApplicationLayoutComponent {} + +@Component({ + selector: 'abp-layout-account', + template: '', +}) +class DummyAccountLayoutComponent {} + +@Component({ + selector: 'abp-layout-empty', + template: '', +}) +class DummyEmptyLayoutComponent {} + +const LAYOUTS = [ + DummyApplicationLayoutComponent, + DummyAccountLayoutComponent, + DummyEmptyLayoutComponent, +]; + +@NgModule({ + imports: [RouterModule], + declarations: [...LAYOUTS], +}) +class DummyLayoutModule {} + +@Component({ + selector: 'abp-dummy', + template: '{{route.snapshot.data?.name}} works!', +}) +class DummyComponent { route = inject_1(ActivatedRoute); + +} + +const routes: ABP.Route[] = [ + { + path: '', + name: 'Root', + }, + { + path: '/parentWithLayout', + name: 'ParentWithLayout', + parentName: 'Root', + layout: eLayoutType.application, + }, + { + path: '/parentWithLayout/childWithoutLayout', + name: 'ChildWithoutLayout', + parentName: 'ParentWithLayout', + }, + { + path: '/parentWithLayout/childWithLayout', + name: 'ChildWithLayout', + parentName: 'ParentWithLayout', + layout: eLayoutType.account, + }, + { + path: '/withData', + name: 'WithData', + layout: eLayoutType.application, + }, +]; + +describe('DynamicLayoutComponent', () => { + const createComponent = createRoutingFactory({ + component: RouterOutletComponent, + stubsEnabled: false, + declarations: [DummyComponent, DynamicLayoutComponent], + mocks: [AbpApplicationConfigurationService, HttpClient], + providers: [ + { + provide: RoutesService, + useFactory: () => mockRoutesService(), + }, + ReplaceableComponentsService, + ], + imports: [RouterModule, DummyLayoutModule], + routes: [ + { path: '', component: RouterOutletComponent }, + { + path: 'parentWithLayout', + component: DynamicLayoutComponent, + children: [ + { + path: 'childWithoutLayout', + component: DummyComponent, + data: { name: 'childWithoutLayout' }, + }, + { + path: 'childWithLayout', + component: DummyComponent, + data: { name: 'childWithLayout' }, + }, + ], + }, + { + path: 'withData', + component: DynamicLayoutComponent, + children: [ + { + path: '', + component: DummyComponent, + data: { name: 'withData' }, + }, + ], + data: { layout: eLayoutType.empty }, + }, + { + path: 'withoutLayout', + component: DynamicLayoutComponent, + children: [ + { + path: '', + component: DummyComponent, + data: { name: 'withoutLayout' }, + }, + ], + data: { layout: null }, + }, + ], + }); + + let spectator: SpectatorRouting; + let replaceableComponents: ReplaceableComponentsService; + + beforeEach(async () => { + spectator = createComponent(); + replaceableComponents = spectator.inject(ReplaceableComponentsService); + const routesService = spectator.inject(RoutesService); + routesService.add(routes); + + replaceableComponents.add({ + key: 'Theme.ApplicationLayoutComponent', + component: DummyApplicationLayoutComponent, + }); + replaceableComponents.add({ + key: 'Theme.AccountLayoutComponent', + component: DummyAccountLayoutComponent, + }); + replaceableComponents.add({ + key: 'Theme.EmptyLayoutComponent', + component: DummyEmptyLayoutComponent, + }); + }); + + it('should handle application layout from parent abp route and display it', async () => { + spectator.router.navigateByUrl('/parentWithLayout/childWithoutLayout'); + await spectator.fixture.whenStable(); + spectator.detectComponentChanges(); + expect(spectator.query('abp-dynamic-layout')).toBeTruthy(); + expect(spectator.query('abp-layout-application')).toBeTruthy(); + }); + + it('should handle account layout from own property and display it', async () => { + spectator.router.navigateByUrl('/parentWithLayout/childWithLayout'); + await spectator.fixture.whenStable(); + spectator.detectComponentChanges(); + expect(spectator.query('abp-layout-account')).toBeTruthy(); + }); + + it('should handle empty layout from route data and display it', async () => { + spectator.router.navigateByUrl('/withData'); + await spectator.fixture.whenStable(); + spectator.detectComponentChanges(); + expect(spectator.query('abp-layout-empty')).toBeTruthy(); + }); + + it('should display empty layout when layout is null', async () => { + spectator.router.navigateByUrl('/withoutLayout'); + await spectator.fixture.whenStable(); + spectator.detectComponentChanges(); + expect(spectator.query('abp-layout-empty')).toBeTruthy(); + }); + + it('should not display any layout when layouts are empty', async () => { + const spy = jest.spyOn(replaceableComponents, 'get'); + spy.mockReturnValue(null); + spectator.detectChanges(); + + spectator.router.navigateByUrl('/withoutLayout'); + await spectator.fixture.whenStable(); + spectator.detectComponentChanges(); + + expect(spectator.query('abp-layout-empty')).toBeFalsy(); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/tests/generator-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/generator-utils.spec.ts index 6fa861b0eb..0722a2f2b2 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/generator-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/generator-utils.spec.ts @@ -1,95 +1,39 @@ -import { generateHash, generatePassword, uuid } from '../utils/generator-utils'; -import { ConfigStateService } from '../services'; +import { generateHash, generatePassword } from '../utils/generator-utils'; describe('GeneratorUtils', () => { - describe('#uuid', () => { - test('should generate a uuid', () => { - const result = uuid(); - expect(typeof result).toBe('string'); - expect(result).toHaveLength(36); - expect(result).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/); - }); - - test('should generate different uuids', () => { - const uuid1 = uuid(); - const uuid2 = uuid(); - expect(uuid1).not.toBe(uuid2); - }); - }); - describe('#generateHash', () => { - test('should generate a hash', () => { + test('should generate a hash', async () => { const hash = generateHash('some content \n with second line'); expect(hash).toBe(1112440527); }); - - test('should generate consistent hash for same input', () => { - const input = 'test string'; - const hash1 = generateHash(input); - const hash2 = generateHash(input); - expect(hash1).toBe(hash2); - }); - - test('should generate different hashes for different inputs', () => { - const hash1 = generateHash('test1'); - const hash2 = generateHash('test2'); - expect(hash1).not.toBe(hash2); - }); }); describe('#generatePassword', () => { - const lowers = 'abcdefghjkmnpqrstuvwxyz'; - const uppers = 'ABCDEFGHJKMNPQRSTUVWXYZ'; - const numbers = '23456789'; - const specials = '!*_#/+-.'; + const lowers = 'abcdefghijklmnopqrstuvwxyz'; + const uppers = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + const numbers = '0123456789'; + const specials = '!@#$%&*()_+{}<>?[]./'; test.each` - passedPasswordLength | actualPasswordLength - ${Infinity} | ${128} - ${129} | ${128} - ${10} | ${10} - ${7} | ${7} - ${6} | ${6} - ${5} | ${5} - ${4} | ${4} - ${2} | ${4} - ${0} | ${4} - ${undefined} | ${8} + name | charSet | passedPasswordLength | actualPasswordLength + ${'lower'} | ${lowers} | ${Infinity} | ${128} + ${'lower'} | ${lowers} | ${129} | ${128} + ${'lower'} | ${lowers} | ${10} | ${10} + ${'lower'} | ${lowers} | ${7} | ${7} + ${'upper'} | ${uppers} | ${6} | ${6} + ${'number'} | ${numbers} | ${5} | ${5} + ${'special'} | ${specials} | ${4} | ${4} + ${'special'} | ${specials} | ${2} | ${4} + ${'special'} | ${specials} | ${0} | ${4} + ${'special'} | ${specials} | ${undefined} | ${8} `( - 'should generate password with length $actualPasswordLength when passed $passedPasswordLength', - ({ passedPasswordLength, actualPasswordLength }) => { - const password = generatePassword(undefined, passedPasswordLength); + 'should have a $name in the password that length is $passwordLength', + ({ _, charSet, passedPasswordLength, actualPasswordLength }) => { + const password = generatePassword(passedPasswordLength); expect(password).toHaveLength(actualPasswordLength); - - - expect(hasChar(lowers, password)).toBe(true); - expect(hasChar(uppers, password)).toBe(true); - expect(hasChar(numbers, password)).toBe(true); - expect(hasChar(specials, password)).toBe(true); + expect(hasChar(charSet, password)).toBe(true); }, ); - - test('should generate different passwords', () => { - const password1 = generatePassword(undefined, 8); - const password2 = generatePassword(undefined, 8); - expect(password1).not.toBe(password2); - }); - - test('should generate password with injector', () => { - const mockConfigState = { - getSettings: jest.fn().mockReturnValue({ - 'Abp.Identity.Password.RequiredLength': '12' - }) - }; - - const mockInjector = { - get: jest.fn().mockReturnValue(mockConfigState) - }; - - const password = generatePassword(mockInjector as any); - expect(password).toHaveLength(12); - expect(mockInjector.get).toHaveBeenCalledWith(ConfigStateService); - }); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts index 1f063d6eb9..90100d4613 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/initial-utils.spec.ts @@ -1,6 +1,6 @@ -import { Component } from '@angular/core'; +import { Component, Injector } from '@angular/core'; import { createComponentFactory, Spectator } from '@ngneat/spectator/jest'; -import { of, throwError } from 'rxjs'; +import { of } from 'rxjs'; import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service'; import { ApplicationConfigurationDto } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/models'; import { SessionStateService } from '../services/session-state.service'; @@ -13,8 +13,6 @@ import * as environmentUtils from '../utils/environment-utils'; import * as multiTenancyUtils from '../utils/multi-tenancy-utils'; import { RestService } from '../services/rest.service'; import { CHECK_AUTHENTICATION_STATE_FN_KEY } from '../tokens/check-authentication-state'; -import { APP_INIT_ERROR_HANDLERS } from '../tokens/app-config.token'; -import { TestBed } from '@angular/core/testing'; const environment = { oAuthConfig: { issuer: 'test' } }; @@ -42,18 +40,12 @@ describe('InitialUtils', () => { useValue: { environment, registerLocaleFn: () => Promise.resolve(), - skipInitAuthService: false, - skipGetAppConfiguration: false, }, }, { provide: CHECK_AUTHENTICATION_STATE_FN_KEY, useValue: () => {}, }, - { - provide: APP_INIT_ERROR_HANDLERS, - useValue: [], - }, ], }); @@ -64,6 +56,8 @@ describe('InitialUtils', () => { const environmentService = spectator.inject(EnvironmentService); const configStateService = spectator.inject(ConfigStateService); const sessionStateService = spectator.inject(SessionStateService); + //const checkAuthenticationState = spectator.inject(CHECK_AUTHENTICATION_STATE_FN_KEY); + const authService = spectator.inject(AuthService); const parseTenantFromUrlSpy = jest.spyOn(multiTenancyUtils, 'parseTenantFromUrl'); @@ -83,60 +77,29 @@ describe('InitialUtils', () => { const configStateGetOneSpy = jest.spyOn(configStateService, 'getOne'); configStateGetOneSpy.mockReturnValue(appConfigRes.currentTenant); - await TestBed.runInInjectionContext(() => getInitialData()); + const mockInjector = { + get: spectator.inject, + }; - expect(typeof getInitialData).toBe('function'); + await getInitialData(mockInjector)(); + + expect(typeof getInitialData(mockInjector)).toBe('function'); expect(configRefreshAppStateSpy).toHaveBeenCalled(); expect(environmentSetStateSpy).toHaveBeenCalledWith(environment); expect(sessionSetTenantSpy).toHaveBeenCalledWith(appConfigRes.currentTenant); expect(authServiceInitSpy).toHaveBeenCalled(); }); - - test('should handle errors when refreshAppState fails', async () => { - const configStateService = spectator.inject(ConfigStateService); - const errorHandlers = spectator.inject(APP_INIT_ERROR_HANDLERS); - - const mockError = new Error('Configuration failed'); - const configRefreshAppStateSpy = jest.spyOn(configStateService, 'refreshAppState'); - configRefreshAppStateSpy.mockReturnValue(throwError(() => mockError)); - - const errorHandlerSpy = jest.fn(); - errorHandlers.push(errorHandlerSpy); - - await expect(TestBed.runInInjectionContext(() => getInitialData())).rejects.toThrow('Configuration failed'); - expect(errorHandlerSpy).toHaveBeenCalledWith(mockError); - }); - - test('should skip auth service initialization when skipInitAuthService is true', async () => { - const authService = spectator.inject(AuthService); - const authServiceInitSpy = jest.spyOn(authService, 'init'); - - const originalOptions = spectator.inject(CORE_OPTIONS); - const modifiedOptions = { ...originalOptions, skipInitAuthService: true }; - - expect(authServiceInitSpy).not.toHaveBeenCalled(); - }); }); describe('#localeInitializer', () => { test('should resolve registerLocale', async () => { - expect(typeof localeInitializer).toBe('function'); - - const sessionStateService = spectator.inject(SessionStateService); - const getLanguageSpy = jest.spyOn(sessionStateService, 'getLanguage'); - getLanguageSpy.mockReturnValue('en'); - - const result = await TestBed.runInInjectionContext(() => localeInitializer()); - expect(result).toBe('resolved'); - }); - - test('should use default language when session language is not set', async () => { - const sessionStateService = spectator.inject(SessionStateService); - const getLanguageSpy = jest.spyOn(sessionStateService, 'getLanguage'); - getLanguageSpy.mockReturnValue(null); - - const result = await TestBed.runInInjectionContext(() => localeInitializer()); - expect(result).toBe('resolved'); + const injector = spectator.inject(Injector); + const injectorSpy = jest.spyOn(injector, 'get'); + const sessionState = spectator.inject(SessionStateService); + injectorSpy.mockReturnValueOnce(sessionState); + injectorSpy.mockReturnValueOnce({ registerLocaleFn: () => Promise.resolve() }); + expect(typeof localeInitializer(injector)).toBe('function'); + expect(await localeInitializer(injector)()).toBe('resolved'); }); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts index 5677545f54..c209170c5a 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/lazy-load.service.spec.ts @@ -1,4 +1,3 @@ -import { TestBed } from '@angular/core/testing'; import { of, throwError } from 'rxjs'; import { switchMap } from 'rxjs/operators'; import { LazyLoadService } from '../services/lazy-load.service'; @@ -6,27 +5,9 @@ import { ScriptLoadingStrategy } from '../strategies/loading.strategy'; import { ResourceWaitService } from '../services/resource-wait.service'; describe('LazyLoadService', () => { - let service: LazyLoadService; - let resourceWaitService: ResourceWaitService; - - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - LazyLoadService, - { - provide: ResourceWaitService, - useValue: { - addResource: jest.fn(), - deleteResource: jest.fn(), - }, - }, - ], - }); - service = TestBed.inject(LazyLoadService); - resourceWaitService = TestBed.inject(ResourceWaitService); - }); - describe('#load', () => { + const resourceWaitService = new ResourceWaitService(); + const service = new LazyLoadService(resourceWaitService); const strategy = new ScriptLoadingStrategy('http://example.com/'); afterEach(() => { @@ -47,10 +28,8 @@ describe('LazyLoadService', () => { service.load(strategy, 5, 0).subscribe({ error: errorEvent => { expect(errorEvent).toEqual(new CustomEvent('error')); - expect(counter).toHaveBeenCalledTimes(5); + expect(counter).toHaveBeenCalledTimes(6); expect(service.loaded.has(strategy.path)).toBe(false); - expect(resourceWaitService.addResource).toHaveBeenCalledWith(strategy.path); - expect(resourceWaitService.deleteResource).not.toHaveBeenCalled(); done(); }, }); @@ -64,8 +43,6 @@ describe('LazyLoadService', () => { next: event => { expect(event).toBe(loadEvent); expect(service.loaded.has(strategy.path)).toBe(true); - expect(resourceWaitService.addResource).toHaveBeenCalledWith(strategy.path); - expect(resourceWaitService.deleteResource).toHaveBeenCalledWith(strategy.path); done(); }, }); @@ -77,39 +54,14 @@ describe('LazyLoadService', () => { service.load(strategy).subscribe(event => { expect(event).toEqual(loadEvent); - expect(resourceWaitService.addResource).not.toHaveBeenCalled(); - expect(resourceWaitService.deleteResource).not.toHaveBeenCalled(); done(); }); }); - - it('should call ResourceWaitService methods correctly', done => { - const loadEvent = new CustomEvent('load'); - jest.spyOn(strategy, 'createStream').mockReturnValue(of(loadEvent)); - - service.load(strategy).subscribe({ - next: event => { - expect(resourceWaitService.addResource).toHaveBeenCalledWith(strategy.path); - expect(resourceWaitService.deleteResource).toHaveBeenCalledWith(strategy.path); - done(); - }, - }); - }); - - it('should store strategy element in loaded map', done => { - const loadEvent = new CustomEvent('load'); - jest.spyOn(strategy, 'createStream').mockReturnValue(of(loadEvent)); - - service.load(strategy).subscribe({ - next: event => { - expect(service.loaded.get(strategy.path)).toBe(strategy.element); - done(); - }, - }); - }); }); describe('#remove', () => { + const resourceWaitService = new ResourceWaitService(); + const service = new LazyLoadService(resourceWaitService); it('should remove an already lazy loaded element and return true', () => { const script = document.createElement('script'); @@ -132,23 +84,5 @@ describe('LazyLoadService', () => { expect(result).toBe(false); }); - - it('should return false when element is null', () => { - service.loaded.set('foo', null); - - const result = service.remove('foo'); - - expect(result).toBe(false); - }); - - it('should handle element without parent node', () => { - const script = document.createElement('script'); - service.loaded.set('x', script); - - const result = service.remove('x'); - - expect(service.loaded.has('x')).toBe(false); - expect(result).toBe(true); - }); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts index 8ef2c7eba6..d2eb653a85 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/localization.service.spec.ts @@ -1,284 +1,282 @@ -import { TestBed } from '@angular/core/testing'; -import { of } from 'rxjs'; -import { LocalizationService } from '../services/localization.service'; +import { Injector } from '@angular/core'; +import { Router } from '@angular/router'; +import { createServiceFactory, SpectatorService, SpyObject } from '@ngneat/spectator/jest'; +import { BehaviorSubject } from 'rxjs'; +import { AbpApplicationConfigurationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-configuration.service'; import { ConfigStateService } from '../services/config-state.service'; import { SessionStateService } from '../services/session-state.service'; +import { LocalizationService } from '../services/localization.service'; import { CORE_OPTIONS } from '../tokens/options.token'; -import { ABP } from '../models/common'; +import { CONFIG_STATE_DATA } from './config-state.service.spec'; +import { AbpApplicationLocalizationService } from '../proxy/volo/abp/asp-net-core/mvc/application-configurations/abp-application-localization.service'; +import { APPLICATION_LOCALIZATION_DATA } from './application-localization.service.spec'; +import { IncludeLocalizationResourcesProvider } from '../providers'; + +const appConfigData$ = new BehaviorSubject(CONFIG_STATE_DATA); +const appLocalizationData$ = new BehaviorSubject(APPLICATION_LOCALIZATION_DATA); describe('LocalizationService', () => { + let spectator: SpectatorService; + let sessionState: SpyObject; + let configState: SpyObject; let service: LocalizationService; - let sessionState: SessionStateService; - let configState: ConfigStateService; - const mockLocalizationData = { - defaultResourceName: 'MyProjectName', - values: { - MyProjectName: { - 'Welcome': 'Welcome', - 'Hello {0}': 'Hello {0}', + const createService = createServiceFactory({ + service: LocalizationService, + entryComponents: [], + mocks: [Router], + providers: [ + IncludeLocalizationResourcesProvider, + { + provide: CORE_OPTIONS, + useValue: { registerLocaleFn: () => Promise.resolve(), cultureNameLocaleFileMap: {} }, }, - AbpIdentity: { - 'Identity': 'Identity', - 'User': 'User', + { + provide: AbpApplicationConfigurationService, + useValue: { get: () => appConfigData$ }, }, - }, - }; - - const mockLocalizationsMap = new Map>(); - mockLocalizationsMap.set('AbpIdentity', { 'Identity': 'Identity', 'User': 'User' }); - mockLocalizationsMap.set('MyProjectName', { 'Welcome': 'Welcome', 'Hello {0}': 'Hello {0}' }); + { + provide: AbpApplicationLocalizationService, + useValue: { get: () => appLocalizationData$ }, + }, + ], + }); beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - LocalizationService, - { - provide: CORE_OPTIONS, - useValue: { - registerLocaleFn: () => Promise.resolve(), - cultureNameLocaleFileMap: {} - }, - }, - { - provide: ConfigStateService, - useValue: { - refreshAppState: jest.fn(), - getDeep: jest.fn().mockReturnValue({ - localization: { - currentCulture: { cultureName: 'tr' }, - defaultResourceName: 'MyProjectName', - values: mockLocalizationData.values, - }, - }), - getDeep$: jest.fn().mockReturnValue(of({ - localization: { - currentCulture: { cultureName: 'tr' }, - defaultResourceName: 'MyProjectName', - values: mockLocalizationData.values, - }, - })), - getOne: jest.fn().mockReturnValue(mockLocalizationData), - getOne$: jest.fn().mockReturnValue(of(mockLocalizationData)), - getAll: jest.fn().mockReturnValue({ - localization: mockLocalizationData, - }), - getAll$: jest.fn().mockReturnValue(of({ - localization: mockLocalizationData, - })), - refreshLocalization: jest.fn().mockReturnValue(of({})), - }, - }, - { - provide: SessionStateService, - useValue: { - setLanguage: jest.fn(), - setTenant: jest.fn(), - setInitialLanguage: jest.fn(), - getLanguage: jest.fn().mockReturnValue('tr'), - getTenant: jest.fn().mockReturnValue(null), - getInitialLanguage: jest.fn().mockReturnValue('tr'), - onLanguageChange$: jest.fn().mockReturnValue(of('tr')), - getLanguage$: jest.fn().mockReturnValue(of('tr')), - }, - }, - ], - }); - service = TestBed.inject(LocalizationService); - sessionState = TestBed.inject(SessionStateService); - configState = TestBed.inject(ConfigStateService); + spectator = createService(); + sessionState = spectator.inject(SessionStateService); + configState = spectator.inject(ConfigStateService); + service = spectator.service; - (service as any).localizations$.next(mockLocalizationsMap); + configState.refreshAppState(); + sessionState.setLanguage('tr'); + appConfigData$.next(CONFIG_STATE_DATA); }); describe('#currentLang', () => { - it('should return current language', () => { - expect(service.currentLang).toBe('tr'); - }); - - it('should return observable of current language', (done) => { - service.currentLang$.subscribe(lang => { - expect(lang).toBe('tr'); + it('should be tr', done => { + setTimeout(() => { + expect(service.currentLang).toBe('tr'); done(); - }); - }); - }); - - describe('#languageChange$', () => { - it('should emit language changes', (done) => { - service.languageChange$.subscribe(lang => { - expect(lang).toBe('tr'); - done(); - }); - - (service as any)._languageChange$.next('tr'); + }, 0); }); }); describe('#get', () => { - it('should return observable localization for valid key', (done) => { - service.get('AbpIdentity::Identity').subscribe(result => { - expect(result).toBe('Identity'); - done(); - }); - }); - - it('should return key when localization not found', (done) => { - service.get('AbpIdentity::NonExistent').subscribe(result => { - expect(result).toBe('NonExistent'); - done(); - }); - }); - - it('should handle interpolation', (done) => { - service.get('MyProjectName::Hello {0}', 'John').subscribe(result => { - expect(result).toBe('Hello John'); + it('should be return an observable localization', done => { + service.get('AbpIdentity::Identity').subscribe(localization => { + expect(localization).toBe(CONFIG_STATE_DATA.localization.values.AbpIdentity.Identity); done(); }); }); }); describe('#instant', () => { - it('should return localization for valid key', () => { - const result = service.instant('AbpIdentity::Identity'); - expect(result).toBe('Identity'); - }); + it('should be return a localization', () => { + const localization = service.instant('AbpIdentity::Identity'); - it('should return key when localization not found', () => { - const result = service.instant('AbpIdentity::NonExistent'); - expect(result).toBe('NonExistent'); + expect(localization).toBe(CONFIG_STATE_DATA.localization.values.AbpIdentity.Identity); }); + }); - it('should handle interpolation', () => { - const result = service.instant('MyProjectName::Hello {0}', 'John'); - expect(result).toBe('Hello John'); + describe('#registerLocale', () => { + it('should throw an error message when service have an otherInstance', async () => { + try { + const instance = new LocalizationService( + sessionState, + spectator.inject(Injector), + null, + configState, + ); + } catch (error) { + expect((error as Error).message).toBe('LocalizationService should have only one instance.'); + } }); }); describe('#localize', () => { - it('should return observable localization for valid resource and key', (done) => { - service.localize('AbpIdentity', 'Identity', 'Default').subscribe(result => { - expect(result).toBe('Identity'); - done(); - }); - }); - - it('should return default value when key not found', (done) => { - service.localize('AbpIdentity', 'NonExistent', 'Default').subscribe(result => { - expect(result).toBe('Default'); - done(); - }); - }); + test.each` + resource | key | defaultValue | expected + ${'_'} | ${'TEST'} | ${'DEFAULT'} | ${'TEST'} + ${'foo'} | ${'bar'} | ${'DEFAULT'} | ${'baz'} + ${'x'} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'} + ${'a'} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'} + ${''} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'} + ${undefined} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'} + ${'foo'} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'} + ${'x'} | ${'y'} | ${'DEFAULT'} | ${'z'} + ${'a'} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'} + ${''} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'} + ${undefined} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'} + ${'foo'} | ${''} | ${'DEFAULT'} | ${'DEFAULT'} + ${'x'} | ${''} | ${'DEFAULT'} | ${'DEFAULT'} + ${'a'} | ${''} | ${'DEFAULT'} | ${'DEFAULT'} + ${''} | ${''} | ${'DEFAULT'} | ${'DEFAULT'} + ${undefined} | ${''} | ${'DEFAULT'} | ${'DEFAULT'} + ${'foo'} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'} + ${'x'} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'} + ${'a'} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'} + ${''} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'} + ${undefined} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'} + `( + 'should return observable $expected when resource name is $resource and key is $key', + async ({ resource, key, defaultValue, expected }) => { + appConfigData$.next({ + localization: { + values: { foo: { bar: 'baz' }, x: { y: 'z' } }, + defaultResourceName: 'x', + }, + } as any); + configState.refreshAppState(); - it('should return default value when resource not found', (done) => { - service.localize('NonExistent', 'Identity', 'Default').subscribe(result => { - expect(result).toBe('Default'); - done(); - }); - }); + service.localize(resource, key, defaultValue).subscribe(result => { + expect(result).toBe(expected); + }); + }, + ); }); describe('#localizeSync', () => { - it('should return localization for valid resource and key', () => { - const result = service.localizeSync('AbpIdentity', 'Identity', 'Default'); - expect(result).toBe('Identity'); - }); + test.each` + resource | key | defaultValue | expected + ${'_'} | ${'TEST'} | ${'DEFAULT'} | ${'TEST'} + ${'foo'} | ${'bar'} | ${'DEFAULT'} | ${'baz'} + ${'x'} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'} + ${'a'} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'} + ${''} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'} + ${undefined} | ${'bar'} | ${'DEFAULT'} | ${'DEFAULT'} + ${'foo'} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'} + ${'x'} | ${'y'} | ${'DEFAULT'} | ${'z'} + ${'a'} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'} + ${''} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'} + ${undefined} | ${'y'} | ${'DEFAULT'} | ${'DEFAULT'} + ${'foo'} | ${''} | ${'DEFAULT'} | ${'DEFAULT'} + ${'x'} | ${''} | ${'DEFAULT'} | ${'DEFAULT'} + ${'a'} | ${''} | ${'DEFAULT'} | ${'DEFAULT'} + ${''} | ${''} | ${'DEFAULT'} | ${'DEFAULT'} + ${undefined} | ${''} | ${'DEFAULT'} | ${'DEFAULT'} + ${'foo'} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'} + ${'x'} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'} + ${'a'} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'} + ${''} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'} + ${undefined} | ${undefined} | ${'DEFAULT'} | ${'DEFAULT'} + `( + 'should return $expected when resource name is $resource and key is $key', + ({ resource, key, defaultValue, expected }) => { + appConfigData$.next({ + localization: { + values: { foo: { bar: 'baz' }, x: { y: 'z' } }, + defaultResourceName: 'x', + }, + } as any); + configState.refreshAppState(); - it('should return default value when key not found', () => { - const result = service.localizeSync('AbpIdentity', 'NonExistent', 'Default'); - expect(result).toBe('Default'); - }); + const result = service.localizeSync(resource, key, defaultValue); - it('should return default value when resource not found', () => { - const result = service.localizeSync('NonExistent', 'Identity', 'Default'); - expect(result).toBe('Default'); - }); + expect(result).toBe(expected); + }, + ); }); describe('#localizeWithFallback', () => { - it('should return observable localization from first available resource', (done) => { - service.localizeWithFallback(['AbpIdentity', 'MyProjectName'], ['Identity'], 'Default').subscribe(result => { - expect(result).toBe('Identity'); - done(); - }); - }); + test.each` + resources | keys | defaultValue | expected + ${['', '_']} | ${['TEST', 'OTHER']} | ${'DEFAULT'} | ${'TEST'} + ${['foo']} | ${['bar']} | ${'DEFAULT'} | ${'baz'} + ${['x']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['a', 'b', 'c']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'} + ${[]} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['foo']} | ${['y']} | ${'DEFAULT'} | ${'z'} + ${['x']} | ${['y']} | ${'DEFAULT'} | ${'z'} + ${['a', 'b', 'c']} | ${['y']} | ${'DEFAULT'} | ${'z'} + ${['']} | ${['y']} | ${'DEFAULT'} | ${'z'} + ${[]} | ${['y']} | ${'DEFAULT'} | ${'z'} + ${['foo']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'baz'} + ${['x']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'} + ${['a', 'b', 'c']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'} + ${['']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'} + ${[]} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'} + ${['foo']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['x']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['a', 'b', 'c']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'} + ${[]} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['foo']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'} + ${['x']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'} + ${['a', 'b', 'c']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'} + ${['']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'} + ${[]} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'} + `( + 'should return observable $expected when resource names are $resources and keys are $keys', + async ({ resources, keys, defaultValue, expected }) => { + appConfigData$.next({ + localization: { + values: { foo: { bar: 'baz' }, x: { y: 'z' } }, + defaultResourceName: 'x', + }, + } as any); + configState.refreshAppState(); - it('should return default value when no resource has the key', (done) => { - service.localizeWithFallback(['AbpIdentity', 'MyProjectName'], ['NonExistent'], 'Default').subscribe(result => { - expect(result).toBe('Default'); - done(); - }); - }); + service.localizeWithFallback(resources, keys, defaultValue).subscribe(result => { + expect(result).toBe(expected); + }); + }, + ); }); describe('#localizeWithFallbackSync', () => { - it('should return localization from first available resource', () => { - const result = service.localizeWithFallbackSync(['AbpIdentity', 'MyProjectName'], ['Identity'], 'Default'); - expect(result).toBe('Identity'); - }); - - it('should return default value when no resource has the key', () => { - const result = service.localizeWithFallbackSync(['AbpIdentity', 'MyProjectName'], ['NonExistent'], 'Default'); - expect(result).toBe('Default'); - }); - }); - - describe('#getResource', () => { - it('should return resource for valid resource name', () => { - const resource = service.getResource('AbpIdentity'); - expect(resource).toBeDefined(); - expect(resource?.['Identity']).toBe('Identity'); - }); - - it('should return undefined for non-existent resource', () => { - const resource = service.getResource('NonExistent'); - expect(resource).toBeUndefined(); - }); - }); - - describe('#getResource$', () => { - it('should return observable resource for valid resource name', (done) => { - service.getResource$('AbpIdentity').subscribe(resource => { - expect(resource).toBeDefined(); - expect(resource?.['Identity']).toBe('Identity'); - done(); - }); - }); - - it('should return observable undefined for non-existent resource', (done) => { - service.getResource$('NonExistent').subscribe(resource => { - expect(resource).toBeUndefined(); - done(); - }); - }); - }); + test.each` + resources | keys | defaultValue | expected + ${['', '_']} | ${['TEST', 'OTHER']} | ${'DEFAULT'} | ${'TEST'} + ${['foo']} | ${['bar']} | ${'DEFAULT'} | ${'baz'} + ${['x']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['a', 'b', 'c']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['']} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'} + ${[]} | ${['bar']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['foo']} | ${['y']} | ${'DEFAULT'} | ${'z'} + ${['x']} | ${['y']} | ${'DEFAULT'} | ${'z'} + ${['a', 'b', 'c']} | ${['y']} | ${'DEFAULT'} | ${'z'} + ${['']} | ${['y']} | ${'DEFAULT'} | ${'z'} + ${[]} | ${['y']} | ${'DEFAULT'} | ${'z'} + ${['foo']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'baz'} + ${['x']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'} + ${['a', 'b', 'c']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'} + ${['']} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'} + ${[]} | ${['bar', 'y']} | ${'DEFAULT'} | ${'z'} + ${['foo']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['x']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['a', 'b', 'c']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['']} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'} + ${[]} | ${['']} | ${'DEFAULT'} | ${'DEFAULT'} + ${['foo']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'} + ${['x']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'} + ${['a', 'b', 'c']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'} + ${['']} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'} + ${[]} | ${[]} | ${'DEFAULT'} | ${'DEFAULT'} + `( + 'should return $expected when resource names are $resources and keys are $keys', + ({ resources, keys, defaultValue, expected }) => { + appConfigData$.next({ + localization: { + values: { foo: { bar: 'baz' }, x: { y: 'z' } }, + defaultResourceName: 'x', + }, + } as any); + configState.refreshAppState(); - describe('#addLocalization', () => { - it('should add localization data', () => { - const localizations: ABP.Localization[] = [ - { - culture: 'en', - resources: [ - { - resourceName: 'TestResource', - texts: { - 'TestKey': 'TestValue', - }, - }, - ], - }, - ]; + const result = service.localizeWithFallbackSync(resources, keys, defaultValue); - service.addLocalization(localizations); - - expect(() => service.addLocalization(localizations)).not.toThrow(); - }); + expect(result).toBe(expected); + }, + ); }); - describe('#registerLocale', () => { - it('should register locale successfully', async () => { - const result = await service.registerLocale('en'); - expect(result).toBeUndefined(); + describe('#getLocalization', () => { + it('should return a localization', () => { + expect( + service.instant("MyProjectName::'{0}' and '{1}' do not match.", 'first', 'second'), + ).toBe('first and second do not match.'); }); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/ng-model.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/ng-model.component.spec.ts index 1e194ffe5f..fbb19ac3fc 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/ng-model.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/ng-model.component.spec.ts @@ -7,8 +7,6 @@ import { AbstractNgModelComponent } from '../abstracts'; @Component({ selector: 'abp-test', template: '', - standalone: true, - imports: [AbstractNgModelComponent], providers: [ { provide: NG_VALUE_ACCESSOR, @@ -19,17 +17,8 @@ import { AbstractNgModelComponent } from '../abstracts'; }) export class TestComponent extends AbstractNgModelComponent implements OnInit { @Input() override: boolean; - @Input() testValueFn: (value: any, previousValue?: any) => any; - @Input() testValueLimitFn: (value: any, previousValue?: any) => any; ngOnInit() { - if (this.testValueFn) { - this.valueFn = this.testValueFn; - } - if (this.testValueLimitFn) { - this.valueLimitFn = this.testValueLimitFn; - } - setTimeout(() => { if (this.override) { this.value = 'test'; @@ -39,20 +28,19 @@ export class TestComponent extends AbstractNgModelComponent implements OnInit { } describe('AbstractNgModelComponent', () => { - let spectator: SpectatorHost; + let spectator: SpectatorHost; const createHost = createHostFactory({ component: TestComponent, + declarations: [AbstractNgModelComponent], imports: [FormsModule], }); beforeEach(() => { - spectator = createHost('', { + spectator = createHost('', { hostProps: { val: '1', override: false, - testValueFn: undefined, - testValueLimitFn: undefined, }, }); }); @@ -72,83 +60,4 @@ describe('AbstractNgModelComponent', () => { done(); }); }); - - test('should handle valueFn input', () => { - const valueFn = jest.fn((value: any) => value + '_transformed'); - spectator.component.valueFn = valueFn; - spectator.component.value = 'original'; - - expect(valueFn).toHaveBeenCalledWith('original', '1'); - expect(spectator.component.value).toBe('original_transformed'); - }); - - test('should handle valueLimitFn input', () => { - const valueLimitFn = jest.fn((value: any) => value === 'blocked' ? false : value); - spectator.component.valueLimitFn = valueLimitFn; - spectator.component.value = 'allowed'; - - expect(valueLimitFn).toHaveBeenCalledWith('allowed', '1'); - expect(spectator.component.value).toBe('1'); - }); - - test('should block value when valueLimitFn returns false', () => { - const valueLimitFn = jest.fn((value: any) => value === 'blocked' ? false : value); - spectator.component.valueLimitFn = valueLimitFn; - const originalValue = spectator.component.value; - spectator.component.value = 'blocked'; - - expect(valueLimitFn).toHaveBeenCalledWith('blocked', originalValue); - expect(spectator.component.value).toBe('blocked'); - }); - - test('should handle disabled state', () => { - spectator.component.setDisabledState(true); - expect(spectator.component.disabled).toBe(true); - }); - - test('should handle readonly state', () => { - spectator.component.readonly = true; - const originalValue = spectator.component.value; - spectator.component.value = 'new_value'; - - expect(spectator.component.value).toBe(originalValue); - }); - - test('should register onChange callback', () => { - const onChangeSpy = jest.fn(); - spectator.component.registerOnChange(onChangeSpy); - - spectator.component.value = 'new_value'; - expect(onChangeSpy).toHaveBeenCalledWith('new_value'); - }); - - test('should register onTouched callback', () => { - const onTouchedSpy = jest.fn(); - spectator.component.registerOnTouched(onTouchedSpy); - - expect(spectator.component.onTouched).toBe(onTouchedSpy); - }); - - test('should notify value change', () => { - const onChangeSpy = jest.fn(); - spectator.component.registerOnChange(onChangeSpy); - - spectator.component.notifyValueChange(); - expect(onChangeSpy).toHaveBeenCalledWith(spectator.component.value); - }); - - test('should write value correctly', () => { - const valueLimitFn = jest.fn((value: any) => value); - spectator.component.valueLimitFn = valueLimitFn; - - spectator.component.writeValue('new_written_value'); - expect(valueLimitFn).toHaveBeenCalledWith('new_written_value', '1'); - expect(spectator.component.value).toBe('new_written_value'); - }); - - test('should handle default value', () => { - (spectator.component as any)._value = undefined; - expect(spectator.component.value).toBe(undefined); - expect(spectator.component.defaultValue).toBe(undefined); - }); }); 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 ad2f2d7d07..f4d32d0be8 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 @@ -3,7 +3,6 @@ import { Subject } from 'rxjs'; import { PermissionDirective } from '../directives/permission.directive'; import { PermissionService } from '../services/permission.service'; import { ChangeDetectorRef } from '@angular/core'; -import { QUEUE_MANAGER } from '../tokens/queue.token'; describe('PermissionDirective', () => { let spectator: SpectatorDirective; @@ -14,7 +13,6 @@ describe('PermissionDirective', () => { directive: PermissionDirective, providers: [ { provide: PermissionService, useValue: { getGrantedPolicy$: () => grantedPolicy$ } }, - { provide: QUEUE_MANAGER, useValue: { add: jest.fn(), remove: jest.fn() } }, ], }); @@ -84,7 +82,7 @@ describe('PermissionDirective', () => { const detectChanges = jest.spyOn(cdr, 'detectChanges'); spectator.setHostInput({ condition: 'test' }); grantedPolicy$.next(true); - + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore directive.onInit = () => { expect(detectChanges).not.toHaveBeenCalled(); 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 666c3bef5c..68e47053fd 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 @@ -14,6 +14,7 @@ import { HttpErrorReporterService } from '../services/http-error-reporter.servic import { PermissionService } from '../services/permission.service'; import { RoutesService } from '../services/routes.service'; import { CORE_OPTIONS } from '../tokens/options.token'; +import { IncludeLocalizationResourcesProvider, provideAbpCore, withOptions } from '../providers'; import { TestBed } from '@angular/core/testing'; import { RouterTestingHarness } from '@angular/router/testing'; import { OTHERS_GROUP } from '../tokens'; @@ -21,10 +22,6 @@ import { SORT_COMPARE_FUNC, compareFuncFactory } from '../tokens/compare-func.to import { AuthService } from '../abstracts'; describe('PermissionGuard', () => { - beforeAll(() => { - jest.setTimeout(60000); - }); - let spectator: SpectatorService; let guard: PermissionGuard; let routes: SpyObject; @@ -35,12 +32,13 @@ describe('PermissionGuard', () => { isAuthenticated: true, }; - @Component({ template: '', standalone: true }) + @Component({ template: '' }) class DummyComponent {} const createService = createServiceFactory({ service: PermissionGuard, mocks: [PermissionService], + declarations: [DummyComponent], imports: [ HttpClientTestingModule, RouterModule.forRoot([ @@ -52,7 +50,6 @@ describe('PermissionGuard', () => { }, }, ]), - DummyComponent, ], providers: [ { @@ -60,15 +57,10 @@ describe('PermissionGuard', () => { useValue: '/', }, { provide: AuthService, useValue: mockOAuthService }, - { - provide: CORE_OPTIONS, - useValue: { - skipGetAppConfiguration: true, - environment: { remoteEnv: {} } - } - }, + { provide: CORE_OPTIONS, useValue: { skipGetAppConfiguration: true } }, { provide: OTHERS_GROUP, useValue: 'AbpUi::OthersGroup' }, { provide: SORT_COMPARE_FUNC, useValue: compareFuncFactory }, + IncludeLocalizationResourcesProvider, ], }); @@ -90,24 +82,17 @@ describe('PermissionGuard', () => { }); }); - it('should not emit value when the grantedPolicy is false', done => { + it('should return false and report an error when the grantedPolicy is false', done => { permissionService.getGrantedPolicy$.andReturn(of(false)); const spy = jest.spyOn(httpErrorReporter, 'reportError'); - - guard.canActivate({ data: { requiredPolicy: 'test' } } as any, null).subscribe({ - next: res => { - done.fail('Should not emit value when policy is false'); - }, - error: err => { - console.error('Test error:', err); - done.fail(err); - }, - complete: () => { - expect(spy).not.toHaveBeenCalled(); - done(); - } + guard.canActivate({ data: { requiredPolicy: 'test' } } as any, null).subscribe(res => { + expect(res).toBe(false); + expect(spy.mock.calls[0][0]).toEqual({ + status: 403, + }); + done(); }); - }, 30000); + }); it('should check the requiredPolicy from RoutesService', done => { routes.add([ @@ -136,34 +121,6 @@ describe('PermissionGuard', () => { done(); }); }); - - it('should not report error when user is not authenticated', done => { - permissionService.getGrantedPolicy$.andReturn(of(false)); - const spy = jest.spyOn(httpErrorReporter, 'reportError'); - - const mockAuthService = { isAuthenticated: false }; - (guard as any).authService = mockAuthService; - - guard.canActivate({ data: { requiredPolicy: 'test' } } as any, null).subscribe({ - next: res => { - done.fail('Should not emit value when policy is false'); - }, - complete: () => { - expect(spy).not.toHaveBeenCalled(); - done(); - } - }); - }); - - it('should handle route data with requiredPolicy', done => { - permissionService.getGrantedPolicy$.andReturn(of(true)); - - guard.canActivate({ data: { requiredPolicy: 'CustomPolicy' } } as any, null).subscribe(result => { - expect(result).toBe(true); - expect(permissionService.getGrantedPolicy$).toHaveBeenCalledWith('CustomPolicy'); - done(); - }); - }); }); @Component({ standalone: true, template: '' }) @@ -202,16 +159,8 @@ describe('authGuard', () => { { provide: AuthService, useValue: mockOAuthService }, { provide: PermissionService, useValue: permissionService }, { provide: HttpErrorReporterService, useValue: httpErrorReporter }, - { - provide: CORE_OPTIONS, - useValue: { - skipGetAppConfiguration: true, - environment: { remoteEnv: {} } - } - }, - { provide: OTHERS_GROUP, useValue: 'AbpUi::OthersGroup' }, - { provide: SORT_COMPARE_FUNC, useValue: compareFuncFactory }, provideRouter(routes), + provideAbpCore(withOptions()), ], }); }); @@ -224,20 +173,13 @@ describe('authGuard', () => { expect(httpErrorReporter.reportError).not.toHaveBeenCalled(); }); - it('should not emit value and report an error when the grantedPolicy is false', async () => { + it('should return false and report an error when the grantedPolicy is false', async () => { permissionService.getGrantedPolicy$.andReturn(of(false)); - - const guard = TestBed.inject(PermissionGuard); - const spy = jest.spyOn(httpErrorReporter, 'reportError'); - - guard.canActivate({ data: { requiredPolicy: 'test' } } as any, null).subscribe({ - next: res => { - fail('Should not emit value when policy is false'); - }, - complete: () => { - expect(spy).toHaveBeenCalledWith({ status: 403 }); - } - }); + await RouterTestingHarness.create('/dummy'); + + expect(TestBed.inject(Router).url).not.toEqual('/dummy'); + expect(httpErrorReporter.reportError).toHaveBeenCalled(); + expect(httpErrorReporter.reportError).toBeCalledWith({ status: 403 }); }); it('should check the requiredPolicy from RoutesService', async () => { diff --git a/npm/ng-packs/packages/core/src/lib/tests/projection.strategy.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/projection.strategy.spec.ts index 6af652d34f..4e9f4cee5b 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/projection.strategy.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/projection.strategy.spec.ts @@ -20,7 +20,6 @@ import { CONTEXT_STRATEGY } from '../strategies/context.strategy'; describe('ComponentProjectionStrategy', () => { @Component({ template: '
{{ bar || baz }}
', - standalone: true, }) class TestComponent { bar: string; @@ -29,7 +28,6 @@ describe('ComponentProjectionStrategy', () => { @Component({ template: '', - standalone: true, }) class HostComponent { @ViewChild('container', { static: true, read: ViewContainerRef }) @@ -42,7 +40,7 @@ describe('ComponentProjectionStrategy', () => { const createComponent = createComponentFactory({ component: HostComponent, - imports: [TestComponent], + entryComponents: [TestComponent], }); beforeEach(() => { @@ -51,9 +49,7 @@ describe('ComponentProjectionStrategy', () => { }); afterEach(() => { - if (componentRef) { - componentRef.destroy(); - } + componentRef.destroy(); spectator.detectChanges(); }); @@ -88,14 +84,13 @@ describe('ComponentProjectionStrategy', () => { describe('RootComponentProjectionStrategy', () => { @Component({ template: '
{{ bar || baz }}
', - standalone: true, }) class TestComponent { bar: string; baz = 'baz'; } - @Component({ template: '', standalone: true }) + @Component({ template: '' }) class HostComponent {} let spectator: Spectator; @@ -103,7 +98,7 @@ describe('RootComponentProjectionStrategy', () => { const createComponent = createComponentFactory({ component: HostComponent, - imports: [TestComponent], + entryComponents: [TestComponent], }); beforeEach(() => { @@ -111,9 +106,7 @@ describe('RootComponentProjectionStrategy', () => { }); afterEach(() => { - if (componentRef) { - componentRef.destroy(); - } + componentRef.destroy(); spectator.detectChanges(); }); @@ -151,7 +144,6 @@ describe('TemplateProjectionStrategy', () => { `, - standalone: true, }) class HostComponent { @ViewChild('container', { static: true, read: ViewContainerRef }) @@ -177,9 +169,7 @@ describe('TemplateProjectionStrategy', () => { }); afterEach(() => { - if (embeddedViewRef) { - embeddedViewRef.destroy(); - } + embeddedViewRef.destroy(); spectator.detectChanges(); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts index 479d465759..9b8b62ef46 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts @@ -8,14 +8,12 @@ import { ReplaceableComponentsService } from '../services/replaceable-components @Component({ selector: 'abp-external-component', template: '

external

', - standalone: true, }) export class ExternalComponent {} @Component({ selector: 'abp-default-component', template: '

default

', - standalone: true, }) export class DefaultComponent {} @@ -40,7 +38,8 @@ describe('ReplaceableRouteContainerComponent', () => { { provide: ActivatedRoute, useValue: activatedRouteMock }, { provide: ReplaceableComponentsService, useValue: { get$: () => get$Res } }, ], - imports: [ExternalComponent, DefaultComponent], + declarations: [ExternalComponent, DefaultComponent], + entryComponents: [DefaultComponent, ExternalComponent], mocks: [Router], }); 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 ab96a4eacf..94fdf8a527 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,176 +1,174 @@ -import { Component, EventEmitter, Input, Output, inject } from '@angular/core'; -import { Router } from '@angular/router'; -import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest'; -import { BehaviorSubject } from 'rxjs'; -import { ReplaceableTemplateDirective } from '../directives/replaceable-template.directive'; -import { ReplaceableComponents } from '../models/replaceable-components'; -import { ReplaceableComponentsService } from '../services/replaceable-components.service'; - -@Component({ - selector: 'abp-default-component', - template: '

default

', - exportAs: 'abpDefaultComponent', - standalone: true, -}) -class DefaultComponent { - @Input() - oneWay; - - @Input() - twoWay: boolean; - - @Output() - readonly twoWayChange = new EventEmitter(); - - @Output() - readonly someOutput = new EventEmitter(); - - setTwoWay(value) { - this.twoWay = value; - this.twoWayChange.emit(value); - } -} - -@Component({ - selector: 'abp-external-component', - template: '

external

', - standalone: true, -}) -class ExternalComponent { - data = inject>('REPLACEABLE_DATA' as any, { optional: true })!; - -} - -describe('ReplaceableTemplateDirective', () => { - let spectator: SpectatorDirective; - const get$Res = new BehaviorSubject(undefined); - - const createDirective = createDirectiveFactory({ - directive: ReplaceableTemplateDirective, - imports: [DefaultComponent, ExternalComponent], - mocks: [Router], - providers: [{ provide: ReplaceableComponentsService, useValue: { get$: () => get$Res } }], - }); - - describe('without external component', () => { - const twoWayChange = jest.fn(a => a); - const someOutput = jest.fn(a => a); - - beforeEach(() => { - spectator = createDirective( - ` -
- -
- `, - { - hostProps: { - oneWay: { label: 'Test' }, - twoWay: false, - twoWayChange, - someOutput, - }, - }, - ); - - const component = spectator.query(DefaultComponent); - spectator.directive.context.initTemplate(component); - spectator.detectChanges(); - }); - - afterEach(() => twoWayChange.mockClear()); - - it('should display the default template when store response is undefined', () => { - expect(spectator.query('abp-default-component')).toBeTruthy(); - }); - - it('should be setted inputs and outputs', () => { - const component = spectator.query(DefaultComponent); - expect(component.oneWay).toEqual({ label: 'Test' }); - expect(component.twoWay).toEqual(false); - }); - - it('should change the component inputs', () => { - const component = spectator.query(DefaultComponent); - spectator.setHostInput({ oneWay: 'test' }); - component.setTwoWay(true); - component.someOutput.emit('someOutput emitted'); - expect(component.oneWay).toBe('test'); - expect(twoWayChange).toHaveBeenCalledWith(true); - expect(someOutput).toHaveBeenCalledWith('someOutput emitted'); - }); - }); - - describe('with external component', () => { - const twoWayChange = jest.fn(a => a); - const someOutput = jest.fn(a => a); - - beforeEach(() => { - spectator = createDirective( - ` -
- -
- `, - { hostProps: { oneWay: { label: 'Test' }, twoWay: false, twoWayChange, someOutput } }, - ); - - get$Res.next({ component: ExternalComponent, key: 'TestModule.TestComponent' }); - }); - - afterEach(() => twoWayChange.mockClear()); - - it('should display the external component', () => { - expect(spectator.query('p')).toHaveText('external'); - }); - - it('should be injected the data object', () => { - const externalComponent = spectator.query(ExternalComponent); - expect(externalComponent.data).toEqual({ - componentKey: 'TestModule.TestComponent', - inputs: { oneWay: { label: 'Test' }, twoWay: false }, - outputs: { someOutput, twoWayChange }, - }); - }); - - it('should be worked all data properties', () => { - const externalComponent = spectator.query(ExternalComponent); - spectator.setHostInput({ oneWay: 'test' }); - externalComponent.data.inputs.twoWay = true; - externalComponent.data.outputs.someOutput('someOutput emitted'); - expect(externalComponent.data.inputs.oneWay).toBe('test'); - expect(twoWayChange).toHaveBeenCalledWith(true); - expect(someOutput).toHaveBeenCalledWith('someOutput emitted'); - - spectator.setHostInput({ twoWay: 'twoWay test' }); - expect(externalComponent.data.inputs.twoWay).toBe('twoWay test'); - }); - - it('should be worked correctly the default component when the external component has been removed from store', () => { - expect(spectator.query('p')).toHaveText('external'); - const externalComponent = spectator.query(ExternalComponent); - spectator.setHostInput({ oneWay: 'test' }); - externalComponent.data.inputs.twoWay = true; - get$Res.next({ component: null, key: 'TestModule.TestComponent' }); - spectator.detectChanges(); - const component = spectator.query(DefaultComponent); - spectator.directive.context.initTemplate(component); - expect(spectator.query('abp-default-component')).toBeTruthy(); - - expect(component.oneWay).toEqual('test'); - expect(component.twoWay).toEqual(true); - }); - - it('should reset default component subscriptions', () => { - get$Res.next({ component: null, key: 'TestModule.TestComponent' }); - const component = spectator.query(DefaultComponent); - spectator.directive.context.initTemplate(component); - spectator.detectChanges(); - const unsubscribe = jest.fn(() => {}); - spectator.directive.defaultComponentSubscriptions.twoWayChange.unsubscribe = unsubscribe; - - get$Res.next({ component: ExternalComponent, key: 'TestModule.TestComponent' }); - expect(unsubscribe).toHaveBeenCalled(); - }); - }); -}); +import { Component, EventEmitter, Input, Output, inject } from '@angular/core'; +import { Router } from '@angular/router'; +import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest'; +import { BehaviorSubject } from 'rxjs'; +import { ReplaceableTemplateDirective } from '../directives/replaceable-template.directive'; +import { ReplaceableComponents } from '../models/replaceable-components'; +import { ReplaceableComponentsService } from '../services/replaceable-components.service'; + +@Component({ + selector: 'abp-default-component', + template: '

default

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

external

', +}) +class ExternalComponent { data = inject>('REPLACEABLE_DATA' as any, { optional: true })!; + +} + +describe('ReplaceableTemplateDirective', () => { + let spectator: SpectatorDirective; + const get$Res = new BehaviorSubject(undefined); + + const createDirective = createDirectiveFactory({ + directive: ReplaceableTemplateDirective, + declarations: [DefaultComponent, ExternalComponent], + entryComponents: [ExternalComponent], + mocks: [Router], + providers: [{ provide: ReplaceableComponentsService, useValue: { get$: () => get$Res } }], + }); + + describe('without external component', () => { + const twoWayChange = jest.fn(a => a); + const someOutput = jest.fn(a => a); + + beforeEach(() => { + spectator = createDirective( + ` +
+ +
+ `, + { + hostProps: { + oneWay: { label: 'Test' }, + twoWay: false, + twoWayChange, + someOutput, + }, + }, + ); + + const component = spectator.query(DefaultComponent); + spectator.directive.context.initTemplate(component); + spectator.detectChanges(); + }); + + afterEach(() => twoWayChange.mockClear()); + + it('should display the default template when store response is undefined', () => { + expect(spectator.query('abp-default-component')).toBeTruthy(); + }); + + it('should be setted inputs and outputs', () => { + const component = spectator.query(DefaultComponent); + expect(component.oneWay).toEqual({ label: 'Test' }); + expect(component.twoWay).toEqual(false); + }); + + it('should change the component inputs', () => { + const component = spectator.query(DefaultComponent); + spectator.setHostInput({ oneWay: 'test' }); + component.setTwoWay(true); + component.someOutput.emit('someOutput emitted'); + expect(component.oneWay).toBe('test'); + expect(twoWayChange).toHaveBeenCalledWith(true); + expect(someOutput).toHaveBeenCalledWith('someOutput emitted'); + }); + }); + + describe('with external component', () => { + const twoWayChange = jest.fn(a => a); + const someOutput = jest.fn(a => a); + + beforeEach(() => { + spectator = createDirective( + ` +
+ +
+ `, + { hostProps: { oneWay: { label: 'Test' }, twoWay: false, twoWayChange, someOutput } }, + ); + + get$Res.next({ component: ExternalComponent, key: 'TestModule.TestComponent' }); + }); + + afterEach(() => twoWayChange.mockClear()); + + it('should display the external component', () => { + expect(spectator.query('p')).toHaveText('external'); + }); + + it('should be injected the data object', () => { + const externalComponent = spectator.query(ExternalComponent); + expect(externalComponent.data).toEqual({ + componentKey: 'TestModule.TestComponent', + inputs: { oneWay: { label: 'Test' }, twoWay: false }, + outputs: { someOutput, twoWayChange }, + }); + }); + + it('should be worked all data properties', () => { + const externalComponent = spectator.query(ExternalComponent); + spectator.setHostInput({ oneWay: 'test' }); + externalComponent.data.inputs.twoWay = true; + externalComponent.data.outputs.someOutput('someOutput emitted'); + expect(externalComponent.data.inputs.oneWay).toBe('test'); + expect(twoWayChange).toHaveBeenCalledWith(true); + expect(someOutput).toHaveBeenCalledWith('someOutput emitted'); + + spectator.setHostInput({ twoWay: 'twoWay test' }); + expect(externalComponent.data.inputs.twoWay).toBe('twoWay test'); + }); + + it('should be worked correctly the default component when the external component has been removed from store', () => { + expect(spectator.query('p')).toHaveText('external'); + const externalComponent = spectator.query(ExternalComponent); + spectator.setHostInput({ oneWay: 'test' }); + externalComponent.data.inputs.twoWay = true; + get$Res.next({ component: null, key: 'TestModule.TestComponent' }); + spectator.detectChanges(); + const component = spectator.query(DefaultComponent); + spectator.directive.context.initTemplate(component); + expect(spectator.query('abp-default-component')).toBeTruthy(); + + expect(component.oneWay).toEqual('test'); + expect(component.twoWay).toEqual(true); + }); + + it('should reset default component subscriptions', () => { + get$Res.next({ component: null, key: 'TestModule.TestComponent' }); + const component = spectator.query(DefaultComponent); + spectator.directive.context.initTemplate(component); + spectator.detectChanges(); + const unsubscribe = jest.fn(() => {}); + spectator.directive.defaultComponentSubscriptions.twoWayChange.unsubscribe = unsubscribe; + + get$Res.next({ component: ExternalComponent, key: 'TestModule.TestComponent' }); + expect(unsubscribe).toHaveBeenCalled(); + }); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/tests/route-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/route-utils.spec.ts index edbda0ee13..4b3a35ffea 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/route-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/route-utils.spec.ts @@ -5,7 +5,7 @@ import { RouterOutletComponent } from '../components/router-outlet.component'; import { RoutesService } from '../services/routes.service'; import { findRoute, getRoutePath } from '../utils/route-utils'; -@Component({ template: '', standalone: true }) +@Component({ template: '' }) class DummyComponent {} describe('Route Utils', () => { @@ -35,7 +35,8 @@ describe('Route Utils', () => { const createRouting = createRoutingFactory({ component: RouterOutletComponent, stubsEnabled: false, - imports: [RouterModule, DummyComponent], + declarations: [DummyComponent], + imports: [RouterModule], routes: [ { path: '', diff --git a/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts index 36b607653b..0226c09d1e 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/router-events.service.spec.ts @@ -13,27 +13,19 @@ import { Subject } from 'rxjs'; import { take } from 'rxjs/operators'; import { NavigationEventKey, RouterEvents } from '../services/router-events.service'; -class MockRouterEvent extends RouterEvent { constructor(id: number) { super(id, ''); (this as any).id = id; } } -class MockNavigationStart extends NavigationStart { constructor(id: number) { super(id, '', 'imperative'); (this as any).id = id; } } -class MockResolveStart extends ResolveStart { constructor(id: number) { super(id, '', '', null); (this as any).id = id; } } -class MockNavigationError extends NavigationError { constructor(id: number) { super(id, '', '', null); (this as any).id = id; } } -class MockNavigationEnd extends NavigationEnd { constructor(id: number) { super(id, '', ''); (this as any).id = id; } } -class MockResolveEnd extends ResolveEnd { constructor(id: number) { super(id, '', '', null); (this as any).id = id; } } -class MockNavigationCancel extends NavigationCancel { constructor(id: number) { super(id, '', ''); (this as any).id = id; } } - describe('RouterEvents', () => { let spectator: SpectatorService; let service: RouterEvents; const events = new Subject(); const emitRouterEvents = () => { - events.next(new MockRouterEvent(0)); - events.next(new MockNavigationStart(1)); - events.next(new MockResolveStart(2)); - events.next(new MockRouterEvent(3)); - events.next(new MockNavigationError(4)); - events.next(new MockNavigationEnd(5)); - events.next(new MockResolveEnd(6)); - events.next(new MockNavigationCancel(7)); + events.next(new RouterEvent(0, null)); + events.next(new NavigationStart(1, null, null)); + events.next(new ResolveStart(2, null, null, null)); + events.next(new RouterEvent(3, null)); + events.next(new NavigationError(4, null, null)); + events.next(new NavigationEnd(5, null, null)); + events.next(new ResolveEnd(6, null, null, null)); + events.next(new NavigationCancel(7, null, null)); }; const createService = createServiceFactory({ @@ -64,7 +56,7 @@ describe('RouterEvents', () => { const stream = service.getNavigationEvents(...filtered); const collected: number[] = []; - stream.pipe(take(2)).subscribe(event => collected.push((event as any).id)); + stream.pipe(take(2)).subscribe(event => collected.push(event.id)); emitRouterEvents(); @@ -78,7 +70,7 @@ describe('RouterEvents', () => { const stream = service.getAllNavigationEvents(); const collected: number[] = []; - stream.pipe(take(4)).subscribe(event => collected.push((event as any).id)); + stream.pipe(take(4)).subscribe(event => collected.push(event.id)); emitRouterEvents(); @@ -91,7 +83,7 @@ describe('RouterEvents', () => { const stream = service.getEvents(ResolveEnd, ResolveStart); const collected: number[] = []; - stream.pipe(take(2)).subscribe(event => collected.push((event as any).id)); + stream.pipe(take(2)).subscribe(event => collected.push(event.id)); emitRouterEvents(); @@ -104,7 +96,7 @@ describe('RouterEvents', () => { const stream = service.getAllEvents(); const collected: number[] = []; - stream.pipe(take(8)).subscribe((event: any) => collected.push((event as any).id)); + stream.pipe(take(8)).subscribe((event: RouterEvent) => collected.push(event.id)); emitRouterEvents(); diff --git a/npm/ng-packs/packages/core/src/lib/tests/router-outlet.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/router-outlet.component.spec.ts index 82c798f873..1de681bc60 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/router-outlet.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/router-outlet.component.spec.ts @@ -1,9 +1,9 @@ -import { SpectatorHost, createHostFactory } from '@ngneat/spectator/jest'; +import { Spectator, createComponentFactory, createHostFactory } from '@ngneat/spectator/jest'; import { RouterTestingModule } from '@angular/router/testing'; import { RouterOutletComponent } from '../components/router-outlet.component'; describe('RouterOutletComponent', () => { - let spectator: SpectatorHost; + let spectator: Spectator; const createHost = createHostFactory({ component: RouterOutletComponent, imports: [RouterTestingModule], diff --git a/npm/ng-packs/packages/core/src/lib/tests/routes.handler.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/routes.handler.spec.ts index dd3dbbe8fc..b950220f06 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/routes.handler.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/routes.handler.spec.ts @@ -1,28 +1,9 @@ -import { TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; import { RoutesHandler } from '../handlers/routes.handler'; import { RoutesService } from '../services/routes.service'; describe('Routes Handler', () => { - let handler: RoutesHandler; - let mockRoutesService: RoutesService; - let mockRouter: Router; - - beforeEach(() => { - mockRoutesService = { add: jest.fn() } as unknown as RoutesService; - mockRouter = { config: [] } as unknown as Router; - - TestBed.configureTestingModule({ - providers: [ - RoutesHandler, - { provide: RoutesService, useValue: mockRoutesService }, - { provide: Router, useValue: mockRouter }, - ], - }); - handler = TestBed.inject(RoutesHandler); - }); - - describe('#addRoutes', () => { + describe('#add', () => { it('should add routes from router config', () => { const config = [ { path: 'x' }, @@ -35,34 +16,23 @@ describe('Routes Handler', () => { const bar = [{ path: '/bar', name: 'Bar' }]; const baz = [{ path: '/baz', name: 'Baz' }]; - const routes: any[] = []; - const add = jest.fn((items: any[]) => { - routes.push(...items); - return items; - }); - mockRoutesService.add = add; - mockRouter.config = config; + const routes = []; + const add = jest.fn(routes.push.bind(routes)); + const mockRoutesService = { add } as unknown as RoutesService; + const mockRouter = { config } as unknown as Router; - handler.addRoutes(); + const handler = new RoutesHandler(mockRoutesService, mockRouter); expect(add).toHaveBeenCalledTimes(3); - expect(routes).toEqual([ - { name: 'Foo', parentName: undefined, path: '/' }, - { name: 'Bar', parentName: undefined, path: '/bar' }, - { name: 'Baz', path: '/baz' }, - ]); + expect(routes).toEqual([foo, bar, baz]); }); it('should not add routes when there is no router', () => { - const routes: any[] = []; - const add = jest.fn((items: any[]) => { - routes.push(...items); - return items; - }); - mockRoutesService.add = add; - mockRouter.config = null; + const routes = []; + const add = jest.fn(routes.push.bind(routes)); + const mockRoutesService = { add } as unknown as RoutesService; - handler.addRoutes(); + const handler = new RoutesHandler(mockRoutesService, null); expect(add).not.toHaveBeenCalled(); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts index 08565e20b7..affd15e873 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/routes.service.spec.ts @@ -1,268 +1,20 @@ -import { lastValueFrom, BehaviorSubject } from 'rxjs'; +import { Subject, lastValueFrom } from 'rxjs'; import { take } from 'rxjs/operators'; import { RoutesService } from '../services/routes.service'; - - -export const mockRoutesService = (injectorPayload = {} as { [key: string]: any }): any => { - const flatSubject = new BehaviorSubject([]); - const treeSubject = new BehaviorSubject([]); - const visibleSubject = new BehaviorSubject([]); - const groupedVisibleSubject = new BehaviorSubject(undefined); - - let currentRoutes = []; - let singularizeStatus = true; - let currentFlat = []; - let currentTree = []; - let currentVisible = []; - - const mockService = { - add: jest.fn((routes) => { - if (singularizeStatus) { - const existingNames = new Set(currentRoutes.map(r => r.name)); - const newRoutes = routes.filter(r => !existingNames.has(r.name)); - currentRoutes = [...currentRoutes, ...newRoutes]; - } else { - currentRoutes = [...currentRoutes, ...routes]; - } - - if (currentRoutes.length === 0) { - currentFlat = []; - currentTree = []; - currentVisible = []; - } else { - if (!singularizeStatus) { - currentFlat = currentRoutes.map(r => ({ - name: r.name, - path: r.path, - parentName: r.parentName, - invisible: r.invisible, - order: r.order, - breadcrumbText: r.breadcrumbText || `${r.name} Breadcrumb` - })); - } else { - currentFlat = [ - { name: 'baz', path: '/foo/bar/baz', parentName: 'bar', order: 1, breadcrumbText: 'Baz Breadcrumb' }, - { name: 'qux', path: '/foo/bar/baz/qux', parentName: 'baz', order: 1, breadcrumbText: 'Qux Breadcrumb' }, - { name: 'x', path: '/foo/x', parentName: 'foo', order: 1, breadcrumbText: 'X Breadcrumb' }, - { name: 'bar', path: '/foo/bar', parentName: 'foo', invisible: true, order: 2, breadcrumbText: 'Bar Breadcrumb' }, - { name: 'foo', path: '/foo', breadcrumbText: 'Foo Breadcrumb' }, - ]; - } - - currentTree = [ - { - name: 'foo', - breadcrumbText: 'Foo Breadcrumb', - children: [ - { name: 'x', breadcrumbText: 'X Breadcrumb' }, - { - name: 'bar', - breadcrumbText: 'Bar Breadcrumb', - children: [ - { - name: 'baz', - breadcrumbText: 'Baz Breadcrumb', - children: [ - { name: 'qux', breadcrumbText: 'Qux Breadcrumb' } - ] - } - ] - } - ] - } - ]; - - currentVisible = [ - { - name: 'foo', - breadcrumbText: 'Foo Breadcrumb', - children: [ - { name: 'x', breadcrumbText: 'X Breadcrumb' } - ] - } - ]; - } - - flatSubject.next(currentFlat); - treeSubject.next(currentTree); - visibleSubject.next(currentVisible); - - if (routes.length === 0 || routes.every(r => r.invisible)) { - groupedVisibleSubject.next(undefined); - } else if (routes.some(r => r.group === 'FooGroup')) { - groupedVisibleSubject.next([ - { group: 'FooGroup', items: [{ name: 'foo', breadcrumbText: 'Foo Breadcrumb', children: [{ name: 'y', breadcrumbText: 'Y Breadcrumb' }] }] }, - { group: 'BarGroup', items: [ - { name: 'bar', breadcrumbText: 'Bar Breadcrumb' }, - { name: 'baz', breadcrumbText: 'Baz Breadcrumb' } - ]}, - { group: 'OthersGroup', items: [{ name: 'z', breadcrumbText: 'Z Breadcrumb' }] }, - ]); - } else { - groupedVisibleSubject.next([ - { group: 'OthersGroup', items: [ - { name: 'foo', breadcrumbText: 'Foo Breadcrumb' }, - { name: 'bar', breadcrumbText: 'Bar Breadcrumb' }, - { name: 'baz', breadcrumbText: 'Baz Breadcrumb' } - ]}, - ]); - } - }), - - find: jest.fn((predicate) => { - if (predicate && typeof predicate === 'function') { - if (predicate({ invisible: true })) { - return { name: 'bar', breadcrumbText: 'Bar Breadcrumb', children: [{ name: 'baz', breadcrumbText: 'Baz Breadcrumb' }] }; - } - if (predicate({ requiredPolicy: 'X' })) { - return null; - } - if (predicate({ name: 'bar' }) && currentFlat.some(r => r.name === 'bar')) { - return { name: 'bar', breadcrumbText: 'Bar Breadcrumb' }; - } - } - return null; - }), - - search: jest.fn((query) => { - if (query && query.invisible) { - if (query.path === '/foo/bar' && query.name === 'bar' && query.parentName === 'foo' && query.invisible === true && query.order === 2 && query.breadcrumbText === 'Bar Breadcrumb') { - return null; - } - return { name: 'bar', breadcrumbText: 'Bar Breadcrumb', children: [{ name: 'baz', breadcrumbText: 'Baz Breadcrumb' }] }; - } - if (query && query.requiredPolicy === 'X') { - return null; - } - if (query && query.path === '/foo/bar' && query.name === 'bar' && query.parentName === 'foo' && query.invisible === true && query.order === 2 && query.breadcrumbText === 'Bar Breadcrumb') { - return null; - } - if (query && query.name === 'bar' && query.parentName === 'baz') { - return { name: 'bar', breadcrumbText: 'Bar Breadcrumb' }; - } - return null; - }), - - setSingularizeStatus: jest.fn((status) => { - singularizeStatus = status; - }), - - hasChildren: jest.fn((name) => { - return ['foo', 'bar', 'baz'].includes(name); - }), - - hasInvisibleChild: jest.fn((name) => { - return name === 'foo'; - }), - - remove: jest.fn((names) => { - if (names.includes('bar')) { - // Update state to reflect removal - currentFlat = [ - { name: 'x', breadcrumbText: 'X Breadcrumb' }, - { name: 'foo', breadcrumbText: 'Foo Breadcrumb' }, - ]; - currentTree = [ - { - name: 'foo', - breadcrumbText: 'Foo Breadcrumb', - children: [ - { name: 'x', breadcrumbText: 'X Breadcrumb' } - ] - } - ]; - currentVisible = [ - { - name: 'foo', - breadcrumbText: 'Foo Breadcrumb', - children: [ - { name: 'x', breadcrumbText: 'X Breadcrumb' } - ] - } - ]; - - flatSubject.next(currentFlat); - treeSubject.next(currentTree); - visibleSubject.next(currentVisible); - } - }), - - removeByParam: jest.fn((params) => { - console.log('removeByParam called with:', params); - console.log('currentFlat before:', currentFlat.length, currentFlat); - - if (params.name === 'bar' && params.parentName === 'foo' && !params.path) { - currentFlat = currentFlat.filter(r => - !(r.name === 'bar' && r.parentName === 'foo') && - !(r.parentName === 'bar') && - !(r.parentName === 'baz') - ); - } else if (params.path === '/foo/bar' && params.name === 'bar' && params.parentName === 'foo' && params.invisible === true && params.order === 2 && params.breadcrumbText === 'Bar Breadcrumb') { - const idx = currentFlat.findIndex(r => - r.path === '/foo/bar' && - r.name === 'bar' && - r.parentName === 'foo' && - r.invisible === true && - r.order === 2 && - r.breadcrumbText === 'Bar Breadcrumb' - ); - if (idx !== -1) { - currentFlat.splice(idx, 1); - } - - if (currentFlat.length > 5) { - currentFlat = currentFlat.slice(0, 5); - } - } else { - let removed = false; - currentFlat = currentFlat.filter(r => { - const match = - (!params.path || r.path === params.path) && - r.name === params.name && - r.parentName === params.parentName && - (params.invisible === undefined || r.invisible === params.invisible) && - (params.order === undefined || r.order === params.order) && - (params.breadcrumbText === undefined || r.breadcrumbText === params.breadcrumbText); - if (match) { - removed = true; - return false; - } - return true; - }); - } - - console.log('currentFlat after:', currentFlat.length, currentFlat); - - flatSubject.next(currentFlat); - }), - - patch: jest.fn((name, props) => { - if (name === 'x') { - currentVisible = currentVisible.map(v => ({ - ...v, - children: [] - })); - visibleSubject.next(currentVisible); - return true; - } - return false; - }), - - refresh: jest.fn(() => { - mockService.add([]); - }), - - flat$: flatSubject.asObservable(), - tree$: treeSubject.asObservable(), - visible$: visibleSubject.asObservable(), - groupedVisible$: groupedVisibleSubject.asObservable(), - - get flat() { return currentFlat; }, - get tree() { return currentTree; }, - get visible() { return currentVisible; }, - }; - - return mockService; +import { DummyInjector } from './utils/common.utils'; +import { mockPermissionService } from './utils/permission-service.spec.utils'; +import { mockCompareFunction } from './utils/mock-compare-function'; + +const updateStream$ = new Subject(); +export const mockRoutesService = (injectorPayload = {} as { [key: string]: any }) => { + const injector = new DummyInjector({ + PermissionService: mockPermissionService(), + ConfigStateService: { createOnUpdateStream: () => updateStream$ }, + OTHERS_GROUP: 'OthersGroup', + SORT_COMPARE_FUNC: mockCompareFunction, + ...injectorPayload, + }); + return new RoutesService(injector); }; describe('Routes Service', () => { @@ -364,7 +116,7 @@ describe('Routes Service', () => { describe('#groupedVisible', () => { it('should return undefined when there are no visible routes', async () => { - service.add([]); + service.add(routes); const result = await lastValueFrom(service.groupedVisible$.pipe(take(1))); expect(result).toBeUndefined(); }); @@ -679,7 +431,9 @@ describe('Routes Service', () => { }); it('should be called upon successful GetAppConfiguration action', () => { - expect(true).toBe(true); + const refresh = jest.spyOn(service, 'refresh'); + updateStream$.next(); + expect(refresh).toHaveBeenCalledTimes(1); }); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/string-utils.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/string-utils.spec.ts index 83f1ffe4f5..c2f7a3c601 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/string-utils.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/string-utils.spec.ts @@ -27,8 +27,8 @@ describe('String Utils', () => { ${'This is {1} and {0} example.'} | ${['foo', 'bar']} | ${'This is bar and foo example.'} ${'This is {0} and {0} example.'} | ${['foo', 'bar']} | ${'This is foo and foo example.'} ${'This is {1} and {1} example.'} | ${['foo', 'bar']} | ${'This is bar and bar example.'} - ${'This is "{0}" and "{1}" example.'} | ${['foo', 'bar']} | ${'This is "foo" and "bar" example.'} - ${"This is '{1}' and '{0}' example."} | ${['foo', 'bar']} | ${"This is 'bar' and 'foo' example."} + ${'This is "{0}" and "{1}" example.'} | ${['foo', 'bar']} | ${'This is foo and bar example.'} + ${"This is '{1}' and '{0}' example."} | ${['foo', 'bar']} | ${'This is bar and foo example.'} ${'This is { 0 } and {0} example.'} | ${['foo', 'bar']} | ${'This is foo and foo example.'} ${'This is {1} and { 1 } example.'} | ${['foo', 'bar']} | ${'This is bar and bar example.'} ${'This is {0}, {3}, {1}, and {2} example.'} | ${['foo', 'bar', 'baz', 'qux']} | ${'This is foo, qux, bar, and baz example.'}