mirror of https://github.com/abpframework/abp.git
20 changed files with 735 additions and 1517 deletions
@ -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: '<router-outlet></router-outlet>', |
|||
standalone: true, |
|||
imports: [RouterOutlet], |
|||
}) |
|||
class DummyApplicationLayoutComponent {} |
|||
|
|||
@Component({ |
|||
selector: 'abp-layout-account', |
|||
template: '<router-outlet></router-outlet>', |
|||
standalone: true, |
|||
imports: [RouterOutlet], |
|||
}) |
|||
class DummyAccountLayoutComponent {} |
|||
|
|||
@Component({ |
|||
selector: 'abp-layout-empty', |
|||
template: '<router-outlet></router-outlet>', |
|||
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<DynamicLayoutComponent>; |
|||
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: '<router-outlet></router-outlet>', |
|||
}) |
|||
class DummyApplicationLayoutComponent {} |
|||
|
|||
@Component({ |
|||
selector: 'abp-layout-account', |
|||
template: '<router-outlet></router-outlet>', |
|||
}) |
|||
class DummyAccountLayoutComponent {} |
|||
|
|||
@Component({ |
|||
selector: 'abp-layout-empty', |
|||
template: '<router-outlet></router-outlet>', |
|||
}) |
|||
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<RouterOutletComponent>; |
|||
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(); |
|||
}); |
|||
}); |
|||
|
|||
@ -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<LocalizationService>; |
|||
let sessionState: SpyObject<SessionStateService>; |
|||
let configState: SpyObject<ConfigStateService>; |
|||
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<string, Record<string, string>>(); |
|||
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.'); |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
@ -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: ' <p>default</p> ', |
|||
exportAs: 'abpDefaultComponent', |
|||
standalone: true, |
|||
}) |
|||
class DefaultComponent { |
|||
@Input() |
|||
oneWay; |
|||
|
|||
@Input() |
|||
twoWay: boolean; |
|||
|
|||
@Output() |
|||
readonly twoWayChange = new EventEmitter<boolean>(); |
|||
|
|||
@Output() |
|||
readonly someOutput = new EventEmitter<string>(); |
|||
|
|||
setTwoWay(value) { |
|||
this.twoWay = value; |
|||
this.twoWayChange.emit(value); |
|||
} |
|||
} |
|||
|
|||
@Component({ |
|||
selector: 'abp-external-component', |
|||
template: ' <p>external</p> ', |
|||
standalone: true, |
|||
}) |
|||
class ExternalComponent { |
|||
data = inject<ReplaceableComponents.ReplaceableTemplateData<any, any>>('REPLACEABLE_DATA' as any, { optional: true })!; |
|||
|
|||
} |
|||
|
|||
describe('ReplaceableTemplateDirective', () => { |
|||
let spectator: SpectatorDirective<ReplaceableTemplateDirective>; |
|||
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( |
|||
` |
|||
<div *abpReplaceableTemplate="{inputs: {oneWay: {value: oneWay}, twoWay: {value: twoWay, twoWay: true}}, outputs: {twoWayChange: twoWayChange, someOutput: someOutput}, componentKey: 'TestModule.TestComponent'}; let initTemplate = initTemplate"> |
|||
<abp-default-component #defaultComponent="abpDefaultComponent"></abp-default-component> |
|||
</div> |
|||
`,
|
|||
{ |
|||
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( |
|||
` |
|||
<div *abpReplaceableTemplate="{inputs: {oneWay: {value: oneWay}, twoWay: {value: twoWay, twoWay: true}}, outputs: {twoWayChange: twoWayChange, someOutput: someOutput}, componentKey: 'TestModule.TestComponent'}; let initTemplate = initTemplate"> |
|||
<abp-default-component #defaultComponent="abpDefaultComponent"></abp-default-component> |
|||
</div> |
|||
`,
|
|||
{ 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: ' <p>default</p> ', |
|||
exportAs: 'abpDefaultComponent', |
|||
}) |
|||
class DefaultComponent { |
|||
@Input() |
|||
oneWay; |
|||
|
|||
@Input() |
|||
twoWay: boolean; |
|||
|
|||
@Output() |
|||
readonly twoWayChange = new EventEmitter<boolean>(); |
|||
|
|||
@Output() |
|||
readonly someOutput = new EventEmitter<string>(); |
|||
|
|||
setTwoWay(value) { |
|||
this.twoWay = value; |
|||
this.twoWayChange.emit(value); |
|||
} |
|||
} |
|||
|
|||
@Component({ |
|||
selector: 'abp-external-component', |
|||
template: ' <p>external</p> ', |
|||
}) |
|||
class ExternalComponent {
data = inject<ReplaceableComponents.ReplaceableTemplateData<any, any>>('REPLACEABLE_DATA' as any, { optional: true })!; |
|||
|
|||
} |
|||
|
|||
describe('ReplaceableTemplateDirective', () => { |
|||
let spectator: SpectatorDirective<ReplaceableTemplateDirective>; |
|||
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( |
|||
` |
|||
<div *abpReplaceableTemplate="{inputs: {oneWay: {value: oneWay}, twoWay: {value: twoWay, twoWay: true}}, outputs: {twoWayChange: twoWayChange, someOutput: someOutput}, componentKey: 'TestModule.TestComponent'}; let initTemplate = initTemplate"> |
|||
<abp-default-component #defaultComponent="abpDefaultComponent"></abp-default-component> |
|||
</div> |
|||
`,
|
|||
{ |
|||
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( |
|||
` |
|||
<div *abpReplaceableTemplate="{inputs: {oneWay: {value: oneWay}, twoWay: {value: twoWay, twoWay: true}}, outputs: {twoWayChange: twoWayChange, someOutput: someOutput}, componentKey: 'TestModule.TestComponent'}; let initTemplate = initTemplate"> |
|||
<abp-default-component #defaultComponent="abpDefaultComponent"></abp-default-component> |
|||
</div> |
|||
`,
|
|||
{ 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(); |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
Loading…
Reference in new issue