mirror of https://github.com/abpframework/abp.git
38 changed files with 605 additions and 585 deletions
@ -1,201 +1,201 @@ |
|||
import { HttpClient } from '@angular/common/http'; |
|||
import { Component, NgModule } 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 { |
|||
constructor(public route: 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(); |
|||
}); |
|||
}); |
|||
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,178 +1,174 @@ |
|||
import { Component, EventEmitter, Inject, Input, Optional, Output } 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 { |
|||
constructor( |
|||
@Optional() |
|||
@Inject('REPLACEABLE_DATA') |
|||
public data: ReplaceableComponents.ReplaceableTemplateData<any, any>, |
|||
) {} |
|||
} |
|||
|
|||
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(); |
|||
}); |
|||
}); |
|||
}); |
|||
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